Skip to content

[task] Consolidate Build Create Output Job Functions #3650

@github-actions

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions