Skip to content

Commit

Permalink
Augment watermark with label (#1173)
Browse files Browse the repository at this point in the history
* Augment watermark with label.

The label may contain special strings `{workflow}` or `{run}` as an easy
way to make updatable comments specific to the workflow or execution.

* Check for presence of a node title.

* Specify conflict between commentLabel and rmWatermark flags.

* Update bin/cml/comment/create.js

Co-authored-by: Casper da Costa-Luis <[email protected]>

* Update cli flag for comment label.

* Rename --label to --id

* Rename parameter to commentTag.

* Update src/cml.js

Co-authored-by: Helio Machado <[email protected]>

* Rename commentTag flag to watermarkTitle.

* Update bin/cml/comment/create.js

Co-authored-by: Casper da Costa-Luis <[email protected]>

* Update snapshots.

* Refactor watermark function.

* Fix test.

* Use markdown-escape to escape markdown control characters in comment watermark titles.

* Revert "Use markdown-escape to escape markdown control characters in comment watermark titles."

This reverts commit 1400685.

* Simplify escaping of markdown control characters.

It appears that github only escapes asterisks and underscores.

* Add more patterns to escape.

* Run replacement on title, not full watermark.

Co-authored-by: Casper da Costa-Luis <[email protected]>
Co-authored-by: Helio Machado <[email protected]>
Co-authored-by: Daniel Barnes <[email protected]>
  • Loading branch information
4 people authored Oct 29, 2022
1 parent 329ed42 commit 17a045e
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 21 deletions.
7 changes: 7 additions & 0 deletions bin/cml/comment/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ exports.options = kebabcaseKeys({
'Avoid watermark; CML needs a watermark to be able to distinguish CML comments from others',
hidden: true,
telemetryData: 'name'
},
watermarkTitle: {
type: 'string',
description:
'Hidden comment marker (used for targeting in subsequent `cml comment update`); "{workflow}" & "{run}" are auto-replaced',
default: '',
conflicts: ['rmWatermark']
}
});
exports.DOCSURL = DOCSURL;
6 changes: 4 additions & 2 deletions bin/cml/comment/create.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const { exec } = require('../../../src/utils');
describe('Comment integration tests', () => {
test('cml send-comment --help', async () => {
const output = await exec(`node ./bin/cml.js send-comment --help`);

expect(output).toMatchInlineSnapshot(`
"cml.js send-comment <markdown file>
Expand All @@ -30,7 +29,10 @@ describe('Comment integration tests', () => {
[string] [default: \\"https://asset.cml.dev\\"]
--publish-native, --native Uses driver's native capabilities to upload assets
instead of CML's storage; not available on GitHub
[boolean]"
[boolean]
--watermark-title Hidden comment marker (used for targeting in
subsequent \`cml comment update\`); \\"{workflow}\\" &
\\"{run}\\" are auto-replaced [string] [default: \\"\\"]"
`);
});
});
48 changes: 40 additions & 8 deletions src/cml.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const {

const { GITHUB_REPOSITORY, CI_PROJECT_URL, BITBUCKET_REPO_UUID } = process.env;

const WATERMARK_IMAGE = 'https://cml.dev/watermark.png';
const GIT_USER_NAME = 'Olivaw[bot]';
const GIT_USER_EMAIL = '[email protected]';
const GIT_REMOTE = 'origin';
Expand Down Expand Up @@ -164,26 +165,55 @@ class CML {
const triggerSha = await this.triggerSha();
const {
commitSha: inCommitSha = triggerSha,
rmWatermark,
update,
markdownFile,
pr,
publish,
publishUrl,
markdownFile,
report: testReport,
rmWatermark,
triggerFile,
update,
watch,
triggerFile
watermarkTitle
} = opts;

const drv = this.getDriver();

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

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

// Create the watermark.
const genWatermark = (opts = {}) => {
const { label = '', workflow, run } = opts;
// Replace {workflow} and {run} placeholders in label with actual values.
const lbl = label.replace('{workflow}', workflow).replace('{run}', run);

let title = `CML watermark ${lbl}`.trim();
// Github appears to escape underscores and asterisks in markdown content.
// Without escaping them, the watermark content in comments retrieved
// from github will not match the input.
const patterns = [
[/_/g, '\\_'], // underscore
[/\*/g, '\\*'], // asterisk
[/\[/g, '\\['], // opening square bracket
[/</g, '\\<'] // opening angle bracket
];
title = patterns.reduce(
(label, pattern) => label.replace(pattern[0], pattern[1]),
title
);
return `![](${WATERMARK_IMAGE} "${title}")`;
};
const watermark = rmWatermark
? ''
: '![CML watermark](https://raw.githubusercontent.com/iterative/cml/master/assets/watermark.svg)';
: genWatermark({
label: watermarkTitle,
workflow: drv.workflowId,
run: drv.runId
});

let userReport = testReport;
try {
Expand All @@ -195,15 +225,17 @@ class CML {
}

let report = `${userReport}\n\n${watermark}`;
const drv = this.getDriver();

const publishLocalFiles = async (tree) => {
const nodes = [];

visit(tree, ['definition', 'image', 'link'], (node) => nodes.push(node));

const isWatermark = (node) => {
return node.title && node.title.startsWith('CML watermark');
};
const visitor = async (node) => {
if (node.url && node.alt !== 'CML watermark') {
if (node.url && !isWatermark(node)) {
const absolutePath = path.resolve(
path.dirname(markdownFile),
node.url
Expand Down Expand Up @@ -264,7 +296,7 @@ class CML {
let comment;
const updatableComment = (comments) => {
return comments.reverse().find(({ body }) => {
return body.includes('watermark.svg');
return body.includes(watermark);
});
};

Expand Down
16 changes: 14 additions & 2 deletions src/drivers/bitbucket_cloud.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ const ProxyAgent = require('proxy-agent');

const { fetchUploadData, exec, gpuPresent, sleep } = require('../utils');

const { BITBUCKET_COMMIT, BITBUCKET_BRANCH, BITBUCKET_PIPELINE_UUID } =
process.env;
const {
BITBUCKET_COMMIT,
BITBUCKET_BRANCH,
BITBUCKET_PIPELINE_UUID,
BITBUCKET_BUILD_NUMBER
} = process.env;

class BitbucketCloud {
constructor(opts = {}) {
Expand Down Expand Up @@ -421,6 +425,14 @@ class BitbucketCloud {
return command;
}

get workflowId() {
return BITBUCKET_PIPELINE_UUID;
}

get runId() {
return BITBUCKET_BUILD_NUMBER;
}

get sha() {
return BITBUCKET_COMMIT;
}
Expand Down
19 changes: 14 additions & 5 deletions src/drivers/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ const CHECK_TITLE = 'CML Report';
process.env.RUNNER_ALLOW_RUNASROOT = 1;

const {
GITHUB_REPOSITORY,
GITHUB_SHA,
GITHUB_REF,
GITHUB_HEAD_REF,
CI,
GITHUB_EVENT_NAME,
GITHUB_HEAD_REF,
GITHUB_REF,
GITHUB_REPOSITORY,
GITHUB_RUN_ID,
GITHUB_SHA,
GITHUB_TOKEN,
CI,
GITHUB_WORKFLOW,
TPI_TASK
} = process.env;

Expand Down Expand Up @@ -705,6 +706,14 @@ class Github {
return command;
}

get workflowId() {
return GITHUB_WORKFLOW;
}

get runId() {
return GITHUB_RUN_ID;
}

warn(message) {
console.error(`::warning::${message}`);
}
Expand Down
11 changes: 10 additions & 1 deletion src/drivers/gitlab.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ const winston = require('winston');

const { fetchUploadData, download, gpuPresent } = require('../utils');

const { IN_DOCKER, CI_PIPELINE_ID } = process.env;
const { CI_JOB_ID, CI_PIPELINE_ID, IN_DOCKER } = process.env;

const API_VER = 'v4';
class Gitlab {
constructor(opts = {}) {
Expand Down Expand Up @@ -444,6 +445,14 @@ class Gitlab {
return command;
}

get workflowId() {
return CI_PIPELINE_ID;
}

get runId() {
return CI_JOB_ID;
}

get sha() {
return process.env.CI_COMMIT_SHA;
}
Expand Down
6 changes: 3 additions & 3 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,14 @@ const isProcRunning = async (opts) => {
};

const watermarkUri = ({ uri, type } = {}) => {
return uriParmam({ uri, param: 'cml', value: type });
return uriParam({ uri, param: 'cml', value: type });
};

const preventcacheUri = ({ uri } = {}) => {
return uriParmam({ uri, param: 'cache-bypass', value: uuid.v4() });
return uriParam({ uri, param: 'cache-bypass', value: uuid.v4() });
};

const uriParmam = (opts = {}) => {
const uriParam = (opts = {}) => {
const { uri, param, value } = opts;
const url = new URL(uri);
url.searchParams.set(param, value);
Expand Down

2 comments on commit 17a045e

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Comment

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Comment

Please sign in to comment.