diff --git a/services/github/github-actions-workflow-status.service.js b/services/github/github-actions-workflow-status.service.js new file mode 100644 index 0000000000000..2f1896589510d --- /dev/null +++ b/services/github/github-actions-workflow-status.service.js @@ -0,0 +1,101 @@ +import Joi from 'joi' +import { isBuildStatus, renderBuildStatusBadge } from '../build-status.js' +import { NotFound } from '../index.js' +import { GithubAuthV3Service } from './github-auth-service.js' +import { documentation, errorMessagesFor } from './github-helpers.js' + +const schema = Joi.object({ + workflow_runs: Joi.array() + .items( + Joi.object({ + conclusion: Joi.alternatives() + .try(isBuildStatus, Joi.equal('no status')) + .required(), + }) + ) + .required() + .min(0) + .max(1), +}).required() + +const queryParamSchema = Joi.object({ + event: Joi.string(), + branch: Joi.string().required(), +}).required() + +const keywords = ['action', 'actions'] + +export default class GithubActionsWorkflowStatus extends GithubAuthV3Service { + static category = 'build' + + static route = { + base: 'github/actions/workflow/status', + pattern: ':user/:repo/:workflow+', + queryParamSchema, + } + + static examples = [ + { + title: 'GitHub Workflow Status', + namedParams: { + user: 'actions', + repo: 'toolkit', + workflow: 'unit-tests.yml', + }, + queryParams: { + branch: 'main', + }, + staticPreview: renderBuildStatusBadge({ + status: 'passing', + }), + documentation, + keywords, + }, + { + title: 'GitHub Workflow Status (with event)', + namedParams: { + user: 'actions', + repo: 'toolkit', + workflow: 'unit-tests.yml', + }, + queryParams: { + event: 'push', + branch: 'main', + }, + staticPreview: renderBuildStatusBadge({ + status: 'passing', + }), + documentation, + keywords, + }, + ] + + static defaultBadgeData = { + label: 'build', + } + + async fetch({ user, repo, workflow, branch, event }) { + return await this._requestJson({ + schema, + url: `/repos/${user}/${repo}/actions/workflows/${workflow}/runs`, + options: { + searchParams: { + branch, + event, + page: '1', + per_page: '1', + exclude_pull_requests: 'true', + }, + }, + errorMessages: errorMessagesFor('repo or workflow not found'), + }) + } + + async handle({ user, repo, workflow }, { branch, event }) { + const data = await this.fetch({ user, repo, workflow, branch, event }) + if (data.workflow_runs.length === 0) { + throw new NotFound({ prettyMessage: 'branch or event not found' }) + } + return renderBuildStatusBadge({ status: data.workflow_runs[0].conclusion }) + } +} diff --git a/services/github/github-actions-workflow-status.tester.js b/services/github/github-actions-workflow-status.tester.js new file mode 100644 index 0000000000000..9ebc19e4a641c --- /dev/null +++ b/services/github/github-actions-workflow-status.tester.js @@ -0,0 +1,59 @@ +import Joi from 'joi' +import { isBuildStatus } from '../build-status.js' +import { createServiceTester } from '../tester.js' +export const t = await createServiceTester() + +const isWorkflowStatus = Joi.alternatives() + .try(isBuildStatus, Joi.equal('no status')) + .required() + +t.create('missing branch param') + .get('/actions/toolkit/unit-tests.yml.json') + .expectBadge({ + label: 'build', + message: 'invalid query parameter: branch', + }) + +t.create('nonexistent repo') + .get('/badges/shields-fakeness/fake.yml.json?branch=main') + .expectBadge({ + label: 'build', + message: 'repo or workflow not found', + }) + +t.create('nonexistent workflow') + .get('/actions/toolkit/not-a-real-workflow.yml.json?branch=main') + .expectBadge({ + label: 'build', + message: 'repo or workflow not found', + }) + +t.create('nonexistent branch') + .get('/actions/toolkit/unit-tests.yml.json?branch=not-a-real-branch') + .expectBadge({ + label: 'build', + message: 'branch or event not found', + }) + +t.create('nonexistent event') + .get( + '/actions/toolkit/unit-tests.yml.json?branch=main&event=not-a-real-event' + ) + .expectBadge({ + label: 'build', + message: 'branch or event not found', + }) + +t.create('valid workflow') + .get('/actions/toolkit/unit-tests.yml.json?branch=main') + .expectBadge({ + label: 'build', + message: isWorkflowStatus, + }) + +t.create('valid workflow (with event)') + .get('/actions/toolkit/unit-tests.yml.json?branch=main&event=push') + .expectBadge({ + label: 'build', + message: isWorkflowStatus, + }) diff --git a/services/github/github-workflow-status.service.js b/services/github/github-workflow-status.service.js index d1d47ca0bb634..69e6e8f286d8d 100644 --- a/services/github/github-workflow-status.service.js +++ b/services/github/github-workflow-status.service.js @@ -1,100 +1,28 @@ -import Joi from 'joi' -import { isBuildStatus, renderBuildStatusBadge } from '../build-status.js' -import { BaseSvgScrapingService } from '../index.js' -import { documentation } from './github-helpers.js' +import { BaseService } from '../index.js' -const schema = Joi.object({ - message: Joi.alternatives() - .try(isBuildStatus, Joi.equal('no status')) - .required(), -}).required() - -const queryParamSchema = Joi.object({ - event: Joi.string(), -}).required() - -const keywords = ['action', 'actions'] - -export default class GithubWorkflowStatus extends BaseSvgScrapingService { +export default class DeprecatedGithubWorkflowStatus extends BaseService { static category = 'build' static route = { base: 'github/workflow/status', - pattern: ':user/:repo/:workflow/:branch*', - queryParamSchema, - } - - static examples = [ - { - title: 'GitHub Workflow Status', - pattern: ':user/:repo/:workflow', - namedParams: { - user: 'actions', - repo: 'toolkit', - workflow: 'toolkit-unit-tests', - }, - staticPreview: renderBuildStatusBadge({ - status: 'passing', - }), - documentation, - keywords, - }, - { - title: 'GitHub Workflow Status (branch)', - pattern: ':user/:repo/:workflow/:branch', - namedParams: { - user: 'actions', - repo: 'toolkit', - workflow: 'toolkit-unit-tests', - branch: 'master', - }, - staticPreview: renderBuildStatusBadge({ - status: 'passing', - }), - documentation, - keywords, - }, - { - title: 'GitHub Workflow Status (event)', - pattern: ':user/:repo/:workflow', - namedParams: { - user: 'actions', - repo: 'toolkit', - workflow: 'toolkit-unit-tests', - }, - queryParams: { - event: 'push', - }, - staticPreview: renderBuildStatusBadge({ - status: 'passing', - }), - documentation, - keywords, - }, - ] - - static defaultBadgeData = { - label: 'build', - } - - async fetch({ user, repo, workflow, branch, event }) { - const { message: status } = await this._requestSvg({ - schema, - url: `https://github.com/${user}/${repo}/workflows/${encodeURIComponent( - workflow - )}/badge.svg`, - options: { searchParams: { branch, event } }, - valueMatcher: />([^<>]+)<\/tspan><\/text><\/g>