Skip to content

Commit

Permalink
[Fix] get proper required status and output to console
Browse files Browse the repository at this point in the history
  • Loading branch information
Green-Ranger11 committed Mar 14, 2022
1 parent 0b1ec30 commit 194d0e9
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 38 deletions.
76 changes: 49 additions & 27 deletions bin/can-merge
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,31 @@ const args = Yargs

const token = args.token || GITHUB_TOKEN || GH_TOKEN;

function outputStatus(response) {
function outputChecks(failure, pending) {
if (pending.length > 0) {
console.info(chalk.red(`Pending Checks (${pending.length}): `));
pending.forEach((p) => {
if (p.isRequired) {
console.info(chalk.red(`${p.name} `));
} else {
console.info(chalk.yellow(`${p.name} `));

}
});
}
if (failure.length > 0) {
console.info(chalk.red(`Failed Checks (${failure.length}): `));
failure.forEach((f) => {
if (f.isRequired) {
console.info(chalk.red(`${f.name} `));
} else {
console.info(chalk.yellow(`${f.name} `));
}
});
}
}

function outputStatus(response, params) {
if (NODE_ENV === 'DEBUG') {
console.error(JSON.stringify(response, null, 2));
}
Expand All @@ -142,34 +166,30 @@ function outputStatus(response) {
}
}

const prs = filterPullRequest(response, args.pr);

if (prs.length === 0 && !args.commit) {
if (args.pr) {
console.info(chalk.redBright(`⚠ This remote repository does not contain any pull requests matching number: ${args.pr}`));
filterPullRequest(response, params).then((prs) => {
if (prs.length === 0 && !args.commit) {
if (params.pr) {
console.info(chalk.redBright(`⚠ This remote repository does not contain any pull requests matching number: ${args.pr}`));
} else {
console.info(chalk.redBright(`⚠ This remote repository does not contain any pull requests matching sha: ${args.sha}`));
}
} else {
console.info(chalk.redBright(`⚠ This remote repository does not contain any pull requests matching sha: ${args.sha}`));
prs.forEach((pr) => {
const requiredChecks = pr.baseRef?.branchProtectionRule?.requiredStatusCheckContexts;
const status = evaluatePullRequest(pr, requiredChecks);
console.info(pr.url, 'by @'.concat(pr.author.login, ':'), pr.title, '\n', getMessage(status));
if (status !== pullRequestStatus.MERGEABLE) {
process.exitCode = (process.exitCode ?? 0) + 1;
}
const { failure, pending } = evaluateChecks(pr, requiredChecks);

outputChecks(failure, pending);
});
}
} else {
prs.forEach((pr) => {
const status = evaluatePullRequest(pr);
console.info(pr.url, 'by @'.concat(pr.author.login, ':'), pr.title, '\n', getMessage(status));
if (status !== pullRequestStatus.MERGEABLE) {
process.exitCode = (process.exitCode ?? 0) + 1;
}
if (status === pullRequestStatus.STATUS_FAILURE || status === pullRequestStatus.STATUS_PENDING) {
const { failure, pending } = evaluateChecks(pr);
});

if (pending.length > 0) {
console.info(chalk.yellowBright(`Pending Checks (${pending.length}): ${pending.join(', ')}`));
}
if (failure.length > 0) {
console.info(chalk.redBright(`Failed Checks (${failure.length}): ${failure.join(', ')}`));
}
}
});
}
}

const { repo, pr, sha } = args;

const options = {
Expand All @@ -191,11 +211,13 @@ https.request(options, (response) => {
console.error('Waiting for all checks to complete ...\n');
watch(args.retryDelay, () => runQuery(params)).then((res) => {
console.clear();
outputStatus(res, args.pr);
if (args.pr) {
outputStatus(res, params);
}
}).catch((error) => { console.log(chalk.red(error)); });
} else {
runQuery(params).then((res) => {
outputStatus(res, args.pr);
outputStatus(res, params);
}).catch((error) => { console.log(chalk.red(error)); });
}
}).on('error', (err) => {
Expand Down
59 changes: 50 additions & 9 deletions utils/evaluateChecks.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,86 @@
'use strict';

function parseStatusChecks(statusCheckRollup) {
function parseStatusChecks(statusCheckRollup, requiredChecks) {
const pending = [];
const failure = [];

statusCheckRollup.contexts?.nodes?.filter((node) => node.context).forEach((node) => {
if (node.state === 'FAILURE' || node.state === 'ERROR') {
failure.push(node.context);
if (requiredChecks) {
if (requiredChecks.contains(node.context)) {
failure.push({ name: node.context, isRequired: true });
} else {
failure.push({ name: node.context, isRequired: false });
}
} else if (node.isRequired) {
failure.push({ name: node.context, isRequired: true });
} else {
failure.push({ name: node.context, isRequired: false });
}
} else if (node.state === 'PENDING') {
pending.push(node.context);
if (requiredChecks) {
if (requiredChecks.contains(node.context)) {
pending.push({ name: node.context, isRequired: true });
} else {
pending.push({ name: node.context, isRequired: false });
}
} else if (node.isRequired) {
pending.push({ name: node.context, isRequired: true });

} else {
pending.push({ name: node.context, isRequired: false });
}
}
});

return { failure, pending };
}

function parseCheckRuns(statusCheckRollup) {
function parseCheckRuns(statusCheckRollup, requiredChecks) {
const pending = [];
const failure = [];

statusCheckRollup.contexts?.nodes?.filter((node) => node.name).forEach((node) => {
if (node.conclusion === 'FAILURE') {
failure.push(node.name);
if (requiredChecks) {
if (requiredChecks.includes(node.name)) {
failure.push({ name: node.name, isRequired: true });
} else {
failure.push({ name: node.name, isRequired: false });
}
} else if (node.isRequired) {
failure.push({ name: node.name, isRequired: true });
} else {
failure.push({ name: node.name, isRequired: false });
}
} else if (node.status !== 'COMPLETED') {
pending.push(node.name);
if (requiredChecks) {
if (requiredChecks.includes(node.name)) {
pending.push({ name: node.name, isRequired: true });
} else {
pending.push({ name: node.name, isRequired: false });
}
} else if (node.isRequired) {
pending.push({ name: node.name, isRequired: true });
} else {
pending.push({ name: node.name, isRequired: false });
}
}
});

return { failure, pending };
}

module.exports = function evaluateChecks(pullRequest) {
module.exports = function evaluateChecks(pullRequest, requiredChecks) {
const { commits: { nodes: [{ commit: { statusCheckRollup } }] } } = pullRequest;

for (const ctx of statusCheckRollup.contexts.nodes) {
/* eslint no-underscore-dangle: 0 */
if (ctx.__typename === 'StatusContext' && ctx.state !== 'SUCCESS') {
return parseStatusChecks(statusCheckRollup);
return parseStatusChecks(statusCheckRollup, requiredChecks);
}
if (ctx.__typename === 'CheckRun' && ctx.conclusion !== 'SUCCESS') {
return parseCheckRuns(statusCheckRollup);
return parseCheckRuns(statusCheckRollup, requiredChecks);
}
}

Expand Down
24 changes: 22 additions & 2 deletions utils/filterPullRequest.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
'use strict';

const parsePullRequest = require('../utils/parsePullRequest');
const runQuery = require('./runQuery');

module.exports = function filterPullRequest(response, argPr) {
module.exports = async function filterPullRequest(response, params) {
const parsedResponse = parsePullRequest(response);

return argPr ? parsedResponse.filter((pr) => String(pr.number) === argPr) : parsedResponse;
// If user is admin branchProtectionRule is returned
if (parsedResponse[0]?.baseRef?.branchProtectionRule) {
if (params.pr) {
return parsedResponse.filter((pr) => String(pr.number) === params.pr);
}
return parsedResponse;
}

// If --pr is not specified run again to get isRequired
const newParsedResponse = await Promise.all(parsedResponse.map(async (PR) => {
const newParams = { ...params, pr: PR.number };
try {
const newResponse = await runQuery(newParams);
const responseWithRequiredChecks = parsePullRequest(newResponse);
return responseWithRequiredChecks[0];
} catch (err) {
throw err.message;
}
}));
return newParsedResponse;
};

0 comments on commit 194d0e9

Please sign in to comment.