testbeats 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/package.json +65 -0
- package/src/beats/index.js +74 -0
- package/src/cli.js +23 -0
- package/src/commands/publish.js +54 -0
- package/src/extensions/ci-info.js +134 -0
- package/src/extensions/custom.js +29 -0
- package/src/extensions/hyperlinks.js +60 -0
- package/src/extensions/index.js +63 -0
- package/src/extensions/mentions.js +112 -0
- package/src/extensions/metadata.js +89 -0
- package/src/extensions/percy-analysis.js +203 -0
- package/src/extensions/quick-chart-test-summary.js +82 -0
- package/src/extensions/report-portal-analysis.js +95 -0
- package/src/extensions/report-portal-history.js +105 -0
- package/src/helpers/ci.js +62 -0
- package/src/helpers/constants.js +45 -0
- package/src/helpers/extension.helper.js +107 -0
- package/src/helpers/helper.js +102 -0
- package/src/helpers/metadata.helper.js +115 -0
- package/src/helpers/percy.js +60 -0
- package/src/helpers/performance.js +141 -0
- package/src/helpers/report-portal.js +47 -0
- package/src/index.d.ts +243 -0
- package/src/index.js +14 -0
- package/src/targets/chat.js +242 -0
- package/src/targets/custom.js +28 -0
- package/src/targets/delay.js +19 -0
- package/src/targets/index.js +39 -0
- package/src/targets/influx.js +208 -0
- package/src/targets/slack.js +266 -0
- package/src/targets/teams.js +304 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const ENV = process.env;
|
|
2
|
+
|
|
3
|
+
function getCIInformation() {
|
|
4
|
+
if (ENV.GITHUB_ACTIONS) {
|
|
5
|
+
return getGitHubActionsInformation();
|
|
6
|
+
}
|
|
7
|
+
if (ENV.JENKINS_URL) {
|
|
8
|
+
return getJenkinsInformation();
|
|
9
|
+
}
|
|
10
|
+
if (ENV.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI) {
|
|
11
|
+
return getAzureDevOpsInformation();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getGitHubActionsInformation() {
|
|
16
|
+
return {
|
|
17
|
+
ci: 'GITHUB_ACTIONS',
|
|
18
|
+
git: 'GITHUB',
|
|
19
|
+
repository_url: ENV.GITHUB_SERVER_URL + '/' + ENV.GITHUB_REPOSITORY,
|
|
20
|
+
repository_name: ENV.GITHUB_REPOSITORY,
|
|
21
|
+
repository_ref: ENV.GITHUB_REF,
|
|
22
|
+
repository_commit_sha: ENV.GITHUB_SHA,
|
|
23
|
+
build_url: ENV.GITHUB_SERVER_URL + '/' + ENV.GITHUB_REPOSITORY + '/actions/runs/' + ENV.GITHUB_RUN_ID,
|
|
24
|
+
build_number: ENV.GITHUB_RUN_NUMBER,
|
|
25
|
+
build_name: ENV.GITHUB_WORKFLOW,
|
|
26
|
+
user: ENV.GITHUB_ACTOR,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getAzureDevOpsInformation() {
|
|
31
|
+
return {
|
|
32
|
+
ci: 'AZURE_DEVOPS_PIPELINES',
|
|
33
|
+
git: 'AZURE_DEVOPS_REPOS',
|
|
34
|
+
repository_url: ENV.BUILD_REPOSITORY_URI,
|
|
35
|
+
repository_name: ENV.BUILD_REPOSITORY_NAME,
|
|
36
|
+
repository_ref: ENV.BUILD_SOURCEBRANCH,
|
|
37
|
+
repository_commit_sha: ENV.BUILD_SOURCEVERSION,
|
|
38
|
+
build_url: ENV.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI + ENV.SYSTEM_TEAMPROJECT + '/_build/results?buildId=' + ENV.BUILD_BUILDID,
|
|
39
|
+
build_number: ENV.BUILD_BUILDNUMBER,
|
|
40
|
+
build_name: ENV.BUILD_DEFINITIONNAME,
|
|
41
|
+
user: ENV.BUILD_REQUESTEDFOR
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getJenkinsInformation() {
|
|
46
|
+
return {
|
|
47
|
+
ci: 'JENKINS',
|
|
48
|
+
git: '',
|
|
49
|
+
repository_url: ENV.GIT_URL || ENV.GITHUB_URL || ENV.BITBUCKET_URL,
|
|
50
|
+
repository_name: ENV.JOB_NAME,
|
|
51
|
+
repository_ref: ENV.BRANCH || ENV.BRANCH_NAME,
|
|
52
|
+
repository_commit_sha: ENV.GIT_COMMIT || ENV.GIT_COMMIT_SHA || ENV.GITHUB_SHA || ENV.BITBUCKET_COMMIT,
|
|
53
|
+
build_url: ENV.BUILD_URL,
|
|
54
|
+
build_number: ENV.BUILD_NUMBER,
|
|
55
|
+
build_name: ENV.JOB_NAME,
|
|
56
|
+
user: ENV.USER || ENV.USERNAME
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
getCIInformation
|
|
62
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const STATUS = Object.freeze({
|
|
2
|
+
PASS: 'pass',
|
|
3
|
+
FAIL: 'fail',
|
|
4
|
+
PASS_OR_FAIL: 'passOrfail'
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
const HOOK = Object.freeze({
|
|
8
|
+
START: 'start',
|
|
9
|
+
AFTER_SUMMARY: 'after-summary',
|
|
10
|
+
END: 'end',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const TARGET = Object.freeze({
|
|
14
|
+
SLACK: 'slack',
|
|
15
|
+
TEAMS: 'teams',
|
|
16
|
+
CHAT: 'chat',
|
|
17
|
+
CUSTOM: 'custom',
|
|
18
|
+
DELAY: 'delay',
|
|
19
|
+
INFLUX: 'influx',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const EXTENSION = Object.freeze({
|
|
23
|
+
HYPERLINKS: 'hyperlinks',
|
|
24
|
+
MENTIONS: 'mentions',
|
|
25
|
+
REPORT_PORTAL_ANALYSIS: 'report-portal-analysis',
|
|
26
|
+
REPORT_PORTAL_HISTORY: 'report-portal-history',
|
|
27
|
+
QUICK_CHART_TEST_SUMMARY: 'quick-chart-test-summary',
|
|
28
|
+
PERCY_ANALYSIS: 'percy-analysis',
|
|
29
|
+
CUSTOM: 'custom',
|
|
30
|
+
METADATA: 'metadata',
|
|
31
|
+
CI_INFO: 'ci-info',
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const URLS = Object.freeze({
|
|
35
|
+
PERCY: 'https://percy.io',
|
|
36
|
+
QUICK_CHART: 'https://quickchart.io'
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
module.exports = Object.freeze({
|
|
40
|
+
STATUS,
|
|
41
|
+
HOOK,
|
|
42
|
+
TARGET,
|
|
43
|
+
EXTENSION,
|
|
44
|
+
URLS
|
|
45
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add Slack Extension function.
|
|
3
|
+
*
|
|
4
|
+
* @param {object} param0 - the payload object
|
|
5
|
+
* @param {object} param0.payload - the payload object
|
|
6
|
+
* @param {import("..").Extension} param0.extension - the extension to add
|
|
7
|
+
* @param {string} param0.text - the text to include
|
|
8
|
+
* @return {void}
|
|
9
|
+
*/
|
|
10
|
+
function addSlackExtension({ payload, extension, text }) {
|
|
11
|
+
if (extension.inputs.separator) {
|
|
12
|
+
payload.blocks.push({
|
|
13
|
+
"type": "divider"
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
let updated_text = text;
|
|
17
|
+
if (extension.inputs.title) {
|
|
18
|
+
const title = extension.inputs.title_link ? `<${extension.inputs.title_link}|${extension.inputs.title}>` : extension.inputs.title;
|
|
19
|
+
updated_text = `*${title}*\n\n${text}`;
|
|
20
|
+
}
|
|
21
|
+
if (extension.inputs.block_type === 'context') {
|
|
22
|
+
payload.blocks.push({
|
|
23
|
+
"type": "context",
|
|
24
|
+
"elements": [
|
|
25
|
+
{
|
|
26
|
+
"type": "mrkdwn",
|
|
27
|
+
"text": updated_text
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
});
|
|
31
|
+
} else {
|
|
32
|
+
payload.blocks.push({
|
|
33
|
+
"type": "section",
|
|
34
|
+
"text": {
|
|
35
|
+
"type": "mrkdwn",
|
|
36
|
+
"text": updated_text
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Add Teams Extension function.
|
|
44
|
+
*
|
|
45
|
+
* @param {object} param0 - the payload object
|
|
46
|
+
* @param {object} param0.payload - the payload object
|
|
47
|
+
* @param {import("..").Extension} param0.extension - the extension to add
|
|
48
|
+
* @param {string} param0.text - the text to include
|
|
49
|
+
* @return {void}
|
|
50
|
+
*/
|
|
51
|
+
function addTeamsExtension({ payload, extension, text }) {
|
|
52
|
+
if (extension.inputs.title) {
|
|
53
|
+
const title = extension.inputs.title_link ? `[${extension.inputs.title}](${extension.inputs.title_link})` : extension.inputs.title
|
|
54
|
+
payload.body.push({
|
|
55
|
+
"type": "TextBlock",
|
|
56
|
+
"text": title,
|
|
57
|
+
"isSubtle": true,
|
|
58
|
+
"weight": "bolder",
|
|
59
|
+
"separator": extension.inputs.separator,
|
|
60
|
+
"wrap": true
|
|
61
|
+
});
|
|
62
|
+
payload.body.push({
|
|
63
|
+
"type": "TextBlock",
|
|
64
|
+
"text": text,
|
|
65
|
+
"wrap": true
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
payload.body.push({
|
|
69
|
+
"type": "TextBlock",
|
|
70
|
+
"text": text,
|
|
71
|
+
"wrap": true,
|
|
72
|
+
"separator": extension.inputs.separator
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Add Chat Extension function.
|
|
79
|
+
*
|
|
80
|
+
* @param {object} param0 - the payload object
|
|
81
|
+
* @param {object} param0.payload - the payload object
|
|
82
|
+
* @param {import("..").Extension} param0.extension - the extension to add
|
|
83
|
+
* @param {string} param0.text - the text to include
|
|
84
|
+
* @return {void}
|
|
85
|
+
*/
|
|
86
|
+
function addChatExtension({ payload, extension, text }) {
|
|
87
|
+
let updated_text = text;
|
|
88
|
+
if (extension.inputs.title) {
|
|
89
|
+
const title = extension.inputs.title_link ? `<a href="${extension.inputs.title_link}">${extension.inputs.title}</a>` : extension.inputs.title;
|
|
90
|
+
updated_text = `<b>${title}</b><br><br>${text}`;
|
|
91
|
+
}
|
|
92
|
+
payload.sections.push({
|
|
93
|
+
"widgets": [
|
|
94
|
+
{
|
|
95
|
+
"textParagraph": {
|
|
96
|
+
"text": updated_text
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
addSlackExtension,
|
|
105
|
+
addTeamsExtension,
|
|
106
|
+
addChatExtension
|
|
107
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const pretty_ms = require('pretty-ms');
|
|
2
|
+
|
|
3
|
+
const DATA_REF_PATTERN = /(\{[^\}]+\})/g;
|
|
4
|
+
const ALLOWED_CONDITIONS = new Set(['pass', 'fail', 'passorfail']);
|
|
5
|
+
const GENERIC_CONDITIONS = new Set(['always', 'never']);
|
|
6
|
+
|
|
7
|
+
function getPercentage(x, y) {
|
|
8
|
+
if (y > 0) {
|
|
9
|
+
return Math.floor((x / y) * 100);
|
|
10
|
+
}
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function processText(raw) {
|
|
15
|
+
const dataRefMatches = raw.match(DATA_REF_PATTERN);
|
|
16
|
+
if (dataRefMatches) {
|
|
17
|
+
const values = [];
|
|
18
|
+
for (let i = 0; i < dataRefMatches.length; i++) {
|
|
19
|
+
const dataRefMatch = dataRefMatches[i];
|
|
20
|
+
const content = dataRefMatch.slice(1, -1);
|
|
21
|
+
if (process.env[content]) {
|
|
22
|
+
values.push(process.env[content]);
|
|
23
|
+
} else {
|
|
24
|
+
values.push(content);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (let i = 0; i < dataRefMatches.length; i++) {
|
|
28
|
+
raw = raw.replace(dataRefMatches[i], values[i]);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return raw;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @returns {import('../index').PublishConfig }
|
|
36
|
+
*/
|
|
37
|
+
function processData(data) {
|
|
38
|
+
if (typeof data === 'string') {
|
|
39
|
+
return processText(data);
|
|
40
|
+
}
|
|
41
|
+
if (typeof data === 'object') {
|
|
42
|
+
for (const prop in data) {
|
|
43
|
+
data[prop] = processData(data[prop]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return data;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function truncate(text, length) {
|
|
50
|
+
if (text && text.length > length) {
|
|
51
|
+
return text.slice(0, length) + "...";
|
|
52
|
+
} else {
|
|
53
|
+
return text;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getPrettyDuration(ms, format) {
|
|
58
|
+
return pretty_ms(parseInt(ms), { [format]: true, secondsDecimalDigits: 0 })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getTitleText({ result, target }) {
|
|
62
|
+
const title = target.inputs.title ? target.inputs.title : result.name;
|
|
63
|
+
if (target.inputs.title_suffix) {
|
|
64
|
+
return `${title} ${target.inputs.title_suffix}`;
|
|
65
|
+
}
|
|
66
|
+
return `${title}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getResultText({ result }) {
|
|
70
|
+
const percentage = getPercentage(result.passed, result.total);
|
|
71
|
+
return `${result.passed} / ${result.total} Passed (${percentage}%)`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
*
|
|
76
|
+
* @param {object} param0
|
|
77
|
+
* @param {string | Function} param0.condition
|
|
78
|
+
*/
|
|
79
|
+
async function checkCondition({ condition, result, target, extension }) {
|
|
80
|
+
if (typeof condition === 'function') {
|
|
81
|
+
return await condition({ target, result, extension });
|
|
82
|
+
} else {
|
|
83
|
+
const lower_condition = condition.toLowerCase();
|
|
84
|
+
if (ALLOWED_CONDITIONS.has(lower_condition)) {
|
|
85
|
+
return lower_condition.includes(result.status.toLowerCase());
|
|
86
|
+
} else if (GENERIC_CONDITIONS.has(lower_condition)) {
|
|
87
|
+
return lower_condition === 'always';
|
|
88
|
+
} else {
|
|
89
|
+
return eval(condition);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = {
|
|
95
|
+
getPercentage,
|
|
96
|
+
processData,
|
|
97
|
+
truncate,
|
|
98
|
+
getPrettyDuration,
|
|
99
|
+
getTitleText,
|
|
100
|
+
getResultText,
|
|
101
|
+
checkCondition,
|
|
102
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const { checkCondition } = require('./helper')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Asynchronously generates metadata text for slack.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} param0 - the payload object
|
|
7
|
+
* @param {Object} param0.elements - The elements to generate metadata text from
|
|
8
|
+
* @param {import('..').Target} param0.target - The result object
|
|
9
|
+
* @param {import('..').Extension} param0.extension - The result object
|
|
10
|
+
* @param {Object} param0.result - The result object
|
|
11
|
+
* @param {string} param0.default_condition - The default condition object
|
|
12
|
+
* @return {string} The generated metadata text
|
|
13
|
+
*/
|
|
14
|
+
async function getSlackMetaDataText({ elements, target, extension, result, default_condition }) {
|
|
15
|
+
const items = [];
|
|
16
|
+
for (const element of elements) {
|
|
17
|
+
if (await is_valid({ element, result, default_condition })) {
|
|
18
|
+
if (element.type === 'hyperlink') {
|
|
19
|
+
const url = await get_url({ url: element.value, target, result, extension });
|
|
20
|
+
if (element.label) {
|
|
21
|
+
items.push(`*${element.label}:* <${url}|${element.key}>`);
|
|
22
|
+
} else {
|
|
23
|
+
items.push(`<${url}|${element.key}>`);
|
|
24
|
+
}
|
|
25
|
+
} else if (element.key) {
|
|
26
|
+
items.push(`*${element.key}:* ${element.value}`);
|
|
27
|
+
} else {
|
|
28
|
+
items.push(element.value);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return items.join(' | ');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Asynchronously generates metadata text for teams.
|
|
37
|
+
*
|
|
38
|
+
* @param {object} param0 - the payload object
|
|
39
|
+
* @param {Object} param0.elements - The elements to generate metadata text from
|
|
40
|
+
* @param {import('..').Target} param0.target - The result object
|
|
41
|
+
* @param {import('..').Extension} param0.extension - The result object
|
|
42
|
+
* @param {Object} param0.result - The result object
|
|
43
|
+
* @param {string} param0.default_condition - The default condition object
|
|
44
|
+
* @return {string} The generated metadata text
|
|
45
|
+
*/
|
|
46
|
+
async function getTeamsMetaDataText({ elements, target, extension, result, default_condition }) {
|
|
47
|
+
const items = [];
|
|
48
|
+
for (const element of elements) {
|
|
49
|
+
if (await is_valid({ element, result, default_condition })) {
|
|
50
|
+
if (element.type === 'hyperlink') {
|
|
51
|
+
const url = await get_url({ url: element.value, target, result, extension });
|
|
52
|
+
if (element.label) {
|
|
53
|
+
items.push(`**${element.label}:** [${element.key}](${url})`);
|
|
54
|
+
} else {
|
|
55
|
+
items.push(`[${element.key}](${url})`);
|
|
56
|
+
}
|
|
57
|
+
} else if (element.key) {
|
|
58
|
+
items.push(`**${element.key}:** ${element.value}`);
|
|
59
|
+
} else {
|
|
60
|
+
items.push(element.value);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return items.join(' | ');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Asynchronously generates metadata text for chat.
|
|
69
|
+
*
|
|
70
|
+
* @param {object} param0 - the payload object
|
|
71
|
+
* @param {Object} param0.elements - The elements to generate metadata text from
|
|
72
|
+
* @param {import('..').Target} param0.target - The result object
|
|
73
|
+
* @param {import('..').Extension} param0.extension - The result object
|
|
74
|
+
* @param {Object} param0.result - The result object
|
|
75
|
+
* @param {string} param0.default_condition - The default condition object
|
|
76
|
+
* @return {string} The generated metadata text
|
|
77
|
+
*/
|
|
78
|
+
async function getChatMetaDataText({ elements, target, extension, result, default_condition }) {
|
|
79
|
+
const items = [];
|
|
80
|
+
for (const element of elements) {
|
|
81
|
+
if (await is_valid({ element, result, default_condition })) {
|
|
82
|
+
if (element.type === 'hyperlink') {
|
|
83
|
+
const url = await get_url({ url: element.value, target, result, extension });
|
|
84
|
+
if (element.label) {
|
|
85
|
+
items.push(`<b>${element.label}:</b> <a href="${url}">${element.key}</a>`);
|
|
86
|
+
} else {
|
|
87
|
+
items.push(`<a href="${url}">${element.key}</a>`);
|
|
88
|
+
}
|
|
89
|
+
} else if (element.key) {
|
|
90
|
+
items.push(`<b>${element.key}:</b> ${element.value}`);
|
|
91
|
+
} else {
|
|
92
|
+
items.push(element.value);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return items.join(' | ');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function is_valid({ element, result, default_condition }) {
|
|
100
|
+
const condition = element.condition || default_condition;
|
|
101
|
+
return checkCondition({ condition, result });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function get_url({ url, target, extension, result}) {
|
|
105
|
+
if (typeof url === 'function') {
|
|
106
|
+
return url({target, extension, result});
|
|
107
|
+
}
|
|
108
|
+
return url;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = {
|
|
112
|
+
getSlackMetaDataText,
|
|
113
|
+
getTeamsMetaDataText,
|
|
114
|
+
getChatMetaDataText
|
|
115
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const request = require('phin-retry');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {import('../index').PercyAnalysisInputs} inputs
|
|
5
|
+
*/
|
|
6
|
+
async function getProjectByName(inputs) {
|
|
7
|
+
return request.get({
|
|
8
|
+
url: `${inputs.url}/api/v1/projects`,
|
|
9
|
+
headers: {
|
|
10
|
+
'Authorization': `Token ${inputs.token}`
|
|
11
|
+
},
|
|
12
|
+
form: {
|
|
13
|
+
'project_slug': inputs.project_name
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {import('../index').PercyAnalysisInputs} inputs
|
|
20
|
+
*/
|
|
21
|
+
async function getLastBuild(inputs) {
|
|
22
|
+
return request.get({
|
|
23
|
+
url: `${inputs.url}/api/v1/builds?project_id=${inputs.project_id}&page[limit]=1`,
|
|
24
|
+
headers: {
|
|
25
|
+
'Authorization': `Token ${inputs.token}`
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {import('../index').PercyAnalysisInputs} inputs
|
|
32
|
+
*/
|
|
33
|
+
async function getBuild(inputs) {
|
|
34
|
+
return request.get({
|
|
35
|
+
url: `${inputs.url}/api/v1/builds/${inputs.build_id}`,
|
|
36
|
+
headers: {
|
|
37
|
+
'Authorization': `Token ${inputs.token}`
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {import('../index').PercyAnalysisInputs} inputs
|
|
44
|
+
*/
|
|
45
|
+
async function getRemovedSnapshots(inputs) {
|
|
46
|
+
return request.get({
|
|
47
|
+
url: `${inputs.url}/api/v1/builds/${inputs.build_id}/removed-snapshots`,
|
|
48
|
+
headers: {
|
|
49
|
+
'Authorization': `Token ${inputs.token}`
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
module.exports = {
|
|
56
|
+
getProjectByName,
|
|
57
|
+
getLastBuild,
|
|
58
|
+
getBuild,
|
|
59
|
+
getRemovedSnapshots
|
|
60
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const Metric = require("performance-results-parser/src/models/Metric");
|
|
2
|
+
const { checkCondition } = require("./helper");
|
|
3
|
+
const pretty_ms = require('pretty-ms');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {object} param0
|
|
7
|
+
* @param {Metric[]} param0.metrics
|
|
8
|
+
* @param {object} param0.result
|
|
9
|
+
* @param {object} param0.target
|
|
10
|
+
*/
|
|
11
|
+
async function getValidMetrics({ metrics, result, target }) {
|
|
12
|
+
if (target.inputs.metrics && target.inputs.metrics.length > 0) {
|
|
13
|
+
const valid_metrics = [];
|
|
14
|
+
for (let i = 0; i < metrics.length; i++) {
|
|
15
|
+
const metric = metrics[i];
|
|
16
|
+
for (let j = 0; j < target.inputs.metrics.length; j++) {
|
|
17
|
+
const metric_config = target.inputs.metrics[j];
|
|
18
|
+
if (metric.name === metric_config.name) {
|
|
19
|
+
const include = await checkCondition({ condition: metric_config.condition || 'always', result, target });
|
|
20
|
+
if (include) valid_metrics.push(metric);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return valid_metrics;
|
|
25
|
+
}
|
|
26
|
+
return metrics;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {Metric} metric
|
|
31
|
+
* @param {string[]} fields
|
|
32
|
+
*/
|
|
33
|
+
function getCounterMetricFieldValue(metric, fields) {
|
|
34
|
+
let value = '';
|
|
35
|
+
if (fields.includes('sum')) {
|
|
36
|
+
const sum_failure = metric.failures.find(_failure => _failure.field === 'sum');
|
|
37
|
+
if (sum_failure) {
|
|
38
|
+
const emoji = getEmoji(sum_failure.difference);
|
|
39
|
+
value = `${emoji} ${metric['sum']} (${getDifferenceSymbol(sum_failure.difference)}${sum_failure.difference}) `
|
|
40
|
+
} else {
|
|
41
|
+
value = `${metric['sum']} `;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (fields.includes('rate')) {
|
|
45
|
+
let metric_unit = metric.unit.startsWith('/') ? metric.unit : ` ${metric.unit}`;
|
|
46
|
+
const rate_failure = metric.failures.find(_failure => _failure.field === 'rate');
|
|
47
|
+
if (rate_failure) {
|
|
48
|
+
const emoji = getEmoji(rate_failure.difference);
|
|
49
|
+
value += `${emoji} ${metric['rate']}${metric_unit} (${getDifferenceSymbol(rate_failure.difference)}${rate_failure.difference})`
|
|
50
|
+
} else {
|
|
51
|
+
value += `${metric['rate']}${metric_unit}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {Metric} metric
|
|
59
|
+
*/
|
|
60
|
+
function getTrendMetricFieldValue(metric, field) {
|
|
61
|
+
const failure = metric.failures.find(_failure => _failure.field === field);
|
|
62
|
+
if (failure) {
|
|
63
|
+
const emoji = getEmoji(failure.difference);
|
|
64
|
+
return `${emoji} ${field}=${pretty_ms(metric[field])} (${getDifferenceSymbol(failure.difference)}${pretty_ms(failure.difference)})`
|
|
65
|
+
}
|
|
66
|
+
return `${field}=${pretty_ms(metric[field])}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {Metric} metric
|
|
71
|
+
*/
|
|
72
|
+
function getRateMetricFieldValue(metric) {
|
|
73
|
+
const failure = metric.failures.find(_failure => _failure.field === 'rate');
|
|
74
|
+
if (failure) {
|
|
75
|
+
const emoji = getEmoji(failure.difference);
|
|
76
|
+
return `${emoji} ${metric['rate']} ${metric.unit} (${getDifferenceSymbol(failure.difference)}${failure.difference})`;
|
|
77
|
+
}
|
|
78
|
+
return `${metric['rate']} ${metric.unit}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getEmoji(value) {
|
|
82
|
+
return value > 0 ? '🔺' : '🔻';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getDifferenceSymbol(value) {
|
|
86
|
+
return value > 0 ? `+` : '';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
*
|
|
91
|
+
* @param {object} param0
|
|
92
|
+
* @param {Metric} param0.metric
|
|
93
|
+
*/
|
|
94
|
+
function getDisplayFields({ metric, target }) {
|
|
95
|
+
let fields = [];
|
|
96
|
+
if (target.inputs.metrics) {
|
|
97
|
+
const metric_config = target.inputs.metrics.find(_metric => _metric.name === metric.name);
|
|
98
|
+
if (metric_config) {
|
|
99
|
+
fields = metric_config.fields;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (fields && fields.length > 0) {
|
|
103
|
+
return fields;
|
|
104
|
+
} else {
|
|
105
|
+
switch (metric.type) {
|
|
106
|
+
case 'COUNTER':
|
|
107
|
+
return ['sum', 'rate'];
|
|
108
|
+
case 'RATE':
|
|
109
|
+
return ['rate'];
|
|
110
|
+
case 'TREND':
|
|
111
|
+
return ['avg', 'min', 'med', 'max', 'p90', 'p95', 'p99'];
|
|
112
|
+
default:
|
|
113
|
+
return ['sum', 'min', 'max'];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @param {object} param0
|
|
120
|
+
* @param {Metric} param0.metric
|
|
121
|
+
*/
|
|
122
|
+
function getMetricValuesText({ metric, target }) {
|
|
123
|
+
const fields = getDisplayFields({ metric, target });
|
|
124
|
+
const values = [];
|
|
125
|
+
if (metric.type === 'COUNTER') {
|
|
126
|
+
values.push(getCounterMetricFieldValue(metric, fields));
|
|
127
|
+
} else if (metric.type === 'TREND') {
|
|
128
|
+
for (let i = 0; i < fields.length; i++) {
|
|
129
|
+
const field_metric = fields[i];
|
|
130
|
+
values.push(getTrendMetricFieldValue(metric, field_metric));
|
|
131
|
+
}
|
|
132
|
+
} else if (metric.type === 'RATE') {
|
|
133
|
+
values.push(getRateMetricFieldValue(metric));
|
|
134
|
+
}
|
|
135
|
+
return values.join(' | ');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = {
|
|
139
|
+
getValidMetrics,
|
|
140
|
+
getMetricValuesText
|
|
141
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const request = require('phin-retry');
|
|
2
|
+
|
|
3
|
+
async function getLaunchDetails(options) {
|
|
4
|
+
return request.get({
|
|
5
|
+
url: `${options.url}/api/v1/${options.project}/launch/${options.launch_id}`,
|
|
6
|
+
headers: {
|
|
7
|
+
'Authorization': `Bearer ${options.api_key}`
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function getLaunchesByName(options) {
|
|
13
|
+
return request.get({
|
|
14
|
+
url: `${options.url}/api/v1/${options.project}/launch?filter.eq.name=${options.launch_name}&page.size=1&page.sort=startTime%2Cdesc`,
|
|
15
|
+
headers: {
|
|
16
|
+
'Authorization': `Bearer ${options.api_key}`
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function getLastLaunchByName(options) {
|
|
22
|
+
const response = await getLaunchesByName(options);
|
|
23
|
+
if (response.content && response.content.length > 0) {
|
|
24
|
+
return response.content[0];
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function getSuiteHistory(options) {
|
|
30
|
+
return request.get({
|
|
31
|
+
url: `${options.url}/api/v1/${options.project}/item/history`,
|
|
32
|
+
qs: {
|
|
33
|
+
historyDepth: options.history_depth,
|
|
34
|
+
'filter.eq.launchId': options.launch_id,
|
|
35
|
+
'filter.!ex.parentId': 'true'
|
|
36
|
+
},
|
|
37
|
+
headers: {
|
|
38
|
+
'Authorization': `Bearer ${options.api_key}`
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
getLaunchDetails,
|
|
45
|
+
getLastLaunchByName,
|
|
46
|
+
getSuiteHistory
|
|
47
|
+
}
|