Skip to content

Commit 6689f7f

Browse files
committed
feat: add scripts for circleci vs gha analysis
1 parent c2ddca8 commit 6689f7f

7 files changed

+1053
-256
lines changed

circleci.ts

+254
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
export type TrendsOrMetrics = {
2+
total_credits_used: number
3+
total_duration_secs: number
4+
throughput: number
5+
total_runs: number
6+
success_rate: number
7+
}
8+
9+
export type Project = {
10+
project_name: string
11+
}
12+
13+
export type TrendsAndMetrics = {
14+
trends: TrendsOrMetrics
15+
metrics: TrendsOrMetrics
16+
}
17+
18+
export type OrgSummaryData = {
19+
org_data: TrendsAndMetrics
20+
all_projects: string[]
21+
org_project_data: (Project & TrendsAndMetrics)[]
22+
}
23+
24+
export enum ReportingWindow {
25+
LAST_90_DAYS = "last-90-days",
26+
LAST_60_DAYS = "last-60-days",
27+
LAST_30_DAYS = "last-30-days",
28+
LAST_7_DAYS = "last-7-days",
29+
LAST_24_HOURS = "last-24-hours"
30+
}
31+
32+
export type Workflow = {
33+
workflow_name: string
34+
}
35+
36+
export type Branch = {
37+
branch: string
38+
}
39+
40+
export type ProjectWorkflowsPageData = {
41+
project_workflow_branch_data: (Workflow & Branch & TrendsAndMetrics)[]
42+
all_workflows: string[]
43+
org_id: string
44+
all_branches: string[]
45+
project_workflow_data: (Workflow & TrendsAndMetrics)[]
46+
project_id: string
47+
project_data: TrendsAndMetrics
48+
}
49+
50+
export type Paged<T> = {
51+
items: T[]
52+
next_page_token?: string
53+
}
54+
55+
export type Metrics = {
56+
name: string
57+
metrics: {
58+
total_runs: number
59+
successful_runs: number
60+
mttr: number
61+
total_credits_used: number
62+
failed_runs: number
63+
median_credits_used: number
64+
success_rate: number
65+
duration_metrics: {
66+
min: number
67+
mean: number
68+
median: number
69+
p95: number
70+
max: number
71+
standard_deviation: number
72+
total_duration: number
73+
}
74+
total_recoveries: number
75+
throughput: number
76+
}
77+
window_start: string
78+
window_end: string
79+
}
80+
81+
export type ProjectWorkflowMetrics = {
82+
project_id: string
83+
} & Metrics
84+
85+
export type ProjectWorkflowRuns = {
86+
id: string
87+
duration: number
88+
status: string
89+
created_at: string
90+
stopped_at: string
91+
credits_used: number
92+
branch: string
93+
is_approval: boolean
94+
}
95+
96+
export type ProjectWorkflowJobMetrics = Metrics
97+
98+
export type WorkflowJob = {
99+
dependencies: string[]
100+
job_number: number
101+
id: string
102+
started_at: string
103+
name: string
104+
project_slug: string
105+
status: string
106+
type: string
107+
stopped_at: string
108+
}
109+
110+
export type JobDetails = {
111+
created_at: string
112+
duration: number
113+
executor: {
114+
resource_class: string
115+
type: string
116+
}
117+
messages: string[]
118+
queued_at: string
119+
started_at: string
120+
parallel_runs: {
121+
index: number
122+
status: string
123+
}[]
124+
contexts: string[]
125+
latest_workflow: {
126+
id: string
127+
name: string
128+
}
129+
name: string
130+
number: number
131+
organization: {
132+
name: string
133+
}
134+
parallelism: number
135+
pipeline: {
136+
id: string
137+
}
138+
project: {
139+
external_url: string
140+
id: string
141+
name: string
142+
slug: string
143+
}
144+
web_url: string
145+
}
146+
147+
export type ProjectBySlug = {
148+
slug: string
149+
name: string
150+
id: string
151+
organization_name: string
152+
organization_slug: string
153+
organization_id: string
154+
vcs_info: {
155+
vcs_url: string
156+
provider: string
157+
default_branch: string
158+
}
159+
}
160+
161+
export class Client {
162+
token: string
163+
debug: boolean
164+
165+
constructor(token: string, debug: boolean) {
166+
this.token = token
167+
this.debug = debug
168+
}
169+
170+
async getJSON<T>(url: string, params: URLSearchParams = new URLSearchParams()): Promise<T> {
171+
if (this.debug) {
172+
console.log(`GET ${url}?${params}`)
173+
}
174+
const res = await fetch(`${url}?${params}`, {
175+
headers: {
176+
"Circle-Token": this.token
177+
}
178+
})
179+
const json = await res.json()
180+
if (this.debug) {
181+
console.log(JSON.stringify(json, null, 2))
182+
}
183+
return json
184+
}
185+
186+
async getPagedJSON<T>(url: string, params: URLSearchParams = new URLSearchParams()): Promise<T[]> {
187+
const res: T[] = []
188+
let page: Paged<T> | undefined
189+
do {
190+
if (page?.next_page_token) {
191+
params.set("page-token", page?.next_page_token)
192+
}
193+
page = await this.getJSON<Paged<T>>(url, params)
194+
res.push(...page.items)
195+
} while (page?.next_page_token)
196+
return res
197+
}
198+
199+
// https://circleci.com/docs/api/v2/index.html#operation/getOrgSummaryData
200+
getOrgSummaryData(org: string, reportingWindow: ReportingWindow = ReportingWindow.LAST_90_DAYS): Promise<OrgSummaryData> {
201+
return this.getJSON(`https://circleci.com/api/v2/insights/gh/${org}/summary`, new URLSearchParams({"reporting-window": reportingWindow}))
202+
}
203+
204+
// https://circleci.com/docs/api/v2/index.html#operation/getProjectWorkflowsPageData
205+
getProjectWorkflowsPageData(org: string, repo: string, reportingWindow: ReportingWindow = ReportingWindow.LAST_90_DAYS): Promise<ProjectWorkflowsPageData> {
206+
return this.getJSON(`https://circleci.com/api/v2/insights/pages/gh/${org}/${repo}/summary`, new URLSearchParams({"reporting-window": reportingWindow}))
207+
}
208+
209+
// https://circleci.com/docs/api/v2/index.html#operation/getProjectWorkflowMetrics
210+
getProjectWorkflowMetrics(org: string, repo: string, allBranches = false, reportingWindow: ReportingWindow = ReportingWindow.LAST_90_DAYS): Promise<ProjectWorkflowMetrics[]> {
211+
return this.getPagedJSON(`https://circleci.com/api/v2/insights/gh/${org}/${repo}/workflows`, new URLSearchParams({"reporting-window": reportingWindow, "all-branches": allBranches.toString()}))
212+
}
213+
214+
// https://circleci.com/docs/api/v2/index.html#operation/getProjectWorkflowRuns
215+
async getProjectWorkflowRuns(org: string, repo: string, workflow: string, allBranches = false, reportingWindow: ReportingWindow = ReportingWindow.LAST_90_DAYS): Promise<ProjectWorkflowRuns[]> {
216+
const [startDate, endDate] = ((reportingWindow: ReportingWindow) => {
217+
const now = Date.now()
218+
switch (reportingWindow) {
219+
case ReportingWindow.LAST_90_DAYS:
220+
return [new Date(now - 90 * 24 * 60 * 60 * 1000).toISOString(), new Date(now).toISOString()]
221+
case ReportingWindow.LAST_60_DAYS:
222+
return [new Date(now - 60 * 24 * 60 * 60 * 1000).toISOString(), new Date(now).toISOString()]
223+
case ReportingWindow.LAST_30_DAYS:
224+
return [new Date(now - 30 * 24 * 60 * 60 * 1000).toISOString(), new Date(now).toISOString()]
225+
case ReportingWindow.LAST_7_DAYS:
226+
return [new Date(now - 7 * 24 * 60 * 60 * 1000).toISOString(), new Date(now).toISOString()]
227+
case ReportingWindow.LAST_24_HOURS:
228+
return [new Date(now - 24 * 60 * 60 * 1000).toISOString(), new Date(now).toISOString()]
229+
}
230+
})(reportingWindow)
231+
const json = await this.getJSON<Paged<ProjectWorkflowRuns>>(`https://circleci.com/api/v2/insights/gh/${org}/${repo}/workflows/${workflow}`, new URLSearchParams({"all-branches": `${allBranches}`, "start-date": startDate, "end-date": endDate}))
232+
return json.items
233+
}
234+
235+
// https://circleci.com/docs/api/v2/index.html#operation/getProjectWorkflowJobMetrics
236+
getProjectWorkflowJobMetrics(org: string, repo: string, workflow: string, allBranches = false, reportingWindow: ReportingWindow = ReportingWindow.LAST_90_DAYS): Promise<ProjectWorkflowJobMetrics[]> {
237+
return this.getPagedJSON(`https://circleci.com/api/v2/insights/gh/${org}/${repo}/workflows/${workflow}/jobs`, new URLSearchParams({"reporting-window": reportingWindow, "all-branches": allBranches.toString()}))
238+
}
239+
240+
// https://circleci.com/docs/api/v2/index.html#operation/listWorkflowJobs
241+
listWorkflowJobs(id: string): Promise<WorkflowJob[]> {
242+
return this.getPagedJSON(`https://circleci.com/api/v2/workflow/${id}/job`)
243+
}
244+
245+
// https://circleci.com/docs/api/v2/index.html#operation/getJobDetails
246+
getJobDetails(org: string, repo: string, number: string): Promise<JobDetails> {
247+
return this.getJSON(`https://circleci.com/api/v2/project/gh/${org}/${repo}/job/${number}`)
248+
}
249+
250+
// https://circleci.com/docs/api/v2/index.html#operation/getProjectBySlug
251+
getProjectBySlug(org: string, repo: string): Promise<ProjectBySlug> {
252+
return this.getJSON(`https://circleci.com/api/v2/project/gh/${org}/${repo}`)
253+
}
254+
}

0 commit comments

Comments
 (0)