diff --git a/pkg/github/workflows.go b/pkg/github/workflows.go index ad531c1..3cef553 100644 --- a/pkg/github/workflows.go +++ b/pkg/github/workflows.go @@ -109,6 +109,13 @@ func GetWorkflowRuns( for _, runRaw := range runs.WorkflowRuns { run := types.NewWorkflowRunFromRaw(runRaw) + duration, err := GetWorkflowRunDuration(ctx, l, client, run) + if err != nil { + return nil, err + } + + run.WorkflowDuration = duration + workflowRuns = append(workflowRuns, run) } @@ -122,6 +129,34 @@ func GetWorkflowRuns( return workflowRuns, nil } +// GetWorkflowRunDuration gets the total amount of time that a workflow run took. +// This is retrieved through GitHub's usage API and is not available in a WorkflowRun object itself. +func GetWorkflowRunDuration( + ctx context.Context, + logger *slog.Logger, + client *github.Client, + run *types.WorkflowRun, +) (time.Duration, error) { + l := logger.With("workflow-id", run.ID) + + l.Debug("Pulling run duration for workflow") + + usage, _, err := WrapWithRateLimitRetry[github.WorkflowRunUsage]( + ctx, l, + func() (*github.WorkflowRunUsage, *github.Response, error) { + return client.Actions.GetWorkflowRunUsageByID(ctx, run.Repository.Owner.Login, run.Repository.Name, run.ID) + }, + ) + + if err != nil { + return -1, fmt.Errorf( + "unable to pull workflow run duration for run with ID %d: %w", run.ID, err, + ) + } + + return time.Duration(usage.GetRunDurationMS() * 1000000), nil +} + // GetTestsForWorkflowRun checks if the given WorkflowRun contains a known JUnit artifact. // If a JUnit file is found and is recognized, it will be downloaded and parsed into a set of TestSuite // and Testcase objects. diff --git a/pkg/types/types.go b/pkg/types/types.go index fffdd4c..f2fdc78 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -93,6 +93,7 @@ type WorkflowRun struct { HeadSHA string `json:"head_sha,omitempty"` HeadCommit Commit `json:"head_commit,omitempty"` WorkflowDispatchInputs map[string]string `json:"workflow_dispatch_inputs,omitempty"` + WorkflowDuration time.Duration `json:"workflow_duration,omitempty"` } func NewWorkflowRunFromRaw(runRaw *github.WorkflowRun) *WorkflowRun { @@ -188,9 +189,9 @@ type JobRun struct { Name string `json:"job_name,omitempty"` Logs string `json:"job_logs,omitempty"` // ErrorLogs contains log lines that contain an error. - ErrorLogs []string `json:"job_error_logs,omitempty"` - Link string `json:"job_link,omitempty"` - Duration time.Duration `json:"job_duration,omitempty"` + ErrorLogs []string `json:"job_error_logs,omitempty"` + Link string `json:"job_link,omitempty"` + JobDuration time.Duration `json:"job_duration,omitempty"` } func NewJobRunFromRaw(parent *WorkflowRun, jobRaw *github.WorkflowJob) *JobRun { @@ -208,7 +209,7 @@ func NewJobRunFromRaw(parent *WorkflowRun, jobRaw *github.WorkflowJob) *JobRun { StartedAt: jobRaw.GetStartedAt().Time, CompletedAt: jobRaw.GetCompletedAt().Time, Name: jobRaw.GetName(), - Duration: jobRaw.CompletedAt.Sub(jobRaw.StartedAt.Time), + JobDuration: jobRaw.CompletedAt.Sub(jobRaw.StartedAt.Time), } job.Link = fmt.Sprintf( "https://github.com/%s/%s/actions/runs/%d/job/%d",