Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 86 additions & 38 deletions bin/ncu-ci
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ const yargs = require('yargs');

// eslint-disable-next-line no-unused-vars
const argv = yargs
.command({
command: 'rate <type>',
desc: 'Calculate the green rate of a CI job in the last 100 runs',
builder: (yargs) => {
yargs
.positional('type', {
describe: 'type of CI',
choices: ['commit', 'pr']
});
},
handler
})
.command({
command: 'walk <type>',
desc: 'Walk the CI and store the failures',
Expand Down Expand Up @@ -117,6 +129,67 @@ async function getResults(cli, request, job) {
return build;
}

async function runQueue(queue, cli, request, argv) {
let dataToCopy = '';
let dataToJson = [];

for (let job of queue) {
cli.separator('');
if (job.link) {
cli.log(`Running ${job.link}`);
} else {
cli.log(`Running ${job.type}: ${job.jobid}`);
}
cli.separator('');
const build = await getResults(cli, request, job);
build.display();

if (argv.copy) {
dataToCopy += build.formatAsMarkdown();
}

if (argv.json) {
dataToJson = dataToJson.concat(build.formatAsJson());
}
}

if (argv.copy) {
clipboardy.writeSync(dataToCopy);
cli.separator('');
cli.log(`Written markdown to clipboard`);
}

if (argv.json) {
writeJson(argv.json, dataToJson);
cli.separator('');
cli.log(`Written JSON to ${argv.json}`);
}
}

function pad(any, length) {
return (any + '').padEnd(length);
}

// Produces a row for https://github.com/nodejs/reliability#ci-health-history
function displayHealth(builds, cli) {
const [
count, success, pending, aborted, failed, unstable
] = [
builds.count, builds.success.length, builds.pending.length,
builds.aborted.length, builds.failed.length, builds.unstable.length
];
const rate = `${(success / (count - pending - aborted) * 100).toFixed(2)}%`;
// eslint-disable-next-line max-len
cli.log('| UTC Time | RUNNING | SUCCESS | UNSTABLE | ABORTED | FAILURE | Green Rate |');
// eslint-disable-next-line max-len
cli.log('| ---------------- | ------- | ------- | -------- | ------- | ------- | ---------- |');
const time = new Date().toISOString().slice(0, 16).replace('T', ' ');
let result = `| ${time} | ${pad(pending, 7)} | ${pad(success, 8)}|`;
result += ` ${pad(unstable, 8)} | ${pad(aborted, 7)} | ${pad(failed, 7)} |`;
result += ` ${pad(rate, 10)} |`;
cli.log(result);
}

async function main(command, argv) {
const cli = new CLI();
const credentials = await auth();
Expand All @@ -129,13 +202,19 @@ async function main(command, argv) {
'benchmark': BENCHMARK
};

if (command === 'walk') {
if (command === 'rate' || command === 'walk') {
const type = commandToType[argv.type];
const builds = await listBuilds(cli, request, type);
for (const build of builds.failed) {
queue.push(build);
if (command === 'walk') {
for (const build of builds.failed) {
queue.push(build);
}
} else {
displayHealth(builds, cli);
}
} else if (command === 'url') {
}

if (command === 'url') {
let parsed = parseJobFromURL(argv.url);
if (parsed) {
queue.push({
Expand All @@ -155,46 +234,15 @@ async function main(command, argv) {
});
}
}
} else {
} else if (commandToType[command]) {
queue.push({
type: commandToType[command],
jobid: argv.jobid
});
}

let dataToCopy = '';
let dataToJson = [];

for (let job of queue) {
cli.separator('');
if (job.link) {
cli.log(`Running ${job.link}`);
} else {
cli.log(`Running ${job.type}: ${job.jobid}`);
}
cli.separator('');
const build = await getResults(cli, request, job);
build.display();

if (argv.copy) {
dataToCopy += build.formatAsMarkdown();
}

if (argv.json) {
dataToJson = dataToJson.concat(build.formatAsJson());
}
}

if (argv.copy) {
clipboardy.writeSync(dataToCopy);
cli.separator('');
cli.log(`Written markdown to clipboard`);
}

if (argv.json) {
writeJson(argv.json, dataToJson);
cli.separator('');
cli.log(`Written JSON to ${argv.json}`);
if (queue.length > 0) {
await runQueue(queue, cli, request, argv);
}
}

Expand Down
9 changes: 9 additions & 0 deletions docs/ncu-ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Supported jobs:
ncu-ci <command>

Commands:
ncu-ci rate <type> Calculate the green rate of a CI job in the last 100
runs
ncu-ci walk <type> Walk the CI and store the failures
ncu-ci url <url> Automatically detect CI type and show results
ncu-ci pr <jobid> Show results of a node-test-pull-request CI job
Expand Down Expand Up @@ -49,6 +51,13 @@ failures into a JSON file
ncu-ci walk pr --json database.json
```

Calculate the green rate of the CI for
[CI Health History](https://github.com/nodejs/reliability#ci-health-history)

```
ncu-ci rate pr
```

## Caveats

The CI failures are parsed using pattern matching and could be incorrect. Feel
Expand Down
2 changes: 2 additions & 0 deletions lib/ci.js
Original file line number Diff line number Diff line change
Expand Up @@ -550,9 +550,11 @@ async function listBuilds(cli, request, type) {
const aborted = filterBuild(builds, ABORTED);
const pending = filterBuild(builds, null);
const unstable = filterBuild(builds, UNSTABLE);
const success = filterBuild(builds, SUCCESS);
cli.stopSpinner('Done');

return {
success,
failed,
aborted,
pending,
Expand Down