Skip to content

Commit 2a9716e

Browse files
committed
Add first_review_time_seconds metric
1 parent 7aa40a4 commit 2a9716e

File tree

6 files changed

+109
-3
lines changed

6 files changed

+109
-3
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,9 @@ It has the following tags:
216216
- `base_ref`
217217
- `head_ref`
218218
- `merged` = `true` or `false`
219+
- `first_review_time_seconds`
220+
- Time from the first review request to the first review
221+
- Available if a pull request has both review request and review
219222
- `requested_team`
220223
- Team(s) of requested reviewer(s)
221224
- `label`

src/generated/graphql.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export type ClosedPullRequestQueryVariables = Types.Exact<{
77
}>;
88

99

10-
export type ClosedPullRequestQuery = { __typename?: 'Query', repository?: { __typename?: 'Repository', pullRequest?: { __typename?: 'PullRequest', commits: { __typename?: 'PullRequestCommitConnection', nodes?: Array<{ __typename?: 'PullRequestCommit', commit: { __typename?: 'Commit', authoredDate: string, committedDate: string } } | null> | null } } | null } | null };
10+
export type ClosedPullRequestQuery = { __typename?: 'Query', rateLimit?: { __typename?: 'RateLimit', cost: number } | null, repository?: { __typename?: 'Repository', pullRequest?: { __typename?: 'PullRequest', commits: { __typename?: 'PullRequestCommitConnection', nodes?: Array<{ __typename?: 'PullRequestCommit', commit: { __typename?: 'Commit', authoredDate: string, committedDate: string } } | null> | null }, reviewRequests: { __typename?: 'PullRequestTimelineItemsConnection', nodes?: Array<{ __typename?: 'AddedToProjectEvent' } | { __typename?: 'AssignedEvent' } | { __typename?: 'AutoMergeDisabledEvent' } | { __typename?: 'AutoMergeEnabledEvent' } | { __typename?: 'AutoRebaseEnabledEvent' } | { __typename?: 'AutoSquashEnabledEvent' } | { __typename?: 'AutomaticBaseChangeFailedEvent' } | { __typename?: 'AutomaticBaseChangeSucceededEvent' } | { __typename?: 'BaseRefChangedEvent' } | { __typename?: 'BaseRefDeletedEvent' } | { __typename?: 'BaseRefForcePushedEvent' } | { __typename?: 'ClosedEvent' } | { __typename?: 'CommentDeletedEvent' } | { __typename?: 'ConnectedEvent' } | { __typename?: 'ConvertToDraftEvent' } | { __typename?: 'ConvertedNoteToIssueEvent' } | { __typename?: 'CrossReferencedEvent' } | { __typename?: 'DemilestonedEvent' } | { __typename?: 'DeployedEvent' } | { __typename?: 'DeploymentEnvironmentChangedEvent' } | { __typename?: 'DisconnectedEvent' } | { __typename?: 'HeadRefDeletedEvent' } | { __typename?: 'HeadRefForcePushedEvent' } | { __typename?: 'HeadRefRestoredEvent' } | { __typename?: 'IssueComment' } | { __typename?: 'LabeledEvent' } | { __typename?: 'LockedEvent' } | { __typename?: 'MarkedAsDuplicateEvent' } | { __typename?: 'MentionedEvent' } | { __typename?: 'MergedEvent' } | { __typename?: 'MilestonedEvent' } | { __typename?: 'MovedColumnsInProjectEvent' } | { __typename?: 'PinnedEvent' } | { __typename?: 'PullRequestCommit' } | { __typename?: 'PullRequestCommitCommentThread' } | { __typename?: 'PullRequestReview' } | { __typename?: 'PullRequestReviewThread' } | { __typename?: 'PullRequestRevisionMarker' } | { __typename?: 'ReadyForReviewEvent' } | { __typename?: 'ReferencedEvent' } | { __typename?: 'RemovedFromProjectEvent' } | { __typename?: 'RenamedTitleEvent' } | { __typename?: 'ReopenedEvent' } | { __typename?: 'ReviewDismissedEvent' } | { __typename?: 'ReviewRequestRemovedEvent' } | { __typename?: 'ReviewRequestedEvent', createdAt: string } | { __typename?: 'SubscribedEvent' } | { __typename?: 'TransferredEvent' } | { __typename?: 'UnassignedEvent' } | { __typename?: 'UnlabeledEvent' } | { __typename?: 'UnlockedEvent' } | { __typename?: 'UnmarkedAsDuplicateEvent' } | { __typename?: 'UnpinnedEvent' } | { __typename?: 'UnsubscribedEvent' } | { __typename?: 'UserBlockedEvent' } | null> | null }, reviews: { __typename?: 'PullRequestTimelineItemsConnection', nodes?: Array<{ __typename?: 'AddedToProjectEvent' } | { __typename?: 'AssignedEvent' } | { __typename?: 'AutoMergeDisabledEvent' } | { __typename?: 'AutoMergeEnabledEvent' } | { __typename?: 'AutoRebaseEnabledEvent' } | { __typename?: 'AutoSquashEnabledEvent' } | { __typename?: 'AutomaticBaseChangeFailedEvent' } | { __typename?: 'AutomaticBaseChangeSucceededEvent' } | { __typename?: 'BaseRefChangedEvent' } | { __typename?: 'BaseRefDeletedEvent' } | { __typename?: 'BaseRefForcePushedEvent' } | { __typename?: 'ClosedEvent' } | { __typename?: 'CommentDeletedEvent' } | { __typename?: 'ConnectedEvent' } | { __typename?: 'ConvertToDraftEvent' } | { __typename?: 'ConvertedNoteToIssueEvent' } | { __typename?: 'CrossReferencedEvent' } | { __typename?: 'DemilestonedEvent' } | { __typename?: 'DeployedEvent' } | { __typename?: 'DeploymentEnvironmentChangedEvent' } | { __typename?: 'DisconnectedEvent' } | { __typename?: 'HeadRefDeletedEvent' } | { __typename?: 'HeadRefForcePushedEvent' } | { __typename?: 'HeadRefRestoredEvent' } | { __typename?: 'IssueComment' } | { __typename?: 'LabeledEvent' } | { __typename?: 'LockedEvent' } | { __typename?: 'MarkedAsDuplicateEvent' } | { __typename?: 'MentionedEvent' } | { __typename?: 'MergedEvent' } | { __typename?: 'MilestonedEvent' } | { __typename?: 'MovedColumnsInProjectEvent' } | { __typename?: 'PinnedEvent' } | { __typename?: 'PullRequestCommit' } | { __typename?: 'PullRequestCommitCommentThread' } | { __typename?: 'PullRequestReview', createdAt: string, author?: { __typename?: 'Bot', login: string } | { __typename?: 'EnterpriseUserAccount', login: string } | { __typename?: 'Mannequin', login: string } | { __typename?: 'Organization', login: string } | { __typename?: 'User', login: string } | null } | { __typename?: 'PullRequestReviewThread' } | { __typename?: 'PullRequestRevisionMarker' } | { __typename?: 'ReadyForReviewEvent' } | { __typename?: 'ReferencedEvent' } | { __typename?: 'RemovedFromProjectEvent' } | { __typename?: 'RenamedTitleEvent' } | { __typename?: 'ReopenedEvent' } | { __typename?: 'ReviewDismissedEvent' } | { __typename?: 'ReviewRequestRemovedEvent' } | { __typename?: 'ReviewRequestedEvent' } | { __typename?: 'SubscribedEvent' } | { __typename?: 'TransferredEvent' } | { __typename?: 'UnassignedEvent' } | { __typename?: 'UnlabeledEvent' } | { __typename?: 'UnlockedEvent' } | { __typename?: 'UnmarkedAsDuplicateEvent' } | { __typename?: 'UnpinnedEvent' } | { __typename?: 'UnsubscribedEvent' } | { __typename?: 'UserBlockedEvent' } | null> | null } } | null } | null };
1111

1212
export type CompletedCheckSuiteQueryVariables = Types.Exact<{
1313
node_id: Types.Scalars['ID'];

src/pullRequest/metrics.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ export const computePullRequestClosedMetrics = (
134134
points: [[t, t - unixTime(pr.firstCommit.committedDate)]],
135135
}
136136
)
137+
138+
if (pr.firstReviewRequest && pr.firstReview) {
139+
const firstReviewRequestedAt = unixTime(pr.firstReviewRequest.createdAt)
140+
const firstReviewedAt = unixTime(pr.firstReview.createdAt)
141+
series.push({
142+
host: 'github.com',
143+
tags,
144+
metric: 'github.actions.pull_request_closed.first_review_time_seconds',
145+
type: 'gauge',
146+
points: [[t, firstReviewedAt - firstReviewRequestedAt]],
147+
})
148+
}
137149
}
138150

139151
// Datadog treats a tag as combination of values.

src/queries/closedPullRequest.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { Octokit } from '../types'
33

44
const query = /* GraphQL */ `
55
query closedPullRequest($owner: String!, $name: String!, $number: Int!) {
6+
rateLimit {
7+
cost
8+
}
69
repository(owner: $owner, name: $name) {
710
pullRequest(number: $number) {
811
commits(first: 1) {
@@ -13,6 +16,22 @@ const query = /* GraphQL */ `
1316
}
1417
}
1518
}
19+
reviewRequests: timelineItems(itemTypes: [REVIEW_REQUESTED_EVENT], first: 1) {
20+
nodes {
21+
__typename
22+
... on ReviewRequestedEvent {
23+
createdAt
24+
}
25+
}
26+
}
27+
reviews: timelineItems(itemTypes: [PULL_REQUEST_REVIEW], first: 1) {
28+
nodes {
29+
__typename
30+
... on PullRequestReview {
31+
createdAt
32+
}
33+
}
34+
}
1635
}
1736
}
1837
}
@@ -23,6 +42,12 @@ export type ClosedPullRequest = {
2342
authoredDate: string
2443
committedDate: string
2544
}
45+
firstReviewRequest?: {
46+
createdAt: string
47+
}
48+
firstReview?: {
49+
createdAt: string
50+
}
2651
}
2752

