diff --git a/pkg/github/actions.go b/pkg/github/actions.go index c16efa0f18..1629b818f7 100644 --- a/pkg/github/actions.go +++ b/pkg/github/actions.go @@ -884,7 +884,7 @@ func listWorkflowRuns(ctx context.Context, client *github.Client, args map[strin } defer func() { _ = resp.Body.Close() }() - r, err := json.Marshal(workflowRuns) + r, err := json.Marshal(convertToMinimalWorkflowRuns(workflowRuns)) if err != nil { return nil, nil, fmt.Errorf("failed to marshal workflow runs: %w", err) } @@ -919,7 +919,7 @@ func listWorkflowJobs(ctx context.Context, client *github.Client, args map[strin } response := map[string]any{ - "jobs": workflowJobs, + "jobs": convertToMinimalWorkflowJobs(workflowJobs), } defer func() { _ = resp.Body.Close() }() diff --git a/pkg/github/actions_minimal_test.go b/pkg/github/actions_minimal_test.go new file mode 100644 index 0000000000..4f0f8bd977 --- /dev/null +++ b/pkg/github/actions_minimal_test.go @@ -0,0 +1,267 @@ +package github + +import ( + "encoding/json" + "testing" + "time" + + "github.com/google/go-github/v89/github" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestConvertToMinimalWorkflowRun(t *testing.T) { + workflowRun := actionsTestWorkflowRun() + + minimal := convertToMinimalWorkflowRun(workflowRun) + + assert.Equal(t, workflowRun.GetID(), minimal.ID) + assert.Equal(t, workflowRun.GetWorkflowID(), minimal.WorkflowID) + assert.Equal(t, workflowRun.GetDisplayTitle(), minimal.DisplayTitle) + assert.Equal(t, workflowRun.GetHeadSHA(), minimal.HeadSHA) + assert.Equal(t, []int{42}, minimal.PullRequests) + require.NotNil(t, minimal.HeadCommit) + assert.Equal(t, "Reduce GitHub Actions response payloads", minimal.HeadCommit.Message) + require.Len(t, minimal.ReferencedWorkflows, 1) + assert.Equal(t, ".github/workflows/reusable-tests.yml", minimal.ReferencedWorkflows[0].Path) + assert.Equal(t, "refs/tags/v3", minimal.ReferencedWorkflows[0].Ref) + assert.Equal(t, "9f4f87d9790ab0f5c2c5ad2b74b886cab515a886", minimal.ReferencedWorkflows[0].SHA) + require.NotNil(t, minimal.Actor) + assert.Equal(t, "octocat", minimal.Actor.Login) + require.NotNil(t, minimal.TriggeringActor) + assert.Equal(t, "hubot", minimal.TriggeringActor.Login) + + payload := marshalActionsObject(t, minimal) + assert.NotContains(t, payload, "node_id") + assert.NotContains(t, payload, "repository") + assert.NotContains(t, payload, "head_repository") + assert.NotContains(t, payload, "jobs_url") + assert.NotContains(t, payload, "logs_url") + assert.NotContains(t, payload, "artifacts_url") + assert.Equal(t, map[string]any{ + "message": "Reduce GitHub Actions response payloads", + }, payload["head_commit"]) + assert.Equal(t, []any{ + map[string]any{ + "path": ".github/workflows/reusable-tests.yml", + "sha": "9f4f87d9790ab0f5c2c5ad2b74b886cab515a886", + "ref": "refs/tags/v3", + }, + }, payload["referenced_workflows"]) +} + +func TestConvertToMinimalWorkflowJob(t *testing.T) { + workflowJob := actionsTestWorkflowJob() + + minimal := convertToMinimalWorkflowJob(workflowJob) + + assert.Equal(t, workflowJob.GetID(), minimal.ID) + assert.Equal(t, workflowJob.GetRunID(), minimal.RunID) + assert.Equal(t, workflowJob.GetRunnerID(), minimal.RunnerID) + assert.Equal(t, workflowJob.GetRunnerName(), minimal.RunnerName) + assert.Equal(t, workflowJob.GetRunnerGroupID(), minimal.RunnerGroupID) + assert.Equal(t, workflowJob.GetRunnerGroupName(), minimal.RunnerGroupName) + assert.Equal(t, workflowJob.GetLabels(), minimal.Labels) + require.Len(t, minimal.Steps, 2) + assert.Equal(t, "Run tests", minimal.Steps[1].Name) + assert.Equal(t, "failure", minimal.Steps[1].Conclusion) + + payload := marshalActionsObject(t, minimal) + assert.NotContains(t, payload, "node_id") + assert.NotContains(t, payload, "url") + assert.NotContains(t, payload, "run_url") + assert.NotContains(t, payload, "check_run_url") + assert.Equal(t, float64(1), payload["runner_id"]) + assert.Equal(t, float64(2), payload["runner_group_id"]) + assert.Equal(t, "GitHub Actions", payload["runner_group_name"]) +} + +func TestConvertToMinimalActionsLists(t *testing.T) { + t.Run("workflow runs", func(t *testing.T) { + result := convertToMinimalWorkflowRuns(&github.WorkflowRuns{ + TotalCount: github.Ptr(2), + WorkflowRuns: []*github.WorkflowRun{actionsTestWorkflowRun(), nil}, + }) + assert.Equal(t, 2, result.TotalCount) + assert.Len(t, result.WorkflowRuns, 1) + }) + + t.Run("workflow jobs", func(t *testing.T) { + result := convertToMinimalWorkflowJobs(&github.Jobs{ + TotalCount: github.Ptr(2), + Jobs: []*github.WorkflowJob{actionsTestWorkflowJob(), nil}, + }) + assert.Equal(t, 2, result.TotalCount) + assert.Len(t, result.Jobs, 1) + }) + + t.Run("nil workflow runs", func(t *testing.T) { + result := convertToMinimalWorkflowRuns(nil) + assert.NotNil(t, result.WorkflowRuns) + assert.Empty(t, result.WorkflowRuns) + }) + + t.Run("nil workflow jobs", func(t *testing.T) { + result := convertToMinimalWorkflowJobs(nil) + assert.NotNil(t, result.Jobs) + assert.Empty(t, result.Jobs) + }) +} + +func actionsTestWorkflowRun() *github.WorkflowRun { + repository := &github.Repository{ + ID: github.Ptr(int64(1296269)), + NodeID: github.Ptr("MDEwOlJlcG9zaXRvcnkxMjk2MjY5"), + Name: github.Ptr("octo-repo"), + FullName: github.Ptr("octo-org/octo-repo"), + Description: github.Ptr("A representative repository description included in the full API response."), + HTMLURL: github.Ptr("https://github.com/octo-org/octo-repo"), + URL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo"), + CloneURL: github.Ptr("https://github.com/octo-org/octo-repo.git"), + Language: github.Ptr("Go"), + Topics: []string{"actions", "mcp", "automation"}, + } + + return &github.WorkflowRun{ + ID: github.Ptr(int64(30433642)), + Name: github.Ptr("CI"), + NodeID: github.Ptr("MDEyOldvcmtmbG93IFJ1bjI2OTI4OQ=="), + HeadBranch: github.Ptr("feature/minimal-actions"), + HeadSHA: github.Ptr("acb5820ced9479c074f688cc328bf03f341a511d"), + Path: github.Ptr(".github/workflows/ci.yml"), + RunNumber: github.Ptr(562), + RunAttempt: github.Ptr(2), + Event: github.Ptr("pull_request"), + DisplayTitle: github.Ptr("Reduce GitHub Actions response payloads"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("failure"), + WorkflowID: github.Ptr(int64(161335)), + CheckSuiteID: github.Ptr(int64(42)), + CheckSuiteNodeID: github.Ptr("MDEwOkNoZWNrU3VpdGU0Mg=="), + URL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642"), + HTMLURL: github.Ptr("https://github.com/octo-org/octo-repo/actions/runs/30433642"), + JobsURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/jobs"), + LogsURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/logs"), + CheckSuiteURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/check-suites/42"), + ArtifactsURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/artifacts"), + CancelURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/cancel"), + RerunURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/rerun"), + PreviousAttemptURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/attempts/1"), + WorkflowURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/workflows/161335"), + Repository: repository, + HeadRepository: repository, + Actor: &github.User{ + Login: github.Ptr("octocat"), + ID: github.Ptr(int64(1)), + NodeID: github.Ptr("MDQ6VXNlcjE="), + AvatarURL: github.Ptr("https://github.com/images/error/octocat_happy.gif"), + HTMLURL: github.Ptr("https://github.com/octocat"), + URL: github.Ptr("https://api.github.com/users/octocat"), + Name: github.Ptr("The Octocat"), + Bio: github.Ptr("A long biography that is not needed to identify the workflow run actor."), + }, + TriggeringActor: &github.User{ + Login: github.Ptr("hubot"), + ID: github.Ptr(int64(2)), + HTMLURL: github.Ptr("https://github.com/hubot"), + URL: github.Ptr("https://api.github.com/users/hubot"), + }, + PullRequests: []*github.PullRequest{ + { + ID: github.Ptr(int64(1001)), + Number: github.Ptr(42), + Title: github.Ptr("Reduce GitHub Actions response payloads"), + Body: github.Ptr("A pull request body that is unnecessary in a workflow run response."), + HTMLURL: github.Ptr("https://github.com/octo-org/octo-repo/pull/42"), + Head: &github.PullRequestBranch{ + Ref: github.Ptr("feature/minimal-actions"), + SHA: github.Ptr("acb5820ced9479c074f688cc328bf03f341a511d"), + Repo: repository, + }, + Base: &github.PullRequestBranch{ + Ref: github.Ptr("main"), + SHA: github.Ptr("9a2f3ec"), + Repo: repository, + }, + }, + }, + HeadCommit: &github.HeadCommit{ + Message: github.Ptr("Reduce GitHub Actions response payloads"), + URL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/commits/acb5820"), + Author: &github.CommitAuthor{ + Name: github.Ptr("The Octocat"), + Email: github.Ptr("octocat@example.com"), + }, + }, + ReferencedWorkflows: []*github.ReferencedWorkflow{ + { + Path: github.Ptr(".github/workflows/reusable-tests.yml"), + SHA: github.Ptr("9f4f87d9790ab0f5c2c5ad2b74b886cab515a886"), + Ref: github.Ptr("refs/tags/v3"), + }, + nil, + }, + CreatedAt: actionsTestTimestamp(), + UpdatedAt: actionsTestTimestamp(), + RunStartedAt: actionsTestTimestamp(), + } +} + +func actionsTestWorkflowJob() *github.WorkflowJob { + return &github.WorkflowJob{ + ID: github.Ptr(int64(399444496)), + RunID: github.Ptr(int64(30433642)), + RunURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642"), + NodeID: github.Ptr("MDEyOldvcmtmbG93IEpvYjM5OTQ0NDQ5Ng=="), + HeadBranch: github.Ptr("feature/minimal-actions"), + HeadSHA: github.Ptr("acb5820ced9479c074f688cc328bf03f341a511d"), + URL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/jobs/399444496"), + HTMLURL: github.Ptr("https://github.com/octo-org/octo-repo/runs/399444496"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("failure"), + CreatedAt: actionsTestTimestamp(), + StartedAt: actionsTestTimestamp(), + CompletedAt: actionsTestTimestamp(), + Name: github.Ptr("test (ubuntu-latest, Go 1.24)"), + CheckRunURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/check-runs/399444496"), + Labels: []string{"ubuntu-latest", "x64"}, + RunnerID: github.Ptr(int64(1)), + RunnerName: github.Ptr("GitHub Actions 1"), + RunnerGroupID: github.Ptr(int64(2)), + RunnerGroupName: github.Ptr("GitHub Actions"), + RunAttempt: github.Ptr(int64(2)), + WorkflowName: github.Ptr("CI"), + Steps: []*github.TaskStep{ + { + Name: github.Ptr("Set up job"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("success"), + Number: github.Ptr(int64(1)), + StartedAt: actionsTestTimestamp(), + CompletedAt: actionsTestTimestamp(), + }, + { + Name: github.Ptr("Run tests"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("failure"), + Number: github.Ptr(int64(2)), + StartedAt: actionsTestTimestamp(), + CompletedAt: actionsTestTimestamp(), + }, + }, + } +} + +func actionsTestTimestamp() *github.Timestamp { + return &github.Timestamp{Time: time.Date(2026, time.August, 6, 10, 30, 0, 0, time.UTC)} +} + +func marshalActionsObject(t *testing.T, value any) map[string]any { + t.Helper() + data, err := json.Marshal(value) + require.NoError(t, err) + + var object map[string]any + require.NoError(t, json.Unmarshal(data, &object)) + return object +} diff --git a/pkg/github/actions_test.go b/pkg/github/actions_test.go index 4ed9c87d69..e5c0662cc4 100644 --- a/pkg/github/actions_test.go +++ b/pkg/github/actions_test.go @@ -154,10 +154,12 @@ func Test_ActionsList_ListWorkflowRuns(t *testing.T) { require.False(t, result.IsError) textContent := getTextResult(t, result) - var response github.WorkflowRuns + var response MinimalWorkflowRunsResult err = json.Unmarshal([]byte(textContent.Text), &response) require.NoError(t, err) - assert.NotNil(t, response.TotalCount) + assert.Equal(t, 1, response.TotalCount) + require.Len(t, response.WorkflowRuns, 1) + assert.Equal(t, int64(123), response.WorkflowRuns[0].ID) }) t.Run("list all workflow runs without resource_id", func(t *testing.T) { @@ -202,13 +204,47 @@ func Test_ActionsList_ListWorkflowRuns(t *testing.T) { require.False(t, result.IsError) textContent := getTextResult(t, result) - var response github.WorkflowRuns + var response MinimalWorkflowRunsResult err = json.Unmarshal([]byte(textContent.Text), &response) require.NoError(t, err) - assert.Equal(t, 2, *response.TotalCount) + assert.Equal(t, 2, response.TotalCount) + assert.Len(t, response.WorkflowRuns, 2) }) } +func Test_ActionsList_ListWorkflowJobs(t *testing.T) { + toolDef := ActionsList(translations.NullTranslationHelper) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsJobsByOwnerByRepoByRunID: mockResponse(t, http.StatusOK, &github.Jobs{ + TotalCount: github.Ptr(1), + Jobs: []*github.WorkflowJob{actionsTestWorkflowJob()}, + }), + }) + + client := mustNewGHClient(t, mockedClient) + deps := BaseDeps{Client: client} + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "list_workflow_jobs", + "owner": "owner", + "repo": "repo", + "resource_id": "30433642", + }) + + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + require.NoError(t, err) + require.False(t, result.IsError) + + var response struct { + Jobs MinimalWorkflowJobsResult `json:"jobs"` + } + require.NoError(t, json.Unmarshal([]byte(getTextResult(t, result).Text), &response)) + assert.Equal(t, 1, response.Jobs.TotalCount) + require.Len(t, response.Jobs.Jobs, 1) + assert.Equal(t, int64(399444496), response.Jobs.Jobs[0].ID) + assert.Len(t, response.Jobs.Jobs[0].Steps, 2) +} + func Test_ActionsGet(t *testing.T) { // Verify tool definition once toolDef := ActionsGet(translations.NullTranslationHelper) diff --git a/pkg/github/minimal_types.go b/pkg/github/minimal_types.go index e2bf8b684b..c05c38e7bd 100644 --- a/pkg/github/minimal_types.go +++ b/pkg/github/minimal_types.go @@ -318,6 +318,88 @@ type MinimalTag struct { SHA string `json:"sha"` } +// MinimalWorkflowRunHeadCommit is the trimmed commit context for a workflow run. +type MinimalWorkflowRunHeadCommit struct { + Message string `json:"message"` +} + +// MinimalReferencedWorkflow identifies a reusable workflow invoked by a workflow run. +type MinimalReferencedWorkflow struct { + Path string `json:"path,omitempty"` + SHA string `json:"sha,omitempty"` + Ref string `json:"ref,omitempty"` +} + +// MinimalWorkflowRun is the trimmed output type for GitHub Actions workflow runs. +type MinimalWorkflowRun struct { + ID int64 `json:"id"` + Name string `json:"name"` + DisplayTitle string `json:"display_title,omitempty"` + WorkflowID int64 `json:"workflow_id"` + RunNumber int `json:"run_number"` + RunAttempt int `json:"run_attempt"` + Event string `json:"event,omitempty"` + Status string `json:"status"` + Conclusion string `json:"conclusion,omitempty"` + HeadBranch string `json:"head_branch,omitempty"` + HeadSHA string `json:"head_sha,omitempty"` + HeadCommit *MinimalWorkflowRunHeadCommit `json:"head_commit,omitempty"` + Path string `json:"path,omitempty"` + HTMLURL string `json:"html_url,omitempty"` + PullRequests []int `json:"pull_requests,omitempty"` + Actor *MinimalUser `json:"actor,omitempty"` + TriggeringActor *MinimalUser `json:"triggering_actor,omitempty"` + ReferencedWorkflows []MinimalReferencedWorkflow `json:"referenced_workflows,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + RunStartedAt string `json:"run_started_at,omitempty"` +} + +// MinimalWorkflowRunsResult is the trimmed output type for workflow run list results. +type MinimalWorkflowRunsResult struct { + TotalCount int `json:"total_count"` + WorkflowRuns []MinimalWorkflowRun `json:"workflow_runs"` +} + +// MinimalWorkflowJobStep is the trimmed output type for workflow job steps. +type MinimalWorkflowJobStep struct { + Name string `json:"name"` + Status string `json:"status"` + Conclusion string `json:"conclusion,omitempty"` + Number int64 `json:"number"` + StartedAt string `json:"started_at,omitempty"` + CompletedAt string `json:"completed_at,omitempty"` +} + +// MinimalWorkflowJob is the trimmed output type for GitHub Actions workflow jobs. +type MinimalWorkflowJob struct { + ID int64 `json:"id"` + RunID int64 `json:"run_id"` + Name string `json:"name"` + WorkflowName string `json:"workflow_name,omitempty"` + Status string `json:"status"` + Conclusion string `json:"conclusion,omitempty"` + HeadBranch string `json:"head_branch,omitempty"` + HeadSHA string `json:"head_sha,omitempty"` + HTMLURL string `json:"html_url,omitempty"` + RunAttempt int64 `json:"run_attempt,omitempty"` + RunnerID int64 `json:"runner_id,omitempty"` + RunnerName string `json:"runner_name,omitempty"` + RunnerGroupID int64 `json:"runner_group_id,omitempty"` + RunnerGroupName string `json:"runner_group_name,omitempty"` + Labels []string `json:"labels,omitempty"` + Steps []MinimalWorkflowJobStep `json:"steps,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + StartedAt string `json:"started_at,omitempty"` + CompletedAt string `json:"completed_at,omitempty"` +} + +// MinimalWorkflowJobsResult is the trimmed output type for workflow job list results. +type MinimalWorkflowJobsResult struct { + TotalCount int `json:"total_count"` + Jobs []MinimalWorkflowJob `json:"jobs"` +} + // MinimalResponse represents a minimal response for all CRUD operations. // Success is implicit in the HTTP response status, and all other information // can be derived from the URL or fetched separately if needed. @@ -1832,6 +1914,144 @@ func convertToMinimalTag(tag *github.RepositoryTag) MinimalTag { return m } +func convertToMinimalWorkflowRun(workflowRun *github.WorkflowRun) MinimalWorkflowRun { + minimalRun := MinimalWorkflowRun{ + ID: workflowRun.GetID(), + Name: workflowRun.GetName(), + DisplayTitle: workflowRun.GetDisplayTitle(), + WorkflowID: workflowRun.GetWorkflowID(), + RunNumber: workflowRun.GetRunNumber(), + RunAttempt: workflowRun.GetRunAttempt(), + Event: workflowRun.GetEvent(), + Status: workflowRun.GetStatus(), + Conclusion: workflowRun.GetConclusion(), + HeadBranch: workflowRun.GetHeadBranch(), + HeadSHA: workflowRun.GetHeadSHA(), + Path: workflowRun.GetPath(), + HTMLURL: workflowRun.GetHTMLURL(), + Actor: convertToMinimalUser(workflowRun.GetActor()), + TriggeringActor: convertToMinimalUser(workflowRun.GetTriggeringActor()), + CreatedAt: formatMinimalTimestamp(workflowRun.CreatedAt), + UpdatedAt: formatMinimalTimestamp(workflowRun.UpdatedAt), + RunStartedAt: formatMinimalTimestamp(workflowRun.RunStartedAt), + } + + for _, pullRequest := range workflowRun.GetPullRequests() { + if pullRequest != nil && pullRequest.GetNumber() != 0 { + minimalRun.PullRequests = append(minimalRun.PullRequests, pullRequest.GetNumber()) + } + } + + if headCommit := workflowRun.GetHeadCommit(); headCommit != nil && headCommit.GetMessage() != "" { + minimalRun.HeadCommit = &MinimalWorkflowRunHeadCommit{ + Message: headCommit.GetMessage(), + } + } + + if len(workflowRun.GetReferencedWorkflows()) > 0 { + minimalRun.ReferencedWorkflows = make([]MinimalReferencedWorkflow, 0, len(workflowRun.ReferencedWorkflows)) + for _, workflow := range workflowRun.GetReferencedWorkflows() { + if workflow != nil { + minimalRun.ReferencedWorkflows = append(minimalRun.ReferencedWorkflows, MinimalReferencedWorkflow{ + Path: workflow.GetPath(), + SHA: workflow.GetSHA(), + Ref: workflow.GetRef(), + }) + } + } + } + + return minimalRun +} + +func convertToMinimalWorkflowRuns(workflowRuns *github.WorkflowRuns) MinimalWorkflowRunsResult { + result := MinimalWorkflowRunsResult{ + WorkflowRuns: make([]MinimalWorkflowRun, 0), + } + if workflowRuns == nil { + return result + } + + result.TotalCount = workflowRuns.GetTotalCount() + result.WorkflowRuns = make([]MinimalWorkflowRun, 0, len(workflowRuns.WorkflowRuns)) + for _, workflowRun := range workflowRuns.WorkflowRuns { + if workflowRun != nil { + result.WorkflowRuns = append(result.WorkflowRuns, convertToMinimalWorkflowRun(workflowRun)) + } + } + return result +} + +func convertToMinimalWorkflowJobStep(step *github.TaskStep) MinimalWorkflowJobStep { + return MinimalWorkflowJobStep{ + Name: step.GetName(), + Status: step.GetStatus(), + Conclusion: step.GetConclusion(), + Number: step.GetNumber(), + StartedAt: formatMinimalTimestamp(step.StartedAt), + CompletedAt: formatMinimalTimestamp(step.CompletedAt), + } +} + +func convertToMinimalWorkflowJob(job *github.WorkflowJob) MinimalWorkflowJob { + minimalJob := MinimalWorkflowJob{ + ID: job.GetID(), + RunID: job.GetRunID(), + Name: job.GetName(), + WorkflowName: job.GetWorkflowName(), + Status: job.GetStatus(), + Conclusion: job.GetConclusion(), + HeadBranch: job.GetHeadBranch(), + HeadSHA: job.GetHeadSHA(), + HTMLURL: job.GetHTMLURL(), + RunAttempt: job.GetRunAttempt(), + RunnerID: job.GetRunnerID(), + RunnerName: job.GetRunnerName(), + RunnerGroupID: job.GetRunnerGroupID(), + RunnerGroupName: job.GetRunnerGroupName(), + Labels: append([]string(nil), job.GetLabels()...), + CreatedAt: formatMinimalTimestamp(job.CreatedAt), + StartedAt: formatMinimalTimestamp(job.StartedAt), + CompletedAt: formatMinimalTimestamp(job.CompletedAt), + } + + if len(job.GetSteps()) > 0 { + minimalJob.Steps = make([]MinimalWorkflowJobStep, 0, len(job.Steps)) + for _, step := range job.GetSteps() { + if step != nil { + minimalJob.Steps = append(minimalJob.Steps, convertToMinimalWorkflowJobStep(step)) + } + } + } + + return minimalJob +} + +func convertToMinimalWorkflowJobs(workflowJobs *github.Jobs) MinimalWorkflowJobsResult { + result := MinimalWorkflowJobsResult{ + Jobs: make([]MinimalWorkflowJob, 0), + } + if workflowJobs == nil { + return result + } + + result.TotalCount = workflowJobs.GetTotalCount() + result.Jobs = make([]MinimalWorkflowJob, 0, len(workflowJobs.Jobs)) + for _, job := range workflowJobs.Jobs { + if job != nil { + result.Jobs = append(result.Jobs, convertToMinimalWorkflowJob(job)) + } + } + return result +} + +func formatMinimalTimestamp(timestamp *github.Timestamp) string { + if timestamp == nil || timestamp.IsZero() { + return "" + } + return timestamp.Format(time.RFC3339) +} + // MinimalCheckRun is the trimmed output type for check run objects. type MinimalCheckRun struct { ID int64 `json:"id"`