Skip to content

Commit

Permalink
Feature comment target (#1228)
Browse files Browse the repository at this point in the history
* Allow creation of issue comments in cml (#1202)

* Driver support for issue comments.

* Create or update issue comments.

* Add e2e issue comment creation tests for github and bitbucket drivers.

* Add gitlab issue comment e2e test.

* Add comment target flag and parsing (#1241)

* Add target cli option.

* Add comment target parsing.

* Add debug logging in comment target parsing.

Co-authored-by: DavidGOrtega <[email protected]>
Co-authored-by: Casper da Costa-Luis <[email protected]>
  • Loading branch information
3 people authored Dec 15, 2022
1 parent 03e009f commit 384cdb5
Show file tree
Hide file tree
Showing 13 changed files with 495 additions and 78 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,15 @@ jobs:
TEST_GITHUB_TOKEN: ${{ secrets.TEST_GITHUB_TOKEN }}
TEST_GITHUB_REPO: https://github.com/iterative/cml_qa_tests_dummy
TEST_GITHUB_SHA: 0cd16da26e35f8e5d57b2549a97e22618abf08f6
TEST_GITHUB_ISSUE: 1
TEST_GITLAB_TOKEN: ${{ secrets.TEST_GITLAB_TOKEN }}
TEST_GITLAB_REPO: https://gitlab.com/iterative.ai/cml_qa_tests_dummy
TEST_GITLAB_SHA: f8b8b49a253243830ef59a7f090eb887157b2b67
TEST_GITLAB_ISSUE: 1
TEST_BBCLOUD_TOKEN: ${{ secrets.TEST_BBCLOUD_TOKEN }}
TEST_BBCLOUD_REPO: https://bitbucket.org/iterative-ai/cml-qa-tests-dummy
TEST_BBCLOUD_SHA: b511535a89f76d3d311b1c15e3e712b15c0b94e3
TEST_BBCLOUD_ISSUE: 1
test-os:
needs: authorize
name: test-${{ matrix.system }}
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,7 @@ These are some example projects using CML.
- [CML with Tensorboard](https://github.com/iterative-test/cml-example-tensorboard)
- [CML with a small EC2 instance](https://github.com/iterative-test/cml-example-cloud)
:key:
- [CML with EC2 GPU](https://github.com/iterative-test/cml-example-cloud-gpu) :key:
- [CML with EC2 GPU](https://github.com/iterative-test/cml-example-cloud-gpu)
:key:

:key: needs a [PAT](#environment-variables).
15 changes: 12 additions & 3 deletions bin/cml/comment/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,25 @@ exports.builder = (yargs) =>
.options(exports.options);

exports.options = kebabcaseKeys({
target: {
type: 'string',
description:
'Comment type (`commit`, `pr`, `commit/f00bar`, `pr/42`, `issue/1337`),' +
'default is automatic (`pr` but fallback to `commit`).'
},
pr: {
type: 'boolean',
description:
'Post to an existing PR/MR associated with the specified commit'
'Post to an existing PR/MR associated with the specified commit',
conflicts: ['target', 'commitSha'],
hidden: true
},
commitSha: {
type: 'string',
alias: 'head-sha',
default: 'HEAD',
description: 'Commit SHA linked to this comment'
description: 'Commit SHA linked to this comment',
conflicts: ['target', 'pr'],
hidden: true
},
watch: {
type: 'boolean',
Expand Down
7 changes: 3 additions & 4 deletions bin/cml/comment/create.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@ describe('Comment integration tests', () => {
--help Show help [boolean]
Options:
--pr Post to an existing PR/MR associated with the
specified commit [boolean]
--commit-sha, --head-sha Commit SHA linked to this comment
[string] [default: \\"HEAD\\"]
--target Comment type (\`commit\`, \`pr\`, \`commit/f00bar\`,
\`pr/42\`, \`issue/1337\`),default is automatic (\`pr\`
but fallback to \`commit\`). [string]
--watch Watch for changes and automatically update the
comment [boolean]
--publish Upload any local images found in the Markdown
Expand Down
62 changes: 14 additions & 48 deletions src/cml.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const winston = require('winston');
const remark = require('remark');
const visit = require('unist-util-visit');

const { parseCommentTarget } = require('./commenttarget');
const Gitlab = require('./drivers/gitlab');
const Github = require('./drivers/github');
const BitbucketCloud = require('./drivers/bitbucket_cloud');
Expand Down Expand Up @@ -162,15 +163,15 @@ class CML {
}

async commentCreate(opts = {}) {
const triggerSha = await this.triggerSha();
const {
commitSha: inCommitSha = triggerSha,
commitSha,
markdownFile,
pr,
publish,
publishUrl,
report: testReport,
rmWatermark,
target: commentTarget = 'auto',
triggerFile,
update,
watch,
Expand All @@ -179,9 +180,6 @@ class CML {

const drv = this.getDriver();

const commitSha =
(await this.revParse({ ref: inCommitSha })) || inCommitSha;

if (rmWatermark && update)
throw new Error('watermarks are mandatory for updateable comments');

Expand Down Expand Up @@ -300,58 +298,26 @@ class CML {
});
};

const isBB = this.driver === BB;
if (pr || isBB) {
let commentUrl;

if (commitSha !== triggerSha)
winston.info(
`Looking for PR associated with --commit-sha="${inCommitSha}".\nSee https://cml.dev/doc/ref/send-comment.`
);

const longReport = `${commitSha.substr(0, 7)}\n\n${report}`;
const [commitPr = {}] = await drv.commitPrs({ commitSha });
const { url } = commitPr;

if (!url && !isBB)
throw new Error(`PR for commit sha "${inCommitSha}" not found`);

if (url) {
const [prNumber] = url.split('/').slice(-1);

if (update)
comment = updatableComment(await drv.prComments({ prNumber }));

if (update && comment) {
commentUrl = await drv.prCommentUpdate({
report: longReport,
id: comment.id,
prNumber
});
} else
commentUrl = await drv.prCommentCreate({
report: longReport,
prNumber
});

if (this.driver !== 'bitbucket') return commentUrl;
}
}
const target = await parseCommentTarget({
commitSha,
pr,
target: commentTarget,
drv
});

if (update) {
comment = updatableComment(await drv.commitComments({ commitSha }));
comment = updatableComment(await drv[target.target + 'Comments'](target));

if (comment)
return await drv.commentUpdate({
return await drv[target.target + 'CommentUpdate']({
report,
id: comment.id,
commitSha
...target
});
}

return await drv.commentCreate({
return await drv[target.target + 'CommentCreate']({
report,
commitSha
...target
});
}

Expand Down
75 changes: 75 additions & 0 deletions src/commenttarget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const winston = require('winston');

const SEPARATOR = '/';

async function parseCommentTarget(opts = {}) {
const { commitSha: commit, pr, target, drv } = opts;

let commentTarget = target;
// Handle legacy comment target flags.
if (commit) {
drv.warn(
`Deprecation warning: use --target="commit${SEPARATOR}<sha>" instead of --commit-sha=<sha>`
);
commentTarget = `commit${SEPARATOR}${commit}`;
}
if (pr) {
drv.warn('Deprecation warning: use --target=pr instead of --pr');
commentTarget = 'pr';
}
// Handle comment targets that are incomplete, e.g. 'pr' or 'commit'.
let prNumber;
let commitPr;
switch (commentTarget.toLowerCase()) {
case 'commit':
winston.debug(`Comment target "commit" mapped to "commit/${drv.sha}"`);
return { target: 'commit', commitSha: drv.sha };
case 'pr':
case 'auto':
// Determine PR id from forge env vars (if we're in a PR context).
prNumber = drv.pr;
if (prNumber) {
winston.debug(
`Comment target "${commentTarget}" mapped to "pr/${prNumber}"`
);
return { target: 'pr', prNumber: prNumber };
}
// Or fallback to determining PR by HEAD commit.
// TODO: handle issue with PR HEAD commit not matching source branch in github.
[commitPr = {}] = await drv.commitPrs({ commitSha: drv.sha });
if (commitPr.url) {
[prNumber] = commitPr.url.split('/').slice(-1);
winston.debug(
`Comment target "${commentTarget}" mapped to "pr/${prNumber}" based on commit "${drv.sha}"`
);
return { target: 'pr', prNumber };
}
// If target is 'auto', fallback to issuing commit comments.
if (commentTarget === 'auto') {
winston.debug(
`Comment target "${commentTarget}" mapped to "commit/${drv.sha}"`
);
return { target: 'commit', commitSha: drv.sha };
}
throw new Error(`PR for commit sha "${drv.sha}" not found`);
}
// Handle qualified comment targets, e.g. 'issue/id'.
const separatorPos = commentTarget.indexOf(SEPARATOR);
if (separatorPos === -1) {
throw new Error(`Failed to parse comment --target="${commentTarget}"`);
}
const targetType = commentTarget.slice(0, separatorPos);
const id = commentTarget.slice(separatorPos + 1);
switch (targetType.toLowerCase()) {
case 'commit':
return { target: targetType, commitSha: id };
case 'pr':
return { target: targetType, prNumber: id };
case 'issue':
return { target: targetType, issueId: id };
default:
throw new Error(`unsupported comment target "${commentTarget}"`);
}
}

module.exports = { parseCommentTarget };
Loading

0 comments on commit 384cdb5

Please sign in to comment.