Skip to content

[task] Consolidate Build Create Output Job Functions #3650

Description

@github-actions

Objective

Eliminate ~600-800 lines of duplicate job building code by creating a generic job builder framework for safe output operations.

Context

The analysis in #3525 identified 10+ nearly identical buildCreateOutput*Job functions with ~85% similar structure. Each safe output type reimplements the same job building pattern with minor variations.

Duplicate Functions (10+ implementations):

  • buildCreateOutputIssueJob (create_issue.go)
  • buildCreateOutputDiscussionJob (create_discussion.go)
  • buildCreateOutputPullRequestJob (create_pull_request.go)
  • buildCreateOutputAgentTaskJob (create_agent_task.go)
  • buildCreateOutputCodeScanningAlertJob (create_code_scanning_alert.go)
  • buildCreateOutputAddCommentJob (add_comment.go)
  • buildCreateOutputReviewCommentJob (create_pull_request_review_comment.go)
  • buildCreateOutputUpdateIssueJob (update_issue.go)
  • buildCreateOutputMissingToolJob (missing_tool.go)
  • buildCreateOutputUploadAssetJob (publish_assets.go)

Common Pattern (~85% identical):

func buildCreateOutput*Job(c *Compiler, config *Config) map[string]any {
    // 1. Create job structure with name, runs-on
    // 2. Add needs dependency
    // 3. Add permissions
    // 4. Add checkout step
    // 5. Add processing steps (varies by type)
    // 6. Add outputs
    // 7. Return job
}

Approach

Step 1: Create Job Builder Package

Create pkg/workflow/jobs/ subpackage with:

  • builder.go - Generic job builder implementation
  • types.go - Job builder interfaces and types
  • safe_outputs.go - Safe output specific builders
  • builder_test.go - Comprehensive tests

Step 2: Define Builder Interface

Create interface for output-type-specific behavior:

// pkg/workflow/jobs/types.go
package jobs

type SafeOutputJobBuilder interface {
    // GetJobName returns the job name (e.g., "create_issue")
    GetJobName() string
    
    // GetPermissions returns required permissions for this job
    GetPermissions() map[string]string
    
    // GetProcessingSteps returns output-specific processing steps
    GetProcessingSteps(config map[string]any) []map[string]any
    
    // GetOutputs returns job outputs
    GetOutputs() map[string]string
    
    // NeedsCheckout returns true if job needs repository checkout
    NeedsCheckout() bool
}

Step 3: Implement Generic Builder

// pkg/workflow/jobs/builder.go
package jobs

func BuildSafeOutputJob(
    builder SafeOutputJobBuilder,
    config map[string]any,
    runsOn any,
    needsDependency string,
) map[string]any {
    job := map[string]any{
        "runs-on": runsOn,
        "needs":   needsDependency,
        "permissions": builder.GetPermissions(),
        "steps": []map[string]any{},
    }
    
    // Add checkout step if needed
    if builder.NeedsCheckout() {
        job["steps"] = append(job["steps"].([]map[string]any), 
            generateCheckoutStep())
    }
    
    // Add processing steps
    processingSteps := builder.GetProcessingSteps(config)
    job["steps"] = append(job["steps"].([]map[string]any), 
        processingSteps...)
    
    // Add outputs if present
    if outputs := builder.GetOutputs(); len(outputs) > 0 {
        job["outputs"] = outputs
    }
    
    return job
}

Step 4: Create Output-Specific Builders

Implement SafeOutputJobBuilder for each output type:

// pkg/workflow/jobs/safe_outputs.go
package jobs

type IssueJobBuilder struct {
    Config *IssueConfig
}

func (b *IssueJobBuilder) GetJobName() string {
    return "create_issue"
}

func (b *IssueJobBuilder) GetPermissions() map[string]string {
    return map[string]string{
        "issues":   "write",
        "contents": "read",
    }
}

func (b *IssueJobBuilder) GetProcessingSteps(config map[string]any) []map[string]any {
    // Issue-specific steps
    return []map[string]any{
        generateProcessIssueStep(b.Config),
    }
}

func (b *IssueJobBuilder) GetOutputs() map[string]string {
    return map[string]string{
        "issue_number": "${{ steps.process.outputs.issue_number }}",
        "issue_url":    "${{ steps.process.outputs.issue_url }}",
    }
}

func (b *IssueJobBuilder) NeedsCheckout() bool {
    return false
}

// Similar implementations for Discussion, PullRequest, AgentTask, etc.

Step 5: Refactor Existing Functions

Update each buildCreateOutput*Job function to use the generic builder:

// Before (in create_issue.go):
func (c *Compiler) buildCreateOutputIssueJob(config *IssueConfig) map[string]any {
    // 60+ lines of job building logic
}

// After:
import "github.com/githubnext/gh-aw/pkg/workflow/jobs"

func (c *Compiler) buildCreateOutputIssueJob(config *IssueConfig) map[string]any {
    builder := &jobs.IssueJobBuilder{Config: config}
    return jobs.BuildSafeOutputJob(
        builder,
        c.convertConfigToMap(config),
        c.runsOn,
        "activation",
    )
}

Step 6: Add Comprehensive Tests

Create tests for:

  • Generic job builder with different output types
  • Each SafeOutputJobBuilder implementation
  • Edge cases (no checkout, no outputs, minimal permissions)
  • Job structure validation

Files to Create

  • pkg/workflow/jobs/builder.go
  • pkg/workflow/jobs/types.go
  • pkg/workflow/jobs/safe_outputs.go
  • pkg/workflow/jobs/builder_test.go

Files to Modify

  • pkg/workflow/create_issue.go
  • pkg/workflow/create_discussion.go
  • pkg/workflow/create_pull_request.go
  • pkg/workflow/create_agent_task.go
  • pkg/workflow/create_code_scanning_alert.go
  • pkg/workflow/add_comment.go
  • pkg/workflow/create_pull_request_review_comment.go
  • pkg/workflow/update_issue.go
  • pkg/workflow/missing_tool.go
  • pkg/workflow/publish_assets.go

Acceptance Criteria

  • New pkg/workflow/jobs/ package created
  • SafeOutputJobBuilder interface defined
  • Generic BuildSafeOutputJob function implemented
  • All 10+ output types have builder implementations
  • All buildCreateOutput*Job functions refactored to use generic builder
  • All existing tests pass without modification
  • New tests added for jobs package with >80% coverage
  • make test-unit passes
  • make lint passes
  • Estimated 600-800 lines of code removed

Benefits

  • Single job building framework
  • Consistent behavior across all output types
  • Easy to add new output types (just implement interface)
  • Reduced maintenance burden

Estimated Impact

AI generated by Plan Command for #3525

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions