Skip to content

Commit

Permalink
feat: migrate addOrUpdateIssueComment to issue-utils (#5217)
Browse files Browse the repository at this point in the history
This function was removed from gcf-utils v15 as it's not a core method
needed by each bot.
  • Loading branch information
chingor13 committed Sep 18, 2023
1 parent 96b25e8 commit b50f612
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 1 deletion.
3 changes: 2 additions & 1 deletion packages/issue-utils/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"extends": "./node_modules/gts"
"extends": "./node_modules/gts",
"root": true
}
104 changes: 104 additions & 0 deletions packages/issue-utils/src/issue-comments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// eslint-disable-next-line node/no-extraneous-import
import {Octokit} from '@octokit/rest';

/**
* It creates a comment string used for `addOrUpdateissuecomment`.
*/
export const getCommentMark = (installationId: number): string => {
return `<!-- probot comment [${installationId}]-->`;
};

export interface IssueComment {
owner: string;
repo: string;
issueNumber: number;
body: string;
htmlUrl: string;
}

interface AddOrUpdateIssueCommentOptions {
onlyUpdate?: boolean;
}

/**
* It creates a comment, or if the bot already created a comment, it
* updates the same comment.
*
* @param {Octokit} octokit - The Octokit instance.
* @param {string} owner - The owner of the issue.
* @param {string} repo - The name of the repository.
* @param {number} issueNumber - The number of the issue.
* @param {number} installationId - A unique number for identifying the issue
* comment.
* @param {string} commentBody - The body of the comment.
* @param {AddOrUpdateIssueCommentOptions} options
* @param {boolean} options.onlyUpdate - If set to true, it will only update an
* existing issue comment.
*/
export const addOrUpdateIssueComment = async (
octokit: Octokit,
owner: string,
repo: string,
issueNumber: number,
installationId: number,
commentBody: string,
options: AddOrUpdateIssueCommentOptions = {}
): Promise<IssueComment | null> => {
const commentMark = getCommentMark(installationId);
const listCommentsResponse = await octokit.issues.listComments({
owner: owner,
repo: repo,
per_page: 50, // I think 50 is enough, but I may be wrong.
issue_number: issueNumber,
});
for (const comment of listCommentsResponse.data) {
if (comment.body?.includes(commentMark)) {
// We found the existing comment, so updating it
const {data: updatedComment} = await octokit.issues.updateComment({
owner,
repo,
comment_id: comment.id,
body: `${commentMark}\n${commentBody}`,
});
return {
owner,
repo,
issueNumber,
body: updatedComment.body || '',
htmlUrl: updatedComment.html_url,
};
}
}

if (options.onlyUpdate) {
return null;
}

const {data: newComment} = await octokit.issues.createComment({
owner: owner,
repo: repo,
issue_number: issueNumber,
body: `${commentMark}\n${commentBody}`,
});
return {
owner,
repo,
issueNumber,
body: newComment.body || '',
htmlUrl: newComment.html_url,
};
};
1 change: 1 addition & 0 deletions packages/issue-utils/src/issue-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// eslint-disable-next-line node/no-extraneous-import
import {Octokit} from '@octokit/rest';
import {GCFLogger, logger as defaultLogger} from 'gcf-utils';
export {addOrUpdateIssueComment} from './issue-comments';

export interface Issue {
owner: string;
Expand Down
134 changes: 134 additions & 0 deletions packages/issue-utils/test/issue-comments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {describe, it} from 'mocha';
import nock from 'nock';
// eslint-disable-next-line node/no-extraneous-import
import {Octokit} from '@octokit/rest';
import {addOrUpdateIssueComment} from '../src/issue-comments';
import assert from 'assert';

nock.disableNetConnect();
const octokit = new Octokit({auth: '123'});

const NEW_COMMENT_BODY = 'This is the new comment body';
const EXPECTED_NEW_COMMENT_BODY = `<!-- probot comment [2345]-->\n${NEW_COMMENT_BODY}`;

describe('addOrUpdateIssueComment', () => {
it('creates a new issue comment', async () => {
const scopes = nock('https://api.github.com/')
.get('/repos/test-owner/test-repo/issues/1234/comments?per_page=50')
.reply(200, [
{
id: 1,
body: 'irrelevant comment',
},
])
.post('/repos/test-owner/test-repo/issues/1234/comments', request => {
assert.strictEqual(request.body, EXPECTED_NEW_COMMENT_BODY);
return true;
})
.reply(201, {
id: 2,
body: EXPECTED_NEW_COMMENT_BODY,
html_url:
'https://github.com/test-owner/test-repo/issues/1234#issuecomment-2',
});
const newComment = await addOrUpdateIssueComment(
octokit,
'test-owner',
'test-repo',
1234,
2345,
NEW_COMMENT_BODY
);
assert.ok(newComment);
assert.strictEqual(newComment.owner, 'test-owner');
assert.strictEqual(newComment.repo, 'test-repo');
assert.strictEqual(newComment.issueNumber, 1234);
assert.strictEqual(
newComment.htmlUrl,
'https://github.com/test-owner/test-repo/issues/1234#issuecomment-2'
);
assert.strictEqual(newComment.body, EXPECTED_NEW_COMMENT_BODY);
scopes.done();
});

it('updates an existing comment', async () => {
const scopes = nock('https://api.github.com/')
.get('/repos/test-owner/test-repo/issues/1234/comments?per_page=50')
.reply(200, [
{
id: 1,
body: 'irrelevant comment',
},
{
id: 2,
body: '<!-- probot comment [2345]-->\nPrevious comment',
},
])
.patch('/repos/test-owner/test-repo/issues/comments/2', request => {
assert.strictEqual(request.body, EXPECTED_NEW_COMMENT_BODY);
return true;
})
.reply(200, {
id: 2,
body: EXPECTED_NEW_COMMENT_BODY,
html_url:
'https://github.com/test-owner/test-repo/issues/1234#issuecomment-2',
});
const updatedComment = await addOrUpdateIssueComment(
octokit,
'test-owner',
'test-repo',
1234,
2345,
NEW_COMMENT_BODY
);
assert.ok(updatedComment);
assert.strictEqual(updatedComment.owner, 'test-owner');
assert.strictEqual(updatedComment.repo, 'test-repo');
assert.strictEqual(updatedComment.issueNumber, 1234);
assert.strictEqual(
updatedComment.htmlUrl,
'https://github.com/test-owner/test-repo/issues/1234#issuecomment-2'
);
assert.strictEqual(updatedComment.body, EXPECTED_NEW_COMMENT_BODY);
scopes.done();
});

it('skips creating a comment for onlyUpdate', async () => {
const scopes = nock('https://api.github.com/')
.get('/repos/test-owner/test-repo/issues/1234/comments?per_page=50')
.reply(200, [
{
id: 1,
body: 'irrelevant comment',
},
]);
const newComment = await addOrUpdateIssueComment(
octokit,
'test-owner',
'test-repo',
1234,
2345,
NEW_COMMENT_BODY,
{
onlyUpdate: true,
}
);
assert.strictEqual(newComment, null);
scopes.done();
});
});

0 comments on commit b50f612

Please sign in to comment.