Skip to content

[Compliance] Stage tracking example PWS #31

[Compliance] Stage tracking example PWS

[Compliance] Stage tracking example PWS #31

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}`);
}