import { RestEndpointMethodTypes } from '@octokit/plugin-rest-endpoint-methods' import { sumBy, min, max } from 'lodash' import { Analyzer, diffSec, Status, TestReport, WorkflowParams, convertToTestReports } from './analyzer' import { RepositoryTagMap } from '../client/github_client' import { Artifact } from '../client/client' export type WorkflowRunsItem = RestEndpointMethodTypes['actions']['listWorkflowRunsForRepo']['response']['data']['workflow_runs'][0] export type JobsItem = RestEndpointMethodTypes['actions']['listJobsForWorkflowRun']['response']['data']['jobs'] type WorkflowReport = { // workflow service: 'github', workflowId: string, // = ${repositroy}-${workflowName} workflowRunId: string // = ${repository}-${workflowName}-${buildNumber} buildNumber: number, // = run_number workflowName: string, createdAt: Date, trigger: string // = event status: Status, repository: string, headSha: string, branch: string, tag: string, // Detect from Github API response jobs: JobReport[], startedAt: Date, // = min(jobs startedAt) completedAt: Date // = max(jobs completedAt) workflowDurationSec: number // = completedAt - startedAt sumJobsDurationSec: number // = sum(jobs sumStepsDurationSec) successCount: 0 | 1 // = 'SUCCESS': 1, others: 0 parameters: [] // GithubAnalyzer does not support build parameters yet queuedDurationSec: number // createdAt - startedAt commitMessage: string actor: string // Kenta Kase url: string // https://github.com/{org}/{repo}/actions/runs/{runId} } type JobReport = { workflowRunId: string, // = workflowRunId buildNumber: number | undefined, // undefined. Github Action does not provide job build number jobId: string, // = id jobName: string, status: Status, startedAt: Date, completedAt: Date, jobDurationSec: number, // = completedAt - startedAt sumStepsDurationSec: number // = sum(steps duration) steps: StepReport[], url: string // https://github.com/{org}/{repo}/runs/{jobId}?check_suite_focus=true executorClass: '', // Github Actions does not provide about class of executor executorType: '', // Github Actions does not provde about type of executor executorName: string // self-hosted runner name } type StepReport = { name: string, status: Status, number: number, startedAt: Date, completedAt: Date, stepDurationSec: number // completedAt - startedAt } export class GithubAnalyzer implements Analyzer { constructor() { } createWorkflowParams(workflowName: string, workflow: WorkflowRunsItem): WorkflowParams { const buildNumber = workflow.run_number const repository = workflow.repository.full_name return { workflowName, buildNumber, workflowId: `${repository}-${workflowName}`, workflowRunId: `${repository}-${workflowName}-${buildNumber}`, } } createWorkflowReport(workflowName: string, workflow: WorkflowRunsItem, jobs: JobsItem, tagMap: RepositoryTagMap): WorkflowReport { const { workflowId, buildNumber, workflowRunId } = this.createWorkflowParams(workflowName, workflow) const jobReports: JobReport[] = jobs.map((job) => { const stepReports: StepReport[] = job.steps!.map((step) => { const startedAt = new Date(step.started_at!) const completedAt = new Date(step.completed_at!) // step return { name: step.name, status: this.normalizeStatus(step.conclusion), number: step.number, startedAt, completedAt, stepDurationSec: diffSec(startedAt, completedAt) } }) const startedAt = new Date(job.started_at) const completedAt = new Date(job.completed_at!) // job return { workflowRunId: workflowRunId, buildNumber: buildNumber, // Github Actions job does not have buildNumber jobId: String(job.id), jobName: job.name, status: this.normalizeStatus(job.conclusion), startedAt, completedAt, jobDurationSec: diffSec(startedAt, completedAt), sumStepsDurationSec: sumBy(stepReports, 'stepDurationSec'), steps: stepReports, url: job.html_url ?? '', executorClass: '', executorType: '', executorName: job.runner_name ?? '', } }) const createdAt = new Date(workflow.created_at) const startedAt = min(jobReports.map((job) => job.startedAt )) || createdAt const completedAt = max(jobReports.map((job) => job.completedAt )) || createdAt const status = this.normalizeStatus(workflow.conclusion as unknown as string) // workflow return { service: 'github', workflowId, buildNumber, workflowRunId, workflowName, createdAt, trigger: workflow.event, status, repository: workflow.repository.full_name, headSha: workflow.head_sha, branch: workflow.head_branch ?? '', tag: tagMap.get(workflow.head_sha) ?? '', jobs: jobReports, startedAt, completedAt, workflowDurationSec: diffSec(startedAt, completedAt), sumJobsDurationSec: sumBy(jobReports, 'sumStepsDurationSec'), successCount: (status === 'SUCCESS') ? 1 : 0, parameters: [], queuedDurationSec: diffSec(createdAt, startedAt), commitMessage: workflow.head_commit?.message ?? '', actor: workflow.head_commit?.author?.name ?? '', url: workflow.html_url, } } normalizeStatus(status: string | null): Status { switch (status) { case 'success': return 'SUCCESS' case 'failure': return 'FAILURE' case 'cancelled': return 'ABORTED' case 'timed_out': return 'ABORTED' default: return 'OTHER'; } } async createTestReports(workflowReport: WorkflowReport, junitArtifacts: Artifact[]): Promise<TestReport[]> { return await convertToTestReports(workflowReport, junitArtifacts) } }