From a54c31d7fa095754bfef525c0c8e5e5674c4b4b1 Mon Sep 17 00:00:00 2001 From: Peter Evans <18365890+peter-evans@users.noreply.github.com> Date: Tue, 25 Apr 2023 14:23:47 +0900 Subject: [PATCH] feat: allow nth as a config input (#170) * feat: allow `nth` as a config input (#164) * refactor: remove dup implementation for first/last * remove `;` for linter * feat: add `nth` input config --------- Co-authored-by: Peter Evans <18365890+peter-evans@users.noreply.github.com> * minor refactor and tests * update readme * ci test --------- Co-authored-by: Colin Kennedy --- .github/workflows/ci.yml | 10 +++ README.md | 13 +++ __test__/find.unit.test.ts | 172 +++++++++++++++++++++++++++++++++---- action.yml | 3 + dist/index.js | 45 ++++++---- src/find.ts | 46 ++++++---- src/main.ts | 3 +- 7 files changed, 239 insertions(+), 53 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5d8998..3fd22f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,6 +132,16 @@ jobs: - if: steps.fc8.outputs.comment-id != 620947762 run: exit 1 + - name: Find nth comment by body-includes + uses: ./ + id: fc9 + with: + issue-number: 1 + body-includes: comment 1 + nth: 2 + - if: steps.fc9.outputs.comment-id != 703343294 + run: exit 1 + package: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: [test] diff --git a/README.md b/README.md index 38e84aa..0e3084f 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,18 @@ The action will output the comment ID of the comment matching the search criteri direction: last ``` +### Find the nth comment containing the specified string + +```yml + - name: Find Comment + uses: peter-evans/find-comment@v2 + id: fc + with: + issue-number: 1 + body-includes: search string 1 + nth: 1 # second matching comment (0-indexed) +``` + ### Action inputs | Name | Description | Default | @@ -76,6 +88,7 @@ The action will output the comment ID of the comment matching the search criteri | `body-includes` | A string to search for in the body of comments. | | | `body-regex` | A regular expression to search for in the body of comments. | | | `direction` | Search direction, specified as `first` or `last` | `first` | +| `nth` | 0-indexed number, specifying which comment to return if multiple are found | 0 | #### Outputs diff --git a/__test__/find.unit.test.ts b/__test__/find.unit.test.ts index c2375c2..268464b 100644 --- a/__test__/find.unit.test.ts +++ b/__test__/find.unit.test.ts @@ -1,6 +1,6 @@ -import {findCommentPredicate} from '../src/find' +import {findCommentPredicate, findMatchingComment} from '../src/find' -describe('find comment tests', () => { +describe('findCommentPredicate tests', () => { test('find by bodyIncludes', async () => { expect( findCommentPredicate( @@ -11,7 +11,8 @@ describe('find comment tests', () => { commentAuthor: '', bodyIncludes: 'Kansas', bodyRegex: '', - direction: 'direction' + direction: 'direction', + nth: 0 }, { id: 1, @@ -31,7 +32,8 @@ describe('find comment tests', () => { commentAuthor: '', bodyIncludes: 'not-exist', bodyRegex: '', - direction: 'direction' + direction: 'direction', + nth: 0 }, { id: 1, @@ -53,7 +55,8 @@ describe('find comment tests', () => { commentAuthor: '', bodyIncludes: '', bodyRegex: '^.*Kansas.*$', - direction: 'direction' + direction: 'direction', + nth: 0 }, { id: 1, @@ -73,7 +76,8 @@ describe('find comment tests', () => { commentAuthor: '', bodyIncludes: '', bodyRegex: '^.*not-exist.*$', - direction: 'direction' + direction: 'direction', + nth: 0 }, { id: 1, @@ -95,7 +99,8 @@ describe('find comment tests', () => { commentAuthor: 'dorothy', bodyIncludes: '', bodyRegex: '', - direction: 'direction' + direction: 'direction', + nth: 0 }, { id: 1, @@ -115,7 +120,8 @@ describe('find comment tests', () => { commentAuthor: 'toto', bodyIncludes: '', bodyRegex: '', - direction: 'direction' + direction: 'direction', + nth: 0 }, { id: 1, @@ -137,7 +143,8 @@ describe('find comment tests', () => { commentAuthor: 'dorothy', bodyIncludes: 'Kansas', bodyRegex: '', - direction: 'direction' + direction: 'direction', + nth: 0 }, { id: 1, @@ -157,7 +164,8 @@ describe('find comment tests', () => { commentAuthor: 'dorothy', bodyIncludes: 'not-exist', bodyRegex: '', - direction: 'direction' + direction: 'direction', + nth: 0 }, { id: 1, @@ -177,7 +185,8 @@ describe('find comment tests', () => { commentAuthor: 'toto', bodyIncludes: 'Kansas', bodyRegex: '', - direction: 'direction' + direction: 'direction', + nth: 0 }, { id: 1, @@ -199,7 +208,8 @@ describe('find comment tests', () => { commentAuthor: 'dorothy', bodyIncludes: '', bodyRegex: '^.*Kansas.*$', - direction: 'direction' + direction: 'direction', + nth: 0 }, { id: 1, @@ -219,7 +229,8 @@ describe('find comment tests', () => { commentAuthor: 'dorothy', bodyIncludes: '', bodyRegex: '/^.*KaNsAs.*$/i', - direction: 'direction' + direction: 'direction', + nth: 0 }, { id: 1, @@ -239,7 +250,8 @@ describe('find comment tests', () => { commentAuthor: 'dorothy', bodyIncludes: '', bodyRegex: '^.*not-exist.*$', - direction: 'direction' + direction: 'direction', + nth: 0 }, { id: 1, @@ -259,7 +271,8 @@ describe('find comment tests', () => { commentAuthor: 'toto', bodyIncludes: '', bodyRegex: '^.*Kansas.*$', - direction: 'direction' + direction: 'direction', + nth: 0 }, { id: 1, @@ -281,7 +294,8 @@ describe('find comment tests', () => { commentAuthor: 'dorothy', bodyIncludes: 'feeling', bodyRegex: '^.*Kansas.*$', - direction: 'direction' + direction: 'direction', + nth: 0 }, { id: 1, @@ -293,3 +307,129 @@ describe('find comment tests', () => { ).toEqual(true) }) }) + +describe('findMatchingComment tests', () => { + // Note: Use `testComments.slice()` to avoid mutating the original array. + const testComments = [ + { + id: 1, + body: `Toto, I've a feeling we're not in Kansas anymore.`, + user: {login: 'dorothy'}, + created_at: '2020-01-01T00:00:00Z' + }, + { + id: 2, + body: `You've always had the power, my dear. You just had to learn it for yourself.`, + user: {login: 'glinda'}, + created_at: '2020-01-01T00:00:00Z' + }, + { + id: 3, + body: `I'll get you, my pretty, and your little dog too!`, + user: {login: 'wicked-witch'}, + created_at: '2020-01-01T00:00:00Z' + }, + { + id: 4, + body: `Toto, I've a feeling we're not in Kansas anymore.`, + user: {login: 'dorothy'}, + created_at: '2020-01-01T00:00:00Z' + }, + { + id: 5, + body: `I'll get you, my pretty, and your little dog too!`, + user: {login: 'wicked-witch'}, + created_at: '2020-01-01T00:00:00Z' + } + ] + + test('no comments', async () => { + expect( + findMatchingComment( + { + token: 'token', + repository: 'repository', + issueNumber: 1, + commentAuthor: '', + bodyIncludes: 'Kansas', + bodyRegex: '', + direction: 'first', + nth: 0 + }, + [] + ) + ).toEqual(undefined) + }) + + test('find with search direction first', async () => { + expect( + findMatchingComment( + { + token: 'token', + repository: 'repository', + issueNumber: 1, + commentAuthor: '', + bodyIncludes: 'Kansas', + bodyRegex: '', + direction: 'first', + nth: 0 + }, + testComments.slice() + )?.id + ).toEqual(1) + }) + + test('find with search direction last', async () => { + expect( + findMatchingComment( + { + token: 'token', + repository: 'repository', + issueNumber: 1, + commentAuthor: '', + bodyIncludes: 'Kansas', + bodyRegex: '', + direction: 'last', + nth: 0 + }, + testComments.slice() + )?.id + ).toEqual(4) + }) + + test('find nth with search direction first', async () => { + expect( + findMatchingComment( + { + token: 'token', + repository: 'repository', + issueNumber: 1, + commentAuthor: '', + bodyIncludes: 'Kansas', + bodyRegex: '', + direction: 'first', + nth: 1 + }, + testComments.slice() + )?.id + ).toEqual(4) + }) + + test('find nth with search direction last', async () => { + expect( + findMatchingComment( + { + token: 'token', + repository: 'repository', + issueNumber: 1, + commentAuthor: '', + bodyIncludes: 'Kansas', + bodyRegex: '', + direction: 'last', + nth: 1 + }, + testComments.slice() + )?.id + ).toEqual(1) + }) +}) diff --git a/action.yml b/action.yml index 5b0811b..e62a862 100644 --- a/action.yml +++ b/action.yml @@ -18,6 +18,9 @@ inputs: direction: description: 'Search direction, specified as `first` or `last`' default: first + nth: + description: '0-indexed number, specifying which comment to return if multiple are found' + default: 0 outputs: comment-id: description: 'The id of the matching comment found.' diff --git a/dist/index.js b/dist/index.js index eb13088..a9136f2 100644 --- a/dist/index.js +++ b/dist/index.js @@ -39,7 +39,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.findComment = exports.findCommentPredicate = void 0; +exports.findComment = exports.findMatchingComment = exports.findCommentPredicate = void 0; const github = __importStar(__nccwpck_require__(5438)); function stringToRegex(s) { const m = s.match(/^(.)(.*?)\1([gimsuy]*)$/); @@ -48,6 +48,17 @@ function stringToRegex(s) { else return new RegExp(s); } +function fetchComments(inputs) { + return __awaiter(this, void 0, void 0, function* () { + const octokit = github.getOctokit(inputs.token); + const [owner, repo] = inputs.repository.split('/'); + return yield octokit.paginate(octokit.rest.issues.listComments, { + owner: owner, + repo: repo, + issue_number: inputs.issueNumber + }); + }); +} function findCommentPredicate(inputs, comment) { return ((inputs.commentAuthor && comment.user ? comment.user.login === inputs.commentAuthor @@ -60,23 +71,22 @@ function findCommentPredicate(inputs, comment) { : true)); } exports.findCommentPredicate = findCommentPredicate; +function findMatchingComment(inputs, comments) { + if (inputs.direction == 'last') { + comments.reverse(); + } + const matchingComments = comments.filter(comment => findCommentPredicate(inputs, comment)); + const comment = matchingComments[inputs.nth]; + if (comment) { + return comment; + } + return undefined; +} +exports.findMatchingComment = findMatchingComment; function findComment(inputs) { return __awaiter(this, void 0, void 0, function* () { - const octokit = github.getOctokit(inputs.token); - const [owner, repo] = inputs.repository.split('/'); - const parameters = { - owner: owner, - repo: repo, - issue_number: inputs.issueNumber - }; - const comments = yield octokit.paginate(octokit.rest.issues.listComments, parameters); - if (inputs.direction == 'last') { - comments.reverse(); - } - const comment = comments.find(comment => findCommentPredicate(inputs, comment)); - if (comment) - return comment; - return undefined; + const comments = yield fetchComments(inputs); + return findMatchingComment(inputs, comments); }); } exports.findComment = findComment; @@ -140,7 +150,8 @@ function run() { commentAuthor: core.getInput('comment-author'), bodyIncludes: core.getInput('body-includes'), bodyRegex: core.getInput('body-regex'), - direction: core.getInput('direction') + direction: core.getInput('direction'), + nth: Number(core.getInput('nth')) }; core.debug(`Inputs: ${(0, util_1.inspect)(inputs)}`); const comment = yield (0, find_1.findComment)(inputs); diff --git a/src/find.ts b/src/find.ts index 6430b93..ec8b883 100644 --- a/src/find.ts +++ b/src/find.ts @@ -8,6 +8,7 @@ export interface Inputs { bodyIncludes: string bodyRegex: string direction: string + nth: number } export interface Comment { @@ -25,6 +26,16 @@ function stringToRegex(s: string): RegExp { else return new RegExp(s) } +async function fetchComments(inputs: Inputs): Promise { + const octokit = github.getOctokit(inputs.token) + const [owner, repo] = inputs.repository.split('/') + return await octokit.paginate(octokit.rest.issues.listComments, { + owner: owner, + repo: repo, + issue_number: inputs.issueNumber + }) +} + export function findCommentPredicate( inputs: Inputs, comment: Comment @@ -42,29 +53,26 @@ export function findCommentPredicate( ) } -export async function findComment( - inputs: Inputs -): Promise { - const octokit = github.getOctokit(inputs.token) - const [owner, repo] = inputs.repository.split('/') - - const parameters = { - owner: owner, - repo: repo, - issue_number: inputs.issueNumber - } - - const comments = await octokit.paginate( - octokit.rest.issues.listComments, - parameters - ) +export function findMatchingComment( + inputs: Inputs, + comments: Comment[] +): Comment | undefined { if (inputs.direction == 'last') { comments.reverse() } - const comment = comments.find(comment => + const matchingComments = comments.filter(comment => findCommentPredicate(inputs, comment) ) - if (comment) return comment - + const comment = matchingComments[inputs.nth] + if (comment) { + return comment + } return undefined } + +export async function findComment( + inputs: Inputs +): Promise { + const comments = await fetchComments(inputs) + return findMatchingComment(inputs, comments) +} diff --git a/src/main.ts b/src/main.ts index 71091e5..22bdea5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -16,7 +16,8 @@ async function run(): Promise { commentAuthor: core.getInput('comment-author'), bodyIncludes: core.getInput('body-includes'), bodyRegex: core.getInput('body-regex'), - direction: core.getInput('direction') + direction: core.getInput('direction'), + nth: Number(core.getInput('nth')) } core.debug(`Inputs: ${inspect(inputs)}`)