Skip to content

Commit b7fbae1

Browse files
tloubrieu-jplclaude
andcommitted
Add automatic team/project assignment workflow
This workflow automatically, when team label is assigned to an issue: - Set podaac project status to triage - Assign to the team github project when it exists, with a New status. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2dadd11 commit b7fbae1

1 file changed

Lines changed: 255 additions & 0 deletions

File tree

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
name: Team Assignment
2+
3+
on:
4+
issues:
5+
types: [labeled]
6+
7+
jobs:
8+
assign-to-team:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Process team label
12+
uses: actions/github-script@v7
13+
with:
14+
github-token: ${{ secrets.PROJECTS_PAT }}
15+
script: |
16+
const label = context.payload.label.name;
17+
const issueNumber = context.payload.issue.number;
18+
19+
// Check if label matches team:<team_name> pattern
20+
const teamMatch = label.match(/^team:(.+)$/);
21+
if (!teamMatch) {
22+
console.log(`Label "${label}" does not match team:<team_name> pattern, skipping`);
23+
return;
24+
}
25+
26+
const teamName = teamMatch[1];
27+
console.log(`Processing team assignment for team: ${teamName}`);
28+
29+
const podaacProjectNumber = 75;
30+
const org = 'podaac';
31+
const repo = context.repo.repo;
32+
33+
// Define reusable GraphQL mutation for updating project status
34+
const updateStatusMutation = `
35+
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: String!) {
36+
updateProjectV2ItemFieldValue(
37+
input: {
38+
projectId: $projectId
39+
itemId: $itemId
40+
fieldId: $fieldId
41+
value: { singleSelectOptionId: $value }
42+
}
43+
) {
44+
projectV2Item {
45+
id
46+
}
47+
}
48+
}
49+
`;
50+
51+
// ========================================
52+
// Step 1: Update status to "triaged" in podaac project
53+
// ========================================
54+
55+
console.log('Step 1: Updating podaac project status to "triaged"...');
56+
57+
// Get the podaac project information
58+
const podaacProjectQuery = `
59+
query($org: String!, $number: Int!) {
60+
organization(login: $org) {
61+
projectV2(number: $number) {
62+
id
63+
fields(first: 20) {
64+
nodes {
65+
... on ProjectV2SingleSelectField {
66+
id
67+
name
68+
options {
69+
id
70+
name
71+
}
72+
}
73+
}
74+
}
75+
}
76+
}
77+
}
78+
`;
79+
80+
const podaacProjectData = await github.graphql(podaacProjectQuery, {
81+
org: org,
82+
number: podaacProjectNumber
83+
});
84+
85+
const podaacProject = podaacProjectData.organization.projectV2;
86+
const podaacStatusField = podaacProject.fields.nodes.find(field => field.name === 'Status');
87+
const triagedOption = podaacStatusField?.options.find(option => option.name === 'triaged');
88+
89+
if (!triagedOption) {
90+
core.warning('Could not find "triaged" status option in podaac project');
91+
} else {
92+
// Get the issue's project item in podaac project
93+
const issueProjectItemQuery = `
94+
query($org: String!, $repo: String!, $number: Int!) {
95+
repository(owner: $org, name: $repo) {
96+
issue(number: $number) {
97+
projectItems(first: 10) {
98+
nodes {
99+
id
100+
project {
101+
id
102+
number
103+
}
104+
}
105+
}
106+
}
107+
}
108+
}
109+
`;
110+
111+
const issueData = await github.graphql(issueProjectItemQuery, {
112+
org: org,
113+
repo: repo,
114+
number: issueNumber
115+
});
116+
117+
const projectItems = issueData.repository.issue.projectItems.nodes;
118+
const podaacProjectItem = projectItems.find(item => item.project.id === podaacProject.id);
119+
120+
if (!podaacProjectItem) {
121+
core.warning('Issue is not in podaac project, cannot update status');
122+
} else {
123+
// Update the status to "triaged"
124+
await github.graphql(updateStatusMutation, {
125+
projectId: podaacProject.id,
126+
itemId: podaacProjectItem.id,
127+
fieldId: podaacStatusField.id,
128+
value: triagedOption.id
129+
});
130+
131+
console.log('✅ Successfully updated status to "triaged" in podaac project');
132+
}
133+
}
134+
135+
// ========================================
136+
// Step 2: Add to team project if it exists
137+
// ========================================
138+
139+
console.log(`Step 2: Looking for team project named "${teamName}"...`);
140+
141+
// Search for team project by name
142+
const teamProjectSearchQuery = `
143+
query($org: String!, $searchQuery: String!) {
144+
organization(login: $org) {
145+
projectsV2(first: 20, query: $searchQuery) {
146+
nodes {
147+
id
148+
number
149+
title
150+
fields(first: 20) {
151+
nodes {
152+
... on ProjectV2SingleSelectField {
153+
id
154+
name
155+
options {
156+
id
157+
name
158+
}
159+
}
160+
}
161+
}
162+
}
163+
}
164+
}
165+
}
166+
`;
167+
168+
const teamProjectSearchData = await github.graphql(teamProjectSearchQuery, {
169+
org: org,
170+
searchQuery: teamName
171+
});
172+
173+
const teamProjects = teamProjectSearchData.organization.projectsV2.nodes;
174+
const teamProject = teamProjects.find(p => p.title.toLowerCase() === teamName.toLowerCase());
175+
176+
if (!teamProject) {
177+
console.log(`⚠️ No project found with name "${teamName}", skipping team project assignment`);
178+
core.notice(`No project found for team "${teamName}". If you want the issue added to a team project, create a project named "${teamName}".`);
179+
return;
180+
}
181+
182+
console.log(`Found team project: ${teamProject.title} (number: ${teamProject.number})`);
183+
184+
// Add issue to team project
185+
const addToProjectMutation = `
186+
mutation($projectId: ID!, $contentId: ID!) {
187+
addProjectV2ItemById(input: {
188+
projectId: $projectId
189+
contentId: $contentId
190+
}) {
191+
item {
192+
id
193+
}
194+
}
195+
}
196+
`;
197+
198+
const issueNodeIdQuery = `
199+
query($org: String!, $repo: String!, $number: Int!) {
200+
repository(owner: $org, name: $repo) {
201+
issue(number: $number) {
202+
id
203+
}
204+
}
205+
}
206+
`;
207+
208+
const issueNodeData = await github.graphql(issueNodeIdQuery, {
209+
org: org,
210+
repo: repo,
211+
number: issueNumber
212+
});
213+
214+
const issueNodeId = issueNodeData.repository.issue.id;
215+
216+
const addResult = await github.graphql(addToProjectMutation, {
217+
projectId: teamProject.id,
218+
contentId: issueNodeId
219+
});
220+
221+
const teamProjectItemId = addResult.addProjectV2ItemById.item.id;
222+
console.log(`✅ Successfully added issue to team project "${teamName}"`);
223+
224+
// ========================================
225+
// Step 3: Set status to "New" in team project
226+
// ========================================
227+
228+
console.log('Step 3: Setting status to "New" in team project...');
229+
230+
const teamStatusField = teamProject.fields.nodes.find(field => field.name === 'Status');
231+
const newStatusOption = teamStatusField?.options.find(option => option.name === '🆕 New');
232+
233+
if (!teamStatusField || !newStatusOption) {
234+
core.warning(`Could not find "Status" field or "New" option in team project "${teamName}"`);
235+
console.log(`⚠️ Team project "${teamName}" does not have a Status field with "🆕 New" option`);
236+
} else {
237+
await github.graphql(updateStatusMutation, {
238+
projectId: teamProject.id,
239+
itemId: teamProjectItemId,
240+
fieldId: teamStatusField.id,
241+
value: newStatusOption.id
242+
});
243+
244+
console.log('✅ Successfully set status to "🆕 New" in team project');
245+
}
246+
247+
// Summary
248+
core.summary
249+
.addHeading(`Team Assignment Complete: ${teamName}`)
250+
.addList([
251+
`Updated podaac project status to "triaged"`,
252+
`Added issue to team project "${teamName}"`,
253+
`Set status to "🆕 New" in team project`
254+
])
255+
.write();

0 commit comments

Comments
 (0)