Skip to content

Commit 1212fe8

Browse files
tloubrieu-jplclaude
andcommitted
Deploy workflow: org-team-assignment.yml
This workflow is being deployed across the organization. Co-Authored-By: Claude (claude-sonnet-4.5) <noreply@anthropic.com>
1 parent d84d0a6 commit 1212fe8

1 file changed

Lines changed: 269 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)