|
| 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