Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add require_passed_checks input to control if should skip checks step #10

Merged
merged 2 commits into from
Apr 5, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
The job of the action is to help click "Update branch" button for you. Designed to work with the `auto-merge` and ["Require branches to be up to date before merging"](https://docs.github.com/en/github/administering-a-repository/about-protected-branches#require-status-checks-before-merging) options. It will update the newest open PR that match the below conditions

- The PR has the `auto-merge` option enabled
- The PR has 2 approvals and no changes-requested review
- The PR has all checks passed
- The PR has 2 approvals and no changes-requested review (configurable)
- The PR has all checks passed (configurable)
- The PR branch has no conflicts with the base branch
- The PR branch is behind the base branch

Expand Down Expand Up @@ -54,7 +54,13 @@ The action will skip PRs that have less approvals than `required_approval_count`

We could retrieve this value from the repo settings through an API call but that will incur one more request. GitHub has [rate limit](https://docs.github.com/en/actions/reference/usage-limits-billing-and-administration#usage-limits) on API usage of GitHub actions.

> API requests - You can execute up to 1000 API requests in an hour across all actions within a repository. If exceeded, additional API calls will fail, which might cause jobs to fail.
### `require_passed_checks`

**Optional**

Default: true

The action will skip PRs that have failed checks.

## Example usage

Expand All @@ -75,6 +81,7 @@ jobs:
token: ${{ secrets.ACTION_USER_TOKEN }}
base: 'master'
required_approval_count: 2
require_passed_checks: false
```

Replace the `VERSION_YOU_WANT_TO_USE` with the actual version you want to use, check the version format [here](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsuses)
Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ inputs:
required: true
description: 'The action will skip PRs that have less approvals than this value'
default: '2'
require_passed_checks:
required: false
description: "If the action should skip PRs that have failed checks, defaults to `true`"
default: "true"
arunshan marked this conversation as resolved.
Show resolved Hide resolved
runs:
using: 'node12'
main: 'dest/index.js'
155 changes: 57 additions & 98 deletions dest/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1461,7 +1461,7 @@ exports.Octokit = Octokit;

Object.defineProperty(exports, "__esModule", ({ value: true }));

var isPlainObject = __nccwpck_require__(558);
var isPlainObject = __nccwpck_require__(287);
var universalUserAgent = __nccwpck_require__(429);

function lowercaseKeys(object) {
Expand Down Expand Up @@ -1849,52 +1849,6 @@ exports.endpoint = endpoint;
//# sourceMappingURL=index.js.map


/***/ }),

/***/ 558:
/***/ ((__unused_webpack_module, exports) => {

"use strict";


Object.defineProperty(exports, "__esModule", ({ value: true }));

/*!
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
*
* Copyright (c) 2014-2017, Jon Schlinkert.
* Released under the MIT License.
*/

function isObject(o) {
return Object.prototype.toString.call(o) === '[object Object]';
}

function isPlainObject(o) {
var ctor,prot;

if (isObject(o) === false) return false;

// If has modified constructor
ctor = o.constructor;
if (ctor === undefined) return true;

// If has modified prototype
prot = ctor.prototype;
if (isObject(prot) === false) return false;

// If constructor does not have an Object-specific method
if (prot.hasOwnProperty('isPrototypeOf') === false) {
return false;
}

// Most likely a plain Object
return true;
}

exports.isPlainObject = isPlainObject;


/***/ }),

/***/ 668:
Expand Down Expand Up @@ -3382,7 +3336,7 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'defau

var endpoint = __nccwpck_require__(440);
var universalUserAgent = __nccwpck_require__(429);
var isPlainObject = __nccwpck_require__(62);
var isPlainObject = __nccwpck_require__(287);
var nodeFetch = _interopDefault(__nccwpck_require__(467));
var requestError = __nccwpck_require__(537);

Expand Down Expand Up @@ -3524,52 +3478,6 @@ exports.request = request;
//# sourceMappingURL=index.js.map


/***/ }),

/***/ 62:
/***/ ((__unused_webpack_module, exports) => {

"use strict";


Object.defineProperty(exports, "__esModule", ({ value: true }));

/*!
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
*
* Copyright (c) 2014-2017, Jon Schlinkert.
* Released under the MIT License.
*/

function isObject(o) {
return Object.prototype.toString.call(o) === '[object Object]';
}

function isPlainObject(o) {
var ctor,prot;

if (isObject(o) === false) return false;

// If has modified constructor
ctor = o.constructor;
if (ctor === undefined) return true;

// If has modified prototype
prot = ctor.prototype;
if (isObject(prot) === false) return false;

// If constructor does not have an Object-specific method
if (prot.hasOwnProperty('isPrototypeOf') === false) {
return false;
}

// Most likely a plain Object
return true;
}

exports.isPlainObject = isPlainObject;


/***/ }),

/***/ 682:
Expand Down Expand Up @@ -3775,6 +3683,52 @@ class Deprecation extends Error {
exports.Deprecation = Deprecation;


/***/ }),

/***/ 287:
/***/ ((__unused_webpack_module, exports) => {

"use strict";


Object.defineProperty(exports, "__esModule", ({ value: true }));

/*!
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
*
* Copyright (c) 2014-2017, Jon Schlinkert.
* Released under the MIT License.
*/

function isObject(o) {
return Object.prototype.toString.call(o) === '[object Object]';
}

function isPlainObject(o) {
var ctor,prot;

if (isObject(o) === false) return false;

// If has modified constructor
ctor = o.constructor;
if (ctor === undefined) return true;

// If has modified prototype
prot = ctor.prototype;
if (isObject(prot) === false) return false;

// If constructor does not have an Object-specific method
if (prot.hasOwnProperty('isPrototypeOf') === false) {
return false;
}

// Most likely a plain Object
return true;
}

exports.isPlainObject = isPlainObject;


/***/ }),

/***/ 467:
Expand Down Expand Up @@ -5978,6 +5932,8 @@ const getAutoUpdateCandidate = async (openPRs) => {
if (!openPRs) return null;

const requiredApprovalCount = github_core.getInput('required_approval_count');
const requirePassedChecks = /true/i.test(github_core.getInput('require_passed_checks'));

// only update `auto merge` enabled PRs
const autoMergeEnabledPRs = openPRs.filter((item) => item.auto_merge);
log(`Count of auto-merge enabled PRs: ${autoMergeEnabledPRs.length}`);
Expand Down Expand Up @@ -6024,12 +5980,15 @@ const getAutoUpdateCandidate = async (openPRs) => {

/**
* #3 check whether the pr has failed checks
* only happens if require_passed_checks input is true
* need to note: the mergeable, and mergeable_state don't reflect the checks status
*/
const didChecksPass = await areAllChecksPassed(sha);
if (!didChecksPass) {
printFailReason(pullNumber, 'The PR has failed or ongoing check(s)');
continue;
if (requirePassedChecks) {
const didChecksPass = await areAllChecksPassed(sha);
if (!didChecksPass) {
printFailReason(pullNumber, 'The PR has failed or ongoing check(s)');
continue;
}
}

return pr;
Expand Down
13 changes: 9 additions & 4 deletions src/lib/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ export const getAutoUpdateCandidate = async (openPRs) => {
if (!openPRs) return null;

const requiredApprovalCount = core.getInput('required_approval_count');
const requirePassedChecks = /true/i.test(core.getInput('require_passed_checks'));

// only update `auto merge` enabled PRs
const autoMergeEnabledPRs = openPRs.filter((item) => item.auto_merge);
log(`Count of auto-merge enabled PRs: ${autoMergeEnabledPRs.length}`);
Expand Down Expand Up @@ -183,12 +185,15 @@ export const getAutoUpdateCandidate = async (openPRs) => {

/**
* #3 check whether the pr has failed checks
* only happens if require_passed_checks input is true
* need to note: the mergeable, and mergeable_state don't reflect the checks status
*/
const didChecksPass = await areAllChecksPassed(sha);
if (!didChecksPass) {
printFailReason(pullNumber, 'The PR has failed or ongoing check(s)');
continue;
if (requirePassedChecks) {
const didChecksPass = await areAllChecksPassed(sha);
if (!didChecksPass) {
printFailReason(pullNumber, 'The PR has failed or ongoing check(s)');
continue;
}
}

return pr;
Expand Down
46 changes: 46 additions & 0 deletions src/lib/github.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const fakeEnv = {
token,
base,
required_approval_count: requiredApprovalCount,
require_passed_checks: 'true'
};

const oldEnv = process.env;
Expand Down Expand Up @@ -443,6 +444,51 @@ describe('getAutoUpdateCanidate()', () => {
);
expect(res).toBe(null);
});
test('PR with failed checks will be selected if require_passed_checks is false', async () => {
process.env.require_passed_checks = 'false';

// has 2 approvals, no request for change review
const reviews = {
...reviewsList,
data: [
{ ...reviewsList.data[0], state: 'APPROVED' },
{ ...reviewsList.data[0], state: 'APPROVED' },
],
};
// pr mergeable: true, merge_state: clean
const prData = {
data: {
...prMetaData.data,
...{ mergeable: true, mergeable_state: 'behind' },
},
};

const check = checksList.data.check_runs[0];
const checks = {
...checksList,
data: {
total_count: 2,
check_runs: [
{ ...check, conclusion: 'success' },
{ ...check, conclusion: 'failure' },
],
},
};

// has auto-merge PR
const prList = [{ ...pullsList.data[0], auto_merge: {} }];
const mockedListReviews = jest.fn().mockResolvedValue(reviews);
const mockedGet = jest.fn().mockResolvedValue(prData);
const mockedListForRef = jest.fn().mockResolvedValue(checks);

github.getOctokit.mockReturnValue({
pulls: { listReviews: mockedListReviews, get: mockedGet },
checks: { listForRef: mockedListForRef },
});

const res = await gitLib.getAutoUpdateCandidate(prList);
expect(res).toBe(prList[0]);
});
test('Should return the first PR if it is all good', async () => {
// has 2 approvals, no request for change review
const reviews = {
Expand Down