[Compliance] Stage tracking example PWS #17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Sync Labels to Project Status | |
| on: | |
| issues: | |
| types: [labeled, unlabeled] | |
| env: | |
| PROJECT_NUMBER: 15 # MetroStar org project: Compliance Pipeline | |
| FIELD_NAME: Stage | |
| jobs: | |
| sync-status: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Sync label to project field | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.PROJECT_TOKEN }} | |
| script: | | |
| const label = context.payload.label?.name; | |
| const issueNodeId = context.payload.issue.node_id; | |
| // Map labels to Stage field values | |
| const labelToStage = { | |
| 'ready': 'Ready', | |
| 'parsing': 'Parsing', | |
| 'querying': 'Querying', | |
| 'complete': 'Complete', | |
| 'failed': 'Failed' | |
| }; | |
| // Map labels to Status field values | |
| const labelToProjectStatus = { | |
| 'ready': 'Todo', | |
| 'parsing': 'In Progress', | |
| 'querying': 'In Progress', | |
| 'complete': 'Done', | |
| 'failed': 'Todo' | |
| }; | |
| const newStage = labelToStage[label]; | |
| const newProjectStatus = labelToProjectStatus[label]; | |
| if (!newStage) { | |
| console.log(`Label "${label}" not in mapping, skipping`); | |
| return; | |
| } | |
| console.log(`Syncing label "${label}" -> Stage: "${newStage}", Status: "${newProjectStatus}"`); | |
| // Get the project (org-level project) with both Stage and Status fields | |
| const projectQuery = ` | |
| query($org: String!, $number: Int!) { | |
| organization(login: $org) { | |
| projectV2(number: $number) { | |
| id | |
| stageField: field(name: "Stage") { | |
| ... on ProjectV2SingleSelectField { | |
| id | |
| options { id name } | |
| } | |
| } | |
| statusField: field(name: "Status") { | |
| ... on ProjectV2SingleSelectField { | |
| id | |
| options { id name } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| `; | |
| const projectResult = await github.graphql(projectQuery, { | |
| org: context.repo.owner, | |
| number: parseInt(process.env.PROJECT_NUMBER) | |
| }); | |
| const project = projectResult.organization.projectV2; | |
| const stageField = project.stageField; | |
| const statusField = project.statusField; | |
| const stageOption = stageField.options.find(o => o.name === newStage); | |
| const statusOption = statusField?.options?.find(o => o.name === newProjectStatus); | |
| if (!stageOption) { | |
| console.log(`Stage option "${newStage}" not found in field`); | |
| return; | |
| } | |
| if (!statusOption) { | |
| console.log(`Status option "${newProjectStatus}" not found (Status field may not exist or option missing)`); | |
| } | |
| // Find the project item for this issue | |
| const itemQuery = ` | |
| query($projectId: ID!) { | |
| node(id: $projectId) { | |
| ... on ProjectV2 { | |
| items(first: 100) { | |
| nodes { | |
| id | |
| content { ... on Issue { id } } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| `; | |
| const itemResult = await github.graphql(itemQuery, { | |
| projectId: project.id | |
| }); | |
| const item = itemResult.node.items.nodes.find( | |
| i => i.content?.id === issueNodeId | |
| ); | |
| if (!item) { | |
| console.log('Issue not found in project, adding it first...'); | |
| // Add issue to project | |
| const addMutation = ` | |
| mutation($projectId: ID!, $contentId: ID!) { | |
| addProjectV2ItemById(input: {projectId: $projectId, contentId: $contentId}) { | |
| item { id } | |
| } | |
| } | |
| `; | |
| const addResult = await github.graphql(addMutation, { | |
| projectId: project.id, | |
| contentId: issueNodeId | |
| }); | |
| var itemId = addResult.addProjectV2ItemById.item.id; | |
| } else { | |
| var itemId = item.id; | |
| } | |
| // Update the Stage field value | |
| const updateMutation = ` | |
| mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { | |
| updateProjectV2ItemFieldValue(input: { | |
| projectId: $projectId | |
| itemId: $itemId | |
| fieldId: $fieldId | |
| value: { singleSelectOptionId: $optionId } | |
| }) { | |
| projectV2Item { id } | |
| } | |
| } | |
| `; | |
| await github.graphql(updateMutation, { | |
| projectId: project.id, | |
| itemId: itemId, | |
| fieldId: stageField.id, | |
| optionId: stageOption.id | |
| }); | |
| console.log(`✅ Updated issue Stage to: ${newStage}`); | |
| // Update the Status field if available | |
| if (statusField && statusOption) { | |
| await github.graphql(updateMutation, { | |
| projectId: project.id, | |
| itemId: itemId, | |
| fieldId: statusField.id, | |
| optionId: statusOption.id | |
| }); | |
| console.log(`✅ Updated issue Status to: ${newProjectStatus}`); | |
| } |