|
1 | 1 | name: Add Label on Project Status Change |
2 | 2 |
|
3 | | -permissions: |
4 | | - issues: write |
5 | | - projects: read |
6 | | - |
7 | 3 | on: |
8 | 4 | workflow_dispatch: |
9 | | - projects_v2_item: |
10 | | - types: [edited] |
| 5 | + inputs: |
| 6 | + project_ids: |
| 7 | + description: 'Project IDs (comma-separated, e.g., PVT_xxx,PVT_yyy)' |
| 8 | + required: true |
| 9 | + default: 'PVT_kwDOAjmOms4BMOXr' |
| 10 | + status_value: |
| 11 | + description: 'Status value to match' |
| 12 | + required: false |
| 13 | + default: 'Need To Verify' |
| 14 | + label_to_add: |
| 15 | + description: 'Label to add' |
| 16 | + required: false |
| 17 | + default: 'kind/need-to-verify' |
| 18 | + schedule: |
| 19 | + - cron: '0 * * * *' # Every hour |
11 | 20 |
|
12 | 21 | jobs: |
13 | 22 | add-label: |
14 | 23 | runs-on: ubuntu-latest |
15 | 24 | steps: |
16 | | - - name: Check project item status and add label |
| 25 | + - name: Check project items with specific status and add label |
17 | 26 | uses: actions/github-script@v7 |
| 27 | + env: |
| 28 | + PROJECT_IDS: ${{ github.event.inputs.project_ids || 'PVT_kwDOAjmOms4BMOXr' }} |
| 29 | + STATUS_VALUE: ${{ github.event.inputs.status_value || 'Need To Verify' }} |
| 30 | + LABEL_TO_ADD: ${{ github.event.inputs.label_to_add || 'kind/need-to-verify' }} |
18 | 31 | with: |
19 | 32 | script: | |
20 | | - try { |
21 | | - const { data: item } = await github.graphql(` |
| 33 | + const projectIds = process.env.PROJECT_IDS.split(',').map(p => p.trim()); |
| 34 | + const statusValue = process.env.STATUS_VALUE; |
| 35 | + const labelToAdd = process.env.LABEL_TO_ADD; |
| 36 | + |
| 37 | + console.log(`Projects: ${projectIds.join(', ')}`); |
| 38 | + console.log(`Status: ${statusValue}, Label: ${labelToAdd}`); |
| 39 | + |
| 40 | + for (const projectId of projectIds) { |
| 41 | + console.log(`\n=== Processing project: ${projectId} ===`); |
| 42 | + |
| 43 | + // Get the Status field ID |
| 44 | + const fieldQuery = ` |
22 | 45 | query($id: ID!) { |
23 | 46 | node(id: $id) { |
24 | | - ... on ProjectV2Item { |
25 | | - content { |
26 | | - ... on Issue { |
27 | | - number |
28 | | - repository { |
| 47 | + ... on ProjectV2 { |
| 48 | + fields(first: 20) { |
| 49 | + nodes { |
| 50 | + ... on ProjectV2Field { |
| 51 | + id |
29 | 52 | name |
30 | | - owner { login } |
| 53 | + } |
| 54 | + ... on ProjectV2SingleSelectField { |
| 55 | + id |
| 56 | + name |
| 57 | + options { |
| 58 | + id |
| 59 | + name |
| 60 | + } |
31 | 61 | } |
32 | 62 | } |
33 | 63 | } |
34 | | - fieldValues(first: 10) { |
| 64 | + } |
| 65 | + } |
| 66 | + } |
| 67 | + `; |
| 68 | + |
| 69 | + const { data: project } = await github.graphql(fieldQuery, { id: projectId }); |
| 70 | + if (!project || !project.fields) { |
| 71 | + console.log(`Could not fetch project ${projectId}, skipping`); |
| 72 | + continue; |
| 73 | + } |
| 74 | + |
| 75 | + const statusField = project.fields.nodes.find(f => f.name === "Status"); |
| 76 | + |
| 77 | + if (!statusField) { |
| 78 | + console.log("Status field not found in project"); |
| 79 | + continue; |
| 80 | + } |
| 81 | + |
| 82 | + // Find the option ID for the target status |
| 83 | + const statusOption = statusField.options?.find(o => o.name === statusValue); |
| 84 | + if (!statusOption) { |
| 85 | + console.log(`Status option "${statusValue}" not found`); |
| 86 | + continue; |
| 87 | + } |
| 88 | + |
| 89 | + console.log(`Status field ID: ${statusField.id}`); |
| 90 | + |
| 91 | + // Query items in the project |
| 92 | + const itemsQuery = ` |
| 93 | + query($id: ID!) { |
| 94 | + node(id: $id) { |
| 95 | + ... on ProjectV2 { |
| 96 | + items(first: 50) { |
35 | 97 | nodes { |
36 | | - ... on ProjectV2ItemFieldValue { |
37 | | - field { name } |
38 | | - value |
| 98 | + id |
| 99 | + content { |
| 100 | + ... on Issue { |
| 101 | + number |
| 102 | + title |
| 103 | + state |
| 104 | + repository { |
| 105 | + name |
| 106 | + owner { login } |
| 107 | + } |
| 108 | + labels { |
| 109 | + nodes { |
| 110 | + name |
| 111 | + } |
| 112 | + } |
| 113 | + } |
| 114 | + } |
| 115 | + fieldValues(first: 20) { |
| 116 | + nodes { |
| 117 | + ... on ProjectV2ItemFieldSingleSelectValue { |
| 118 | + field { id } |
| 119 | + name |
| 120 | + } |
| 121 | + } |
39 | 122 | } |
40 | 123 | } |
41 | 124 | } |
42 | 125 | } |
43 | 126 | } |
44 | 127 | } |
45 | | - `, { id: context.payload.projects_v2_item.node_id }); |
46 | | -
|
47 | | - const statusField = item.fieldValues.nodes.find(f => f.field.name === "Status"); |
48 | | - console.log("current status:", statusField?.value); |
49 | | -
|
50 | | - if ( |
51 | | - statusField?.value === "Need To Verify" && |
52 | | - item.content?.number |
53 | | - ) { |
54 | | - const issue = item.content; |
55 | | - await github.rest.issues.addLabels({ |
56 | | - owner: issue.repository.owner.login, |
57 | | - repo: issue.repository.name, |
58 | | - issue_number: issue.number, |
59 | | - labels: ["kind/need-to-verify"] |
60 | | - }); |
61 | | - console.log("✅ Label added:kind/need-to-verify"); |
62 | | - } else { |
63 | | - console.log("ℹ️ Not a matching item, skip"); |
| 128 | + `; |
| 129 | + |
| 130 | + const { data: projectWithItems } = await github.graphql(itemsQuery, { id: projectId }); |
| 131 | + |
| 132 | + // Filter items with matching status and without the label |
| 133 | + const items = projectWithItems.items.nodes; |
| 134 | + console.log(`Found ${items.length} items in project`); |
| 135 | + |
| 136 | + let labelAdded = 0; |
| 137 | + for (const item of items) { |
| 138 | + const statusFieldValue = item.fieldValues.nodes.find( |
| 139 | + f => f.field?.id === statusField.id |
| 140 | + ); |
| 141 | + |
| 142 | + if (statusFieldValue?.name === statusValue && item.content && item.content.state === 'OPEN') { |
| 143 | + const issue = item.content; |
| 144 | + const hasLabel = issue.labels.nodes.some(l => l.name === labelToAdd); |
| 145 | + |
| 146 | + if (!hasLabel) { |
| 147 | + console.log(`Adding label to issue #${issue.number}: ${issue.title}`); |
| 148 | + try { |
| 149 | + await github.rest.issues.addLabels({ |
| 150 | + owner: issue.repository.owner.login, |
| 151 | + repo: issue.repository.name, |
| 152 | + issue_number: issue.number, |
| 153 | + labels: [labelToAdd] |
| 154 | + }); |
| 155 | + labelAdded++; |
| 156 | + } catch (e) { |
| 157 | + console.error(`Failed to add label to #${issue.number}: ${e.message}`); |
| 158 | + } |
| 159 | + } else { |
| 160 | + console.log(`Issue #${issue.number} already has label, skip`); |
| 161 | + } |
| 162 | + } |
64 | 163 | } |
65 | | - } catch (error) { |
66 | | - console.error("❌ Action failed:", error.message); |
67 | | - throw error; |
| 164 | + |
| 165 | + console.log(`Project ${projectId}: Added label to ${labelAdded} issues`); |
68 | 166 | } |
| 167 | + |
| 168 | + console.log('\n=== Done ==='); |
0 commit comments