-
Notifications
You must be signed in to change notification settings - Fork 6
168 lines (139 loc) · 7.23 KB
/
dora-lead-time-for-changes.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
###################### DO NOT DELETE OR MODIFY THIS FILE #######################
#
# This workflow calculates the Lead Time for Changes DORA metric for PRs merged into your
# default branch.
#
###################### DO NOT DELETE OR MODIFY THIS FILE #######################
name: dora-lead-time-for-changes
run-name: "Calculate DORA Lead Time for Changes"
on:
create:
jobs:
calculate-lead-time-for-changes:
if: ${{ github.ref_type == 'tag' }}
runs-on: ubuntu-latest
steps:
- name: Calculate Lead Time
uses: actions/github-script@v7
env:
DATADOG_API_KEY_FOR_LEAD_TIME_METRIC: ${{ secrets.DATADOG_API_KEY_FOR_LEAD_TIME_METRIC }}
DATADOG_APP_KEY_FOR_LEAD_TIME_METRIC: ${{ secrets.DATADOG_APP_KEY_FOR_LEAD_TIME_METRIC }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
script: |
const { GITHUB_TOKEN, DATADOG_API_KEY_FOR_LEAD_TIME_METRIC, DATADOG_APP_KEY_FOR_LEAD_TIME_METRIC } = process.env
if(!DATADOG_API_KEY_FOR_LEAD_TIME_METRIC || !DATADOG_APP_KEY_FOR_LEAD_TIME_METRIC || !GITHUB_TOKEN) {
core.setFailed('DATADOG_API_KEY_FOR_LEAD_TIME_METRIC or DATADOG_APP_KEY_FOR_LEAD_TIME_METRIC or GITHUB_TOKEN is falsy. All must be set.')
process.exit(1)
}
const millisecondsPerSecond = 1000
const datadogGuageMetricType = 3
const datadogSubmitMetricsUrl = 'https://api.ddog-gov.com/api/v2/series'
const repoName = context?.payload?.repository?.name
if (!repoName) {
core.setFailed('Error: Github context object > context.payload.repository.name not defined. Exiting script.')
process.exit(1)
}
const ghBaseUrl = `https://api.github.com/repos/department-of-veterans-affairs/${repoName}`
async function doFetch(url, options) {
const response = await fetch(url, options)
return await response.json()
}
function concatDedupe (newElements, dedupedArray, dedupeByKey) {
for (let newElement of newElements) {
let elementAlreadyInArray = true
elementAlreadyInArray = dedupedArray.find(p => p[dedupeByKey] === newElement[dedupeByKey])
if (!elementAlreadyInArray) {
dedupedArray.push(newElement)
}
}
return dedupedArray
}
async function getPulls() {
const githubOptions = {
method: 'GET',
headers: {
Authorization: `bearer ${GITHUB_TOKEN}`,
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
}
}
const tagsData = await doFetch(`${ghBaseUrl}/tags`, githubOptions)
if (!tagsData || !tagsData.length || tagsData.length < 2) {
core.setFailed('Unable to calculate DORA Lead Time for Changes for PRs between the most recent and second most recent git tags because this repo has less than two git tags.')
process.exit(1)
}
// Per the requirements in the ticket (API-28959) and the spike ticket (API-28443),
// we *assume* the second element in the array of tags is the second-most recent tag.
const newTag = tagsData[0].name
const previousTag = tagsData[1].name
core.info(`The new tag is: ${newTag}. The previous tag is: ${previousTag}`)
const commitsData = await doFetch(`${ghBaseUrl}/compare/${previousTag}...${newTag}`, githubOptions)
if (!commitsData || !commitsData.commits || !commitsData.commits.length || commitsData.commits.length < 1) {
core.setFailed('Unable to calculate DORA Lead Time for Changes for PRs between the most recent and second most recent git tags because there are no commits between these two tags.')
process.exit(1)
}
let dedupedPulls = []
for (let commit of commitsData.commits) {
let pullsData = await doFetch(`${ghBaseUrl}/commits/${commit.sha}/pulls`, githubOptions)
dedupedPulls = concatDedupe(pullsData, dedupedPulls, 'id')
}
if (!dedupedPulls || !dedupedPulls.length || dedupedPulls.length < 1) {
core.setFailed('Unable to calculate DORA Lead Time for Changes for PRs between the most recent and second most recent git tags because there are no PRs between these two tags.')
process.exit(1)
}
return dedupedPulls
}
function calculateAvergeLeadTime(pulls) {
let averageLeadTime = 0
let mergedPullsCount = 0
for (let pull of dedupedPulls) {
if (pull.merged_at === null) {
core.info(`Pull number ${pull.number} was never merged. Skipping...`)
continue
}
mergedPullsCount++
let millisecondsBetween = new Date().getTime() - new Date(pull.created_at).getTime()
averageLeadTime += Math.round(millisecondsBetween / millisecondsPerSecond)
core.info(`Lead Time for pull number ${pull.number} is ${Math.round(millisecondsBetween / millisecondsPerSecond)} seconds.`)
}
if (mergedPullsCount === 0) {
core.setFailed('Unable to calculate DORA Lead Time for Changes for PRs between the most recent and second most recent git tags because there are no merged PRs between these two tags.')
process.exit(1)
}
return Math.round(averageLeadTime / mergedPullsCount)
}
async function submitMetrics(averageLeadTime) {
let datadogPostBody = {
series: [
{
metric: "lead_time_for_changes",
points: [
{
timestamp: Math.round(new Date().getTime() / millisecondsPerSecond),
value: averageLeadTime
}
],
type: datadogGuageMetricType,
"tags": [ `repo_name:${repoName}` ],
"unit": "second"
}
]
}
let datadogOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'DD-API-KEY': DATADOG_API_KEY_FOR_LEAD_TIME_METRIC,
'DD-APPLICATION-KEY': DATADOG_APP_KEY_FOR_LEAD_TIME_METRIC
},
body: JSON.stringify(datadogPostBody),
}
const datadogData = await doFetch(datadogSubmitMetricsUrl, datadogOptions)
core.info('Datadog metric submission response body is:')
core.info(JSON.stringify(datadogData, null, 2))
}
const dedupedPulls = await getPulls()
const averageLeadTime = calculateAvergeLeadTime(dedupedPulls)
core.info(`Average Lead Time of the merged PRs is ${averageLeadTime} seconds.`)
await submitMetrics(averageLeadTime)