Skip to content

Commit a6067a7

Browse files
authored
chore: add app triage project github actions (#23613)
1 parent b44cb58 commit a6067a7

4 files changed

+244
-2
lines changed

.github/workflows/add_to_triage_project.yml .github/workflows/triage_add_to_project.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
name: Add issue/PR to project
1+
name: 'Triage: add issue/PR to project'
22

33
on:
44
issues:
55
types:
66
- opened
7-
pull_request:
7+
pull_request_target:
88
types:
99
- opened
1010

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: 'Triage: route to team project board'
2+
on:
3+
issues:
4+
types:
5+
- labeled
6+
jobs:
7+
add-to-e2e:
8+
if: github.event.label.name == 'routed-to-e2e'
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Get project data
12+
env:
13+
GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_TOKEN }}
14+
ORGANIZATION: 'cypress-io'
15+
PROJECT_NUMBER: 10
16+
run: |
17+
gh api graphql -f query='
18+
query($org: String!, $number: Int!) {
19+
organization(login: $org){
20+
projectV2(number: $number) {
21+
id
22+
}
23+
}
24+
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
25+
26+
echo 'PROJECT_ID='$(jq -r '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
27+
- name: add issue to e2e project
28+
env:
29+
GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_TOKEN }}
30+
ISSUE_ID: ${{ github.event.issue.node_id }}
31+
run: |
32+
gh api graphql -f query='
33+
mutation($project:ID!, $issue:ID!) {
34+
addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) {
35+
item {
36+
id
37+
}
38+
}
39+
}' -f project=$PROJECT_ID -f issue=$ISSUE_ID
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
name: 'Triage: closed issue comment'
2+
on:
3+
issue_comment:
4+
types:
5+
- created
6+
jobs:
7+
add-to-e2e:
8+
if: ${{ !github.event.issue.pull_request && github.event.issue.state == 'closed' && github.event.sender.login != 'cypress-bot' }}
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Get project data
12+
env:
13+
GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_TOKEN }}
14+
ORGANIZATION: 'cypress-io'
15+
REPOSITORY: 'cypress'
16+
PROJECT_NUMBER: 9
17+
ISSUE_NUMBER: ${{ github.event.issue.number }}
18+
run: |
19+
gh api graphql -f query='
20+
query($org: String!, $repo: String!, $project: Int!, $issue: Int!) {
21+
organization(login: $org) {
22+
repository(name: $repo) {
23+
issue(number: $issue) {
24+
projectItems(first: 10, includeArchived: false) {
25+
nodes {
26+
id
27+
fieldValueByName(name: "Status") {
28+
... on ProjectV2ItemFieldSingleSelectValue {
29+
name
30+
field {
31+
... on ProjectV2SingleSelectField {
32+
project {
33+
... on ProjectV2 {
34+
id
35+
number
36+
}
37+
}
38+
}
39+
}
40+
}
41+
}
42+
}
43+
}
44+
}
45+
}
46+
projectV2(number: $project) {
47+
field(name: "Status") {
48+
... on ProjectV2SingleSelectField {
49+
id
50+
options {
51+
id
52+
name
53+
}
54+
}
55+
}
56+
}
57+
}
58+
}' -f org=$ORGANIZATION -f repo=$REPOSITORY -F issue=$ISSUE_NUMBER -F project=$PROJECT_NUMBER > project_data.json
59+
60+
echo 'PROJECT_ID='$(jq -r '.data.organization.repository.issue.projectItems.nodes[].fieldValueByName.field.project | select(.number == ${{ env.PROJECT_NUMBER }}) | .id' project_data.json) >> $GITHUB_ENV
61+
echo 'PROJECT_ITEM_ID='$(jq -r '.data.organization.repository.issue.projectItems.nodes[] | select(.fieldValueByName.field.project.number == ${{ env.PROJECT_NUMBER }}) | .id' project_data.json) >> $GITHUB_ENV
62+
echo 'STATUS_FIELD_ID='$(jq -r '.data.organization.projectV2.field | .id' project_data.json) >> $GITHUB_ENV
63+
echo 'STATUS='$(jq -r '.data.organization.repository.issue.projectItems.nodes[].fieldValueByName | select(.field.project.number == ${{ env.PROJECT_NUMBER }}) | .name' project_data.json) >> $GITHUB_ENV
64+
echo 'NEW_ISSUE_OPTION_ID='$(jq -r '.data.organization.projectV2.field.options[] | select(.name== "New Issue") | .id' project_data.json) >> $GITHUB_ENV
65+
- name: Move issue to New Issue status
66+
env:
67+
GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_TOKEN }}
68+
if: ${{ env.STATUS == 'Closed' }}
69+
run: |
70+
gh api graphql -f query='
71+
mutation (
72+
$project: ID!
73+
$item: ID!
74+
$status_field: ID!
75+
$status_value: String!
76+
) {
77+
updateProjectV2ItemFieldValue(input: {
78+
projectId: $project
79+
itemId: $item
80+
fieldId: $status_field
81+
value: {
82+
singleSelectOptionId: $status_value
83+
}
84+
}) {
85+
projectV2Item {
86+
id
87+
}
88+
}
89+
}' -f project=$PROJECT_ID -f item=$PROJECT_ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=$NEW_ISSUE_OPTION_ID
+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
name: 'Triage: issue metrics'
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
startDate:
7+
description: 'Start date (YYYY-MM-DD)'
8+
type: date
9+
endDate:
10+
description: 'End date (YYYY-MM-DD)'
11+
type: date
12+
jobs:
13+
seven-day-close:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/github-script@v6
17+
env:
18+
ORGANIZATION: 'cypress-io'
19+
REPOSITORY: 'cypress'
20+
PROJECT_NUMBER: 9
21+
with:
22+
github-token: ${{ secrets.ADD_TO_PROJECT_TOKEN }}
23+
script: |
24+
const ROUTED_TO_LABELS = ['routed-to-e2e', 'routed-to-ct']
25+
const MS_PER_DAY = 1000 * 60 * 60 * 24
26+
const { REPOSITORY, ORGANIZATION, PROJECT_NUMBER } = process.env
27+
28+
const issues = []
29+
30+
const determineDateRange = () => {
31+
const inputStartDate = '${{ inputs.startDate }}'
32+
const inputEndDate = '${{ inputs.endDate }}'
33+
34+
if (inputStartDate && inputEndDate) {
35+
return { startDate: inputStartDate, endDate: inputEndDate }
36+
}
37+
38+
if (inputStartDate || inputEndDate) {
39+
core.setFailed('Both startDate and endDate are required if one is provided.')
40+
}
41+
42+
const startDate = new Date()
43+
44+
startDate.setDate(startDate.getDate() - 6)
45+
46+
return { startDate: startDate.toISOString().split('T')[0], endDate: (new Date()).toISOString().split('T')[0] }
47+
}
48+
49+
const dateRange = determineDateRange()
50+
const query = `is:issue+repo:${ORGANIZATION}/${REPOSITORY}+project:${ORGANIZATION}/${PROJECT_NUMBER}+created:${dateRange.startDate}..${dateRange.endDate}`
51+
52+
const findLabelDateTime = async (issueNumber) => {
53+
const iterator = github.paginate.iterator(github.rest.issues.listEventsForTimeline, {
54+
owner: ORGANIZATION,
55+
repo: REPOSITORY,
56+
issue_number: issueNumber,
57+
})
58+
59+
for await (const { data: timelineData } of iterator) {
60+
for (const timelineItem of timelineData) {
61+
if (timelineItem.event === 'labeled' && ROUTED_TO_LABELS.includes(timelineItem.label.name)) {
62+
return timelineItem.created_at
63+
}
64+
}
65+
}
66+
}
67+
68+
const calculateElapsedDays = (createdAt, routedOrClosedAt) => {
69+
return Math.round((new Date(routedOrClosedAt) - new Date(createdAt)) / MS_PER_DAY, 0)
70+
}
71+
72+
const iterator = github.paginate.iterator(github.rest.search.issuesAndPullRequests, {
73+
q: query,
74+
per_page: 100,
75+
})
76+
77+
for await (const { data } of iterator) {
78+
for (const issue of data) {
79+
let routedOrClosedAt
80+
81+
if (!issue.pull_request) {
82+
const routedLabel = issue.labels.find((label) => ROUTED_TO_LABELS.includes(label.name))
83+
84+
if (routedLabel) {
85+
routedOrClosedAt = await findLabelDateTime(issue.number)
86+
} else if (issue.state === 'closed') {
87+
routedOrClosedAt = issue.closed_at
88+
}
89+
90+
let elapsedDays
91+
92+
if (routedOrClosedAt) {
93+
elapsedDays = calculateElapsedDays(issue.created_at, routedOrClosedAt)
94+
}
95+
96+
issues.push({
97+
number: issue.number,
98+
title: issue.title,
99+
state: issue.state,
100+
url: issue.html_url,
101+
createdAt: issue.created_at,
102+
routedOrClosedAt,
103+
elapsedDays,
104+
})
105+
}
106+
}
107+
}
108+
109+
const issuesRoutedOrClosedIn7Days = issues.filter((issue) => issue.elapsedDays <= 7).length
110+
const percentage = Number(issues.length > 0 ? issuesRoutedOrClosedIn7Days / issues.length : 0).toLocaleString(undefined, { style: 'percent', minimumFractionDigits: 2 })
111+
112+
console.log(`Triage Metrics (${dateRange.startDate} - ${dateRange.endDate})`)
113+
console.log('Total issues:', issues.length)
114+
console.log(`Issues routed/closed within 7 days: ${issuesRoutedOrClosedIn7Days} (${percentage})`)

0 commit comments

Comments
 (0)