2853
export const queryClosedPullRequest = async (
@@ -36,8 +61,35 @@ export const queryClosedPullRequest = async (
3661
if (r.repository.pullRequest.commits.nodes[0] == null) {
3762
throw new Error(`commit is null: ${JSON.stringify(r)}`)
3863
}
39-
const firstCommit = r.repository.pullRequest.commits.nodes[0].commit
4064
return {
41-
firstCommit,
65+
firstCommit: r.repository.pullRequest.commits.nodes[0].commit,
66+
...findFirstReviewRequest(r),
67+
...findFirstReview(r),
68+
}
69+
}
70+
71+
const findFirstReviewRequest = (
72+
r: ClosedPullRequestQuery
73+
): Pick<ClosedPullRequest, 'firstReviewRequest'> | undefined => {
74+
if (!r.repository?.pullRequest?.reviewRequests.nodes?.length) {
75+
return undefined
76+
}
77+
if (r.repository.pullRequest.reviewRequests.nodes[0]?.__typename !== 'ReviewRequestedEvent') {
78+
return undefined
79+
}
80+
return {
81+
firstReviewRequest: r.repository.pullRequest.reviewRequests.nodes[0],
82+
}
83+
}
84+
85+
const findFirstReview = (r: ClosedPullRequestQuery): Pick<ClosedPullRequest, 'firstReview'> | undefined => {
86+
if (!r.repository?.pullRequest?.reviews.nodes?.length) {
87+
return undefined
88+
}
89+
if (r.repository.pullRequest.reviews.nodes[0]?.__typename !== 'PullRequestReview') {
90+
return undefined
91+
}
92+
return {
93+
firstReview: r.repository.pullRequest.reviews.nodes[0],
4294
}
4395
}

tests/__snapshots__/run.test.ts.snap

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,29 @@ Array [
189189
],
190190
"type": "gauge",
191191
},
192+
Object {
193+
"host": "github.com",
194+
"metric": "github.actions.pull_request_closed.first_review_time_seconds",
195+
"points": Array [
196+
Array [
197+
1558279233,
198+
600,
199+
],
200+
],
201+
"tags": Array [
202+
"repository_owner:Codertocat",
203+
"repository_name:Hello-World",
204+
"sender:Codertocat",
205+
"sender_type:User",
206+
"user:Codertocat",
207+
"pull_request_number:2",
208+
"draft:false",
209+
"base_ref:master",
210+
"head_ref:changes",
211+
"merged:false",
212+
],
213+
"type": "gauge",
214+
},
192215
Object {
193216
"host": "github.com",
194217
"metric": "github.actions.api_rate_limit.remaining",

tests/pullRequest/fixtures/closedPullRequest.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,22 @@ export const exampleClosedPullRequestQuery: ClosedPullRequestQuery = {
1414
},
1515
],
1616
},
17+
reviewRequests: {
18+
nodes: [
19+
{
20+
__typename: 'ReviewRequestedEvent',
21+
createdAt: '2019-05-15T15:30:00Z',
22+
},
23+
],
24+
},
25+
reviews: {
26+
nodes: [
27+
{
28+
__typename: 'PullRequestReview',
29+
createdAt: '2019-05-15T15:40:00Z',
30+
},
31+
],
32+
},
1733
},
1834
},
1935
}

0 commit comments

Comments
 (0)