Published
Edited
Jul 21, 2021
4 forks
Importers
7 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
repo = "sveltejs/svelte"
Insert cell
workflow = 196961
Insert cell
Insert cell
Insert cell
Insert cell
workflows = fetchWorkflows(repo)
Insert cell
runs = fetchRuns(repo, workflow, {runsLimit: 5})
Insert cell
jobs = parseJobs(runs)
Insert cell
Insert cell
viewof branch = Inputs.select(new Set(runs.map(r => r.head_branch)))
Insert cell
jobs.filter(d => d.run.head_branch === branch)
Insert cell
Insert cell
sampleWorkflows = FileAttachment("workflows@1.json").json()
Insert cell
sampleRuns = FileAttachment("runs.json").json()
Insert cell
sampleJobs = parseJobs(sampleRuns)
Insert cell
Insert cell
hasApiKey = {
try {
Secret("GITHUB_ACCESS_TOKEN");
return true;
} catch {
return false;
}
}
Insert cell
mutable rateLimit = null
Insert cell
// Makes an authenticated request if token is available, and updates the rate limit info
fetchGithubResponse = async (path, params, options = {}) => {
params = new URLSearchParams(params);
const {headers, ...otherOptions} = options;
const res = await fetch(`https://api.github.com${path}?${params}`, {
headers: {
...(hasApiKey && {authorization: `token ${Secret("GITHUB_ACCESS_TOKEN")}`}),
accept: 'application/vnd.github.v3+json',
...headers
},
...otherOptions
});
mutable rateLimit = {
remaining: +res.headers.get("X-RateLimit-Remaining"),
reset: new Date(res.headers.get("X-RateLimit-Reset") * 1000)
};
return res;
}
Insert cell
// Convenience function to skip right to the json response
fetchGithub = async (...args) => {
const res = await fetchGithubResponse(...args);
return res.ok ? res.json() : false;
}
Insert cell
fetchWorkflows = async (repo, {limit = 50} = {}) =>
(await fetchGithub(`/repos/${repo}/actions/workflows?per_page=${limit}`)).workflows
Insert cell
// This async generator progressively yields more jobs as they're fetched
fetchRuns = async function* (
repo,
workflow,
{ runsLimit = hasApiKey ? 50 : 20, jobsLimit = 100 } = {}
) {
const runs = (
await fetchGithub(`/repos/${repo}/actions/workflows/${workflow}/runs`, {
per_page: runsLimit
})
).workflow_runs;
if (!runs) {
return undefined;
} else {
for await (const [run, { jobs }] of runs.map(async (r) => [
r,
await fetchGithub(r.jobs_url.replace("https://api.github.com", ""), {
filter: "all",
per_page: jobsLimit
})
])) {
run.jobs = jobs;
yield runs;
}
}
}
Insert cell
// There are multiple jobs per run; this flattens them to return tidy data
parseJobs = runs => {
if(!runs) return undefined;
const retries = [];
return runs.flatMap(run => (run.jobs || []).map(j => {
const name = j.name;
const { run_number } = run;
const retry = retries[[run_number, name]] = (retries[[run_number, name]] || 0) + 1;
const started_at = new Date(j.started_at);
const completed_at = new Date(j.completed_at);
return {
...j,
name,
run,
run_number: `${run_number}.${retry}`,
message: run.head_commit.message,
author: run.head_commit.author.name,
branch: run.head_branch,// === 'main' ? 'main' : 'other',
started_at,
completed_at,
minutes: (j.completed_at && j.started_at)
? (completed_at - started_at) / 1000 / 60
: undefined
}
}))
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more