testbeats 2.0.7 → 2.0.9
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/package.json +1 -1
- package/src/beats/beats.api.js +16 -1
- package/src/beats/beats.js +35 -17
- package/src/beats/beats.types.d.ts +15 -0
- package/src/commands/publish.command.js +31 -2
- package/src/extensions/base.extension.js +16 -0
- package/src/extensions/ci-info.extension.js +45 -15
- package/src/extensions/error-clusters.extension.js +46 -0
- package/src/extensions/index.js +3 -0
- package/src/extensions/smart-analysis.extension.js +22 -2
- package/src/helpers/constants.js +1 -0
- package/src/index.d.ts +2 -0
- package/src/targets/chat.js +30 -4
- package/src/targets/index.js +9 -1
- package/src/targets/slack.js +51 -10
- package/src/targets/teams.js +42 -7
package/package.json
CHANGED
package/src/beats/beats.api.js
CHANGED
|
@@ -25,7 +25,7 @@ class BeatsApi {
|
|
|
25
25
|
*/
|
|
26
26
|
getTestRun(run_id) {
|
|
27
27
|
return request.get({
|
|
28
|
-
url: `${this.getBaseUrl()}/api/core/v1/test-runs
|
|
28
|
+
url: `${this.getBaseUrl()}/api/core/v1/test-runs/${run_id}`,
|
|
29
29
|
headers: {
|
|
30
30
|
'x-api-key': this.config.api_key
|
|
31
31
|
}
|
|
@@ -46,6 +46,21 @@ class BeatsApi {
|
|
|
46
46
|
getBaseUrl() {
|
|
47
47
|
return process.env.TEST_BEATS_URL || "https://app.testbeats.com";
|
|
48
48
|
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
* @param {string} run_id
|
|
53
|
+
* @param {number} limit
|
|
54
|
+
* @returns {import('./beats.types').IErrorClustersResponse}
|
|
55
|
+
*/
|
|
56
|
+
getErrorClusters(run_id, limit = 3) {
|
|
57
|
+
return request.get({
|
|
58
|
+
url: `${this.getBaseUrl()}/api/core/v1/test-runs/${run_id}/error-clusters?limit=${limit}`,
|
|
59
|
+
headers: {
|
|
60
|
+
'x-api-key': this.config.api_key
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
49
64
|
}
|
|
50
65
|
|
|
51
66
|
module.exports = { BeatsApi }
|
package/src/beats/beats.js
CHANGED
|
@@ -20,15 +20,20 @@ class Beats {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
async publish() {
|
|
23
|
+
this.#setApiKey();
|
|
24
|
+
if (!this.config.api_key) {
|
|
25
|
+
logger.warn('😿 No API key provided, skipping publishing results to TestBeats Portal...');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
23
28
|
this.#setCIInfo();
|
|
24
29
|
this.#setProjectName();
|
|
25
30
|
this.#setRunName();
|
|
26
|
-
this.#setApiKey();
|
|
27
31
|
await this.#publishTestResults();
|
|
28
32
|
await this.#uploadAttachments();
|
|
29
33
|
this.#updateTitleLink();
|
|
30
34
|
await this.#attachFailureSummary();
|
|
31
35
|
await this.#attachSmartAnalysis();
|
|
36
|
+
await this.#attachErrorClusters();
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
#setCIInfo() {
|
|
@@ -45,13 +50,10 @@ class Beats {
|
|
|
45
50
|
|
|
46
51
|
#setRunName() {
|
|
47
52
|
this.config.run = this.config.run || process.env.TEST_BEATS_RUN || (this.ci && this.ci.build_name) || 'demo-run';
|
|
53
|
+
this.result.name = this.config.run;
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
async #publishTestResults() {
|
|
51
|
-
if (!this.config.api_key) {
|
|
52
|
-
logger.warn('😿 No API key provided, skipping publishing results to TestBeats Portal...');
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
57
|
logger.info("🚀 Publishing results to TestBeats Portal...");
|
|
56
58
|
try {
|
|
57
59
|
const payload = this.#getPayload();
|
|
@@ -89,18 +91,6 @@ class Beats {
|
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
#getAllFailedTestCases() {
|
|
93
|
-
const test_cases = [];
|
|
94
|
-
for (const suite of this.result.suites) {
|
|
95
|
-
for (const test of suite.cases) {
|
|
96
|
-
if (test.status === 'FAIL') {
|
|
97
|
-
test_cases.push(test);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return test_cases;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
94
|
#updateTitleLink() {
|
|
105
95
|
if (!this.test_run_id) {
|
|
106
96
|
return;
|
|
@@ -200,6 +190,34 @@ class Beats {
|
|
|
200
190
|
logger.warn(`🙈 ${text} not generated in given time`);
|
|
201
191
|
}
|
|
202
192
|
|
|
193
|
+
async #attachErrorClusters() {
|
|
194
|
+
if (!this.test_run_id) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (!this.config.targets) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (this.result.status !== 'FAIL') {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (this.config.show_error_clusters === false) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
logger.info('🧮 Fetching Error Clusters...');
|
|
208
|
+
const res = await this.api.getErrorClusters(this.test_run_id, 3);
|
|
209
|
+
this.config.extensions.push({
|
|
210
|
+
name: 'error-clusters',
|
|
211
|
+
hook: HOOK.AFTER_SUMMARY,
|
|
212
|
+
inputs: {
|
|
213
|
+
data: res.values
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
} catch (error) {
|
|
217
|
+
logger.error(`❌ Unable to attach error clusters: ${error.message}`, error);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
203
221
|
}
|
|
204
222
|
|
|
205
223
|
module.exports = { Beats }
|
|
@@ -17,3 +17,18 @@ export type IBeatExecutionMetric = {
|
|
|
17
17
|
test_run_id: string
|
|
18
18
|
org_id: string
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
export type IPaginatedAPIResponse<T> = {
|
|
22
|
+
page: number
|
|
23
|
+
limit: number
|
|
24
|
+
total: number
|
|
25
|
+
values: T[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type IErrorClustersResponse = {} & IPaginatedAPIResponse<IErrorCluster>;
|
|
29
|
+
|
|
30
|
+
export type IErrorCluster = {
|
|
31
|
+
test_failure_id: string
|
|
32
|
+
failure: string
|
|
33
|
+
count: number
|
|
34
|
+
}
|
|
@@ -18,6 +18,7 @@ class PublishCommand {
|
|
|
18
18
|
*/
|
|
19
19
|
constructor(opts) {
|
|
20
20
|
this.opts = opts;
|
|
21
|
+
this.errors = [];
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
async publish() {
|
|
@@ -31,7 +32,7 @@ class PublishCommand {
|
|
|
31
32
|
this.#validateConfig();
|
|
32
33
|
this.#processResults();
|
|
33
34
|
await this.#publishResults();
|
|
34
|
-
|
|
35
|
+
await this.#publishErrors();
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
#validateEnvDetails() {
|
|
@@ -184,13 +185,24 @@ class PublishCommand {
|
|
|
184
185
|
} else if (result_options.type === 'jmeter') {
|
|
185
186
|
this.results.push(prp.parse(result_options));
|
|
186
187
|
} else {
|
|
187
|
-
|
|
188
|
+
const { result, errors } = trp.parseV2(result_options);
|
|
189
|
+
if (result) {
|
|
190
|
+
this.results.push(result);
|
|
191
|
+
}
|
|
192
|
+
if (errors) {
|
|
193
|
+
this.errors = this.errors.concat(errors);
|
|
194
|
+
}
|
|
188
195
|
}
|
|
189
196
|
}
|
|
190
197
|
}
|
|
191
198
|
}
|
|
192
199
|
|
|
193
200
|
async #publishResults() {
|
|
201
|
+
if (!this.results.length) {
|
|
202
|
+
logger.warn('⚠️ No results to publish');
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
194
206
|
for (const config of this.configs) {
|
|
195
207
|
for (let i = 0; i < this.results.length; i++) {
|
|
196
208
|
const result = this.results[i];
|
|
@@ -207,6 +219,23 @@ class PublishCommand {
|
|
|
207
219
|
}
|
|
208
220
|
}
|
|
209
221
|
}
|
|
222
|
+
logger.info('✅ Results published successfully!');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async #publishErrors() {
|
|
226
|
+
if (!this.errors.length) {
|
|
227
|
+
logger.debug('⚠️ No errors to publish');
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
logger.info('🛑 Publishing errors...');
|
|
231
|
+
for (const config of this.configs) {
|
|
232
|
+
if (config.targets) {
|
|
233
|
+
for (const target of config.targets) {
|
|
234
|
+
await target_manager.handleErrors({ target, errors: this.errors });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
throw new Error(this.errors.join('\n'));
|
|
210
239
|
}
|
|
211
240
|
|
|
212
241
|
}
|
|
@@ -86,6 +86,22 @@ class BaseExtension {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* @param {string|number} text
|
|
91
|
+
*/
|
|
92
|
+
bold(text) {
|
|
93
|
+
switch (this.target.name) {
|
|
94
|
+
case 'teams':
|
|
95
|
+
return `**${text}**`;
|
|
96
|
+
case 'slack':
|
|
97
|
+
return `*${text}*`;
|
|
98
|
+
case 'chat':
|
|
99
|
+
return `<b>${text}</b>`;
|
|
100
|
+
default:
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
89
105
|
}
|
|
90
106
|
|
|
91
107
|
module.exports = { BaseExtension }
|
|
@@ -3,6 +3,8 @@ const { getCIInformation } = require('../helpers/ci');
|
|
|
3
3
|
const { getMetaDataText } = require("../helpers/metadata.helper");
|
|
4
4
|
const { STATUS, HOOK } = require("../helpers/constants");
|
|
5
5
|
|
|
6
|
+
const COMMON_BRANCH_NAMES = ['main', 'master', 'dev', 'develop', 'qa', 'test'];
|
|
7
|
+
|
|
6
8
|
class CIInfoExtension extends BaseExtension {
|
|
7
9
|
|
|
8
10
|
constructor(target, extension, result, payload, root_payload) {
|
|
@@ -17,23 +19,24 @@ class CIInfoExtension extends BaseExtension {
|
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
#setDefaultOptions() {
|
|
20
|
-
this.default_options.hook = HOOK.AFTER_SUMMARY
|
|
22
|
+
this.default_options.hook = HOOK.AFTER_SUMMARY;
|
|
21
23
|
this.default_options.condition = STATUS.PASS_OR_FAIL;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
#setDefaultInputs() {
|
|
25
27
|
this.default_inputs.title = '';
|
|
26
28
|
this.default_inputs.title_link = '';
|
|
27
|
-
this.default_inputs.
|
|
28
|
-
this.default_inputs.
|
|
29
|
+
this.default_inputs.show_repository_non_common = true;
|
|
30
|
+
this.default_inputs.show_repository = false;
|
|
31
|
+
this.default_inputs.show_repository_branch = false;
|
|
29
32
|
this.default_inputs.show_build = true;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
async run() {
|
|
33
36
|
this.ci = getCIInformation();
|
|
34
37
|
|
|
35
|
-
this
|
|
36
|
-
this
|
|
38
|
+
this.#setRepositoryElements();
|
|
39
|
+
this.#setBuildElements();
|
|
37
40
|
|
|
38
41
|
const repository_text = await getMetaDataText({ elements: this.repository_elements, target: this.target, extension: this.extension, result: this.result, default_condition: this.default_options.condition });
|
|
39
42
|
const build_text = await getMetaDataText({ elements: this.build_elements, target: this.target, extension: this.extension, result: this.result, default_condition: this.default_options.condition });
|
|
@@ -41,28 +44,55 @@ class CIInfoExtension extends BaseExtension {
|
|
|
41
44
|
this.attach();
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
setRepositoryElements() {
|
|
47
|
+
#setRepositoryElements() {
|
|
45
48
|
if (!this.ci) {
|
|
46
49
|
return;
|
|
47
50
|
}
|
|
51
|
+
if (!this.ci.repository_url || !this.ci.repository_name || !this.ci.repository_ref) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
48
54
|
|
|
49
|
-
if (this.extension.inputs.show_repository
|
|
50
|
-
this
|
|
55
|
+
if (this.extension.inputs.show_repository) {
|
|
56
|
+
this.#setRepositoryElement();
|
|
57
|
+
}
|
|
58
|
+
if (this.extension.inputs.show_repository_branch) {
|
|
59
|
+
if (this.ci.repository_ref.includes('refs/pull')) {
|
|
60
|
+
this.#setPullRequestElement();
|
|
61
|
+
} else {
|
|
62
|
+
this.#setRepositoryBranchElement();
|
|
63
|
+
}
|
|
51
64
|
}
|
|
52
|
-
if (this.extension.inputs.show_repository_branch && this.
|
|
65
|
+
if (!this.extension.inputs.show_repository && !this.extension.inputs.show_repository_branch && this.extension.inputs.show_repository_non_common) {
|
|
53
66
|
if (this.ci.repository_ref.includes('refs/pull')) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
this.repository_elements.push({ label: 'Pull Request', key: pr_name, value: pr_url, type: 'hyperlink' });
|
|
67
|
+
this.#setRepositoryElement();
|
|
68
|
+
this.#setPullRequestElement();
|
|
57
69
|
} else {
|
|
58
|
-
const branch_url = this.ci.repository_url + this.ci.repository_ref.replace('refs/heads/', '/tree/');
|
|
59
70
|
const branch_name = this.ci.repository_ref.replace('refs/heads/', '');
|
|
60
|
-
|
|
71
|
+
if (!COMMON_BRANCH_NAMES.includes(branch_name.toLowerCase())) {
|
|
72
|
+
this.#setRepositoryElement();
|
|
73
|
+
this.#setRepositoryBranchElement();
|
|
74
|
+
}
|
|
61
75
|
}
|
|
62
76
|
}
|
|
63
77
|
}
|
|
64
78
|
|
|
65
|
-
|
|
79
|
+
#setRepositoryElement() {
|
|
80
|
+
this.repository_elements.push({ label: 'Repository', key: this.ci.repository_name, value: this.ci.repository_url, type: 'hyperlink' });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#setPullRequestElement() {
|
|
84
|
+
const pr_url = this.ci.repository_url + this.ci.repository_ref.replace('refs/pull/', '/pull/');
|
|
85
|
+
const pr_name = this.ci.repository_ref.replace('refs/pull/', '').replace('/merge', '');
|
|
86
|
+
this.repository_elements.push({ label: 'Pull Request', key: pr_name, value: pr_url, type: 'hyperlink' });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#setRepositoryBranchElement() {
|
|
90
|
+
const branch_url = this.ci.repository_url + this.ci.repository_ref.replace('refs/heads/', '/tree/');
|
|
91
|
+
const branch_name = this.ci.repository_ref.replace('refs/heads/', '');
|
|
92
|
+
this.repository_elements.push({ label: 'Branch', key: branch_name, value: branch_url, type: 'hyperlink' });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#setBuildElements() {
|
|
66
96
|
if (!this.ci) {
|
|
67
97
|
return;
|
|
68
98
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const { BaseExtension } = require('./base.extension');
|
|
2
|
+
const { STATUS, HOOK } = require("../helpers/constants");
|
|
3
|
+
|
|
4
|
+
class ErrorClustersExtension extends BaseExtension {
|
|
5
|
+
|
|
6
|
+
constructor(target, extension, result, payload, root_payload) {
|
|
7
|
+
super(target, extension, result, payload, root_payload);
|
|
8
|
+
this.#setDefaultOptions();
|
|
9
|
+
this.#setDefaultInputs();
|
|
10
|
+
this.updateExtensionInputs();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
run() {
|
|
14
|
+
this.#setText();
|
|
15
|
+
this.attach();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#setDefaultOptions() {
|
|
19
|
+
this.default_options.hook = HOOK.AFTER_SUMMARY,
|
|
20
|
+
this.default_options.condition = STATUS.PASS_OR_FAIL;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#setDefaultInputs() {
|
|
24
|
+
this.default_inputs.title = 'Top Errors';
|
|
25
|
+
this.default_inputs.title_link = '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#setText() {
|
|
29
|
+
const data = this.extension.inputs.data;
|
|
30
|
+
if (!data || !data.length) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const clusters = data;
|
|
35
|
+
|
|
36
|
+
this.extension.inputs.title = `Top ${clusters.length} Errors`;
|
|
37
|
+
|
|
38
|
+
const texts = [];
|
|
39
|
+
for (const cluster of clusters) {
|
|
40
|
+
texts.push(`${this.bold(`(${cluster.count})`)} - ${cluster.failure}`);
|
|
41
|
+
}
|
|
42
|
+
this.text = this.mergeTexts(texts);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { ErrorClustersExtension }
|
package/src/extensions/index.js
CHANGED
|
@@ -12,6 +12,7 @@ const { CustomExtension } = require('./custom.extension');
|
|
|
12
12
|
const { EXTENSION } = require('../helpers/constants');
|
|
13
13
|
const { checkCondition } = require('../helpers/helper');
|
|
14
14
|
const logger = require('../utils/logger');
|
|
15
|
+
const { ErrorClustersExtension } = require('./error-clusters.extension');
|
|
15
16
|
|
|
16
17
|
async function run(options) {
|
|
17
18
|
const { target, result, hook } = options;
|
|
@@ -60,6 +61,8 @@ function getExtensionRunner(extension, options) {
|
|
|
60
61
|
return new AIFailureSummaryExtension(options.target, extension, options.result, options.payload, options.root_payload);
|
|
61
62
|
case EXTENSION.SMART_ANALYSIS:
|
|
62
63
|
return new SmartAnalysisExtension(options.target, extension, options.result, options.payload, options.root_payload);
|
|
64
|
+
case EXTENSION.ERROR_CLUSTERS:
|
|
65
|
+
return new ErrorClustersExtension(options.target, extension, options.result, options.payload, options.root_payload);
|
|
63
66
|
default:
|
|
64
67
|
return require(extension.name);
|
|
65
68
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { BaseExtension } = require('./base.extension');
|
|
2
2
|
const { STATUS, HOOK } = require("../helpers/constants");
|
|
3
|
+
const logger = require('../utils/logger');
|
|
3
4
|
|
|
4
5
|
class SmartAnalysisExtension extends BaseExtension {
|
|
5
6
|
|
|
@@ -21,7 +22,7 @@ class SmartAnalysisExtension extends BaseExtension {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
#setDefaultInputs() {
|
|
24
|
-
this.default_inputs.title = '
|
|
25
|
+
this.default_inputs.title = '';
|
|
25
26
|
this.default_inputs.title_link = '';
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -37,6 +38,11 @@ class SmartAnalysisExtension extends BaseExtension {
|
|
|
37
38
|
*/
|
|
38
39
|
const execution_metrics = data.execution_metrics[0];
|
|
39
40
|
|
|
41
|
+
if (!execution_metrics) {
|
|
42
|
+
logger.warn('⚠️ No execution metrics found. Skipping.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
40
46
|
const smart_analysis = [];
|
|
41
47
|
if (execution_metrics.newly_failed) {
|
|
42
48
|
smart_analysis.push(`⭕ Newly Failed: ${execution_metrics.newly_failed}`);
|
|
@@ -54,7 +60,21 @@ class SmartAnalysisExtension extends BaseExtension {
|
|
|
54
60
|
smart_analysis.push(`🟢 Recovered: ${execution_metrics.recovered}`);
|
|
55
61
|
}
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
const texts = [];
|
|
64
|
+
const rows = [];
|
|
65
|
+
for (const item of smart_analysis) {
|
|
66
|
+
rows.push(item);
|
|
67
|
+
if (rows.length === 3) {
|
|
68
|
+
texts.push(rows.join(' | '));
|
|
69
|
+
rows.length = 0;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (rows.length > 0) {
|
|
74
|
+
texts.push(rows.join(' | '));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.text = this.mergeTexts(texts);
|
|
58
78
|
}
|
|
59
79
|
|
|
60
80
|
}
|
package/src/helpers/constants.js
CHANGED
|
@@ -22,6 +22,7 @@ const TARGET = Object.freeze({
|
|
|
22
22
|
const EXTENSION = Object.freeze({
|
|
23
23
|
AI_FAILURE_SUMMARY: 'ai-failure-summary',
|
|
24
24
|
SMART_ANALYSIS: 'smart-analysis',
|
|
25
|
+
ERROR_CLUSTERS: 'error-clusters',
|
|
25
26
|
HYPERLINKS: 'hyperlinks',
|
|
26
27
|
MENTIONS: 'mentions',
|
|
27
28
|
REPORT_PORTAL_ANALYSIS: 'report-portal-analysis',
|
package/src/index.d.ts
CHANGED
|
@@ -56,6 +56,7 @@ export interface MentionInputs extends ExtensionInputs {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
export interface CIInfoInputs extends ExtensionInputs {
|
|
59
|
+
show_repository_non_common?: boolean;
|
|
59
60
|
show_repository?: boolean;
|
|
60
61
|
show_repository_branch?: boolean;
|
|
61
62
|
show_build?: boolean;
|
|
@@ -229,6 +230,7 @@ export interface PublishReport {
|
|
|
229
230
|
run?: string;
|
|
230
231
|
show_failure_summary?: boolean;
|
|
231
232
|
show_smart_analysis?: boolean;
|
|
233
|
+
show_error_clusters?: boolean;
|
|
232
234
|
targets?: Target[];
|
|
233
235
|
extensions?: Extension[];
|
|
234
236
|
results?: ParseOptions[] | PerformanceParseOptions[] | CustomResultOptions[];
|
package/src/targets/chat.js
CHANGED
|
@@ -4,6 +4,7 @@ const extension_manager = require('../extensions');
|
|
|
4
4
|
const { HOOK, STATUS } = require('../helpers/constants');
|
|
5
5
|
const PerformanceTestResult = require('performance-results-parser/src/models/PerformanceTestResult');
|
|
6
6
|
const { getValidMetrics, getMetricValuesText } = require('../helpers/performance');
|
|
7
|
+
const logger = require('../utils/logger');
|
|
7
8
|
|
|
8
9
|
async function run({ result, target }) {
|
|
9
10
|
setTargetInputs(target);
|
|
@@ -14,6 +15,7 @@ async function run({ result, target }) {
|
|
|
14
15
|
} else {
|
|
15
16
|
await setFunctionalPayload({ result, target, payload, root_payload });
|
|
16
17
|
}
|
|
18
|
+
logger.info(`🔔 Publishing results to Chat...`);
|
|
17
19
|
return request.post({
|
|
18
20
|
url: target.inputs.url,
|
|
19
21
|
body: root_payload
|
|
@@ -144,9 +146,9 @@ async function setPerformancePayload({ result, target, payload, root_payload })
|
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
/**
|
|
147
|
-
*
|
|
149
|
+
*
|
|
148
150
|
* @param {object} param0
|
|
149
|
-
* @param {PerformanceTestResult} param0.result
|
|
151
|
+
* @param {PerformanceTestResult} param0.result
|
|
150
152
|
*/
|
|
151
153
|
async function setPerformanceMainBlock({ result, target, payload }) {
|
|
152
154
|
const title_text_with_emoji = getTitleTextWithEmoji({ result, target });
|
|
@@ -169,9 +171,9 @@ async function setPerformanceMainBlock({ result, target, payload }) {
|
|
|
169
171
|
}
|
|
170
172
|
|
|
171
173
|
/**
|
|
172
|
-
*
|
|
174
|
+
*
|
|
173
175
|
* @param {object} param0
|
|
174
|
-
* @param {PerformanceTestResult} param0.result
|
|
176
|
+
* @param {PerformanceTestResult} param0.result
|
|
175
177
|
*/
|
|
176
178
|
async function setTransactionBlock({ result, target, payload }) {
|
|
177
179
|
if (target.inputs.include_suites) {
|
|
@@ -236,7 +238,31 @@ const default_inputs = {
|
|
|
236
238
|
]
|
|
237
239
|
};
|
|
238
240
|
|
|
241
|
+
async function handleErrors({ target, errors }) {
|
|
242
|
+
let title = 'Error: Reporting Test Results';
|
|
243
|
+
title = target.inputs.title ? title + ' - ' + target.inputs.title : title;
|
|
244
|
+
|
|
245
|
+
const root_payload = getRootPayload();
|
|
246
|
+
const payload = root_payload.cards[0];
|
|
247
|
+
|
|
248
|
+
payload.sections.push({
|
|
249
|
+
"widgets": [
|
|
250
|
+
{
|
|
251
|
+
"textParagraph": {
|
|
252
|
+
text: `<b>${title}</b><br><br><b>Errors</b>: <br>${errors.join('<br>')}`
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
]
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
return request.post({
|
|
259
|
+
url: target.inputs.url,
|
|
260
|
+
body: root_payload
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
239
264
|
module.exports = {
|
|
240
265
|
run,
|
|
266
|
+
handleErrors,
|
|
241
267
|
default_options
|
|
242
268
|
}
|
package/src/targets/index.js
CHANGED
|
@@ -34,6 +34,14 @@ async function run(target, result) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
async function handleErrors({ target, errors }) {
|
|
38
|
+
const target_runner = getTargetRunner(target);
|
|
39
|
+
if (target_runner.handleErrors) {
|
|
40
|
+
await target_runner.handleErrors({ target, errors });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
module.exports = {
|
|
38
|
-
run
|
|
45
|
+
run,
|
|
46
|
+
handleErrors
|
|
39
47
|
}
|
package/src/targets/slack.js
CHANGED
|
@@ -2,12 +2,14 @@ const request = require('phin-retry');
|
|
|
2
2
|
const { getPercentage, truncate, getPrettyDuration } = require('../helpers/helper');
|
|
3
3
|
const extension_manager = require('../extensions');
|
|
4
4
|
const { HOOK, STATUS } = require('../helpers/constants');
|
|
5
|
+
const logger = require('../utils/logger');
|
|
5
6
|
|
|
6
7
|
const PerformanceTestResult = require('performance-results-parser/src/models/PerformanceTestResult');
|
|
7
8
|
const { getValidMetrics, getMetricValuesText } = require('../helpers/performance');
|
|
8
9
|
const TestResult = require('test-results-parser/src/models/TestResult');
|
|
9
10
|
|
|
10
11
|
|
|
12
|
+
|
|
11
13
|
const COLORS = {
|
|
12
14
|
GOOD: '#36A64F',
|
|
13
15
|
WARNING: '#ECB22E',
|
|
@@ -23,6 +25,7 @@ async function run({ result, target }) {
|
|
|
23
25
|
await setFunctionalPayload({ result, target, payload });
|
|
24
26
|
}
|
|
25
27
|
const message = getRootPayload({ result, target, payload });
|
|
28
|
+
logger.info(`🔔 Publishing results to Slack...`);
|
|
26
29
|
return request.post({
|
|
27
30
|
url: target.inputs.url,
|
|
28
31
|
body: message
|
|
@@ -142,10 +145,10 @@ function getFailureDetails(suite) {
|
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
/**
|
|
145
|
-
*
|
|
146
|
-
* @param {object} param0
|
|
147
|
-
* @param {PerformanceTestResult | TestResult} param0.result
|
|
148
|
-
* @returns
|
|
148
|
+
*
|
|
149
|
+
* @param {object} param0
|
|
150
|
+
* @param {PerformanceTestResult | TestResult} param0.result
|
|
151
|
+
* @returns
|
|
149
152
|
*/
|
|
150
153
|
function getRootPayload({ result, target, payload }) {
|
|
151
154
|
let color = COLORS.GOOD;
|
|
@@ -182,9 +185,9 @@ async function setPerformancePayload({ result, target, payload }) {
|
|
|
182
185
|
}
|
|
183
186
|
|
|
184
187
|
/**
|
|
185
|
-
*
|
|
186
|
-
* @param {object} param0
|
|
187
|
-
* @param {PerformanceTestResult} param0.result
|
|
188
|
+
*
|
|
189
|
+
* @param {object} param0
|
|
190
|
+
* @param {PerformanceTestResult} param0.result
|
|
188
191
|
*/
|
|
189
192
|
async function setPerformanceMainBlock({ result, target, payload }) {
|
|
190
193
|
let text = `*${getTitleText(result, target)}*\n`;
|
|
@@ -206,9 +209,9 @@ async function setPerformanceMainBlock({ result, target, payload }) {
|
|
|
206
209
|
}
|
|
207
210
|
|
|
208
211
|
/**
|
|
209
|
-
*
|
|
210
|
-
* @param {object} param0
|
|
211
|
-
* @param {PerformanceTestResult} param0.result
|
|
212
|
+
*
|
|
213
|
+
* @param {object} param0
|
|
214
|
+
* @param {PerformanceTestResult} param0.result
|
|
212
215
|
*/
|
|
213
216
|
async function setTransactionBlock({ result, target, payload }) {
|
|
214
217
|
if (target.inputs.include_suites) {
|
|
@@ -261,7 +264,45 @@ const default_inputs = {
|
|
|
261
264
|
]
|
|
262
265
|
}
|
|
263
266
|
|
|
267
|
+
async function handleErrors({ target, errors }) {
|
|
268
|
+
let title = 'Error: Reporting Test Results';
|
|
269
|
+
title = target.inputs.title ? title + ' - ' + target.inputs.title : title;
|
|
270
|
+
|
|
271
|
+
const blocks = [];
|
|
272
|
+
|
|
273
|
+
blocks.push({
|
|
274
|
+
"type": "section",
|
|
275
|
+
"text": {
|
|
276
|
+
"type": "mrkdwn",
|
|
277
|
+
"text": title
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
blocks.push({
|
|
281
|
+
"type": "section",
|
|
282
|
+
"text": {
|
|
283
|
+
"type": "mrkdwn",
|
|
284
|
+
"text": errors.join('\n\n')
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const payload = {
|
|
289
|
+
"attachments": [
|
|
290
|
+
{
|
|
291
|
+
"color": COLORS.DANGER,
|
|
292
|
+
"blocks": blocks,
|
|
293
|
+
"fallback": title,
|
|
294
|
+
}
|
|
295
|
+
]
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
return request.post({
|
|
299
|
+
url: target.inputs.url,
|
|
300
|
+
body: payload
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
264
304
|
module.exports = {
|
|
265
305
|
run,
|
|
306
|
+
handleErrors,
|
|
266
307
|
default_options
|
|
267
308
|
}
|
package/src/targets/teams.js
CHANGED
|
@@ -3,14 +3,15 @@ const { getPercentage, truncate, getPrettyDuration } = require('../helpers/helpe
|
|
|
3
3
|
const { getValidMetrics, getMetricValuesText } = require('../helpers/performance');
|
|
4
4
|
const extension_manager = require('../extensions');
|
|
5
5
|
const { HOOK, STATUS } = require('../helpers/constants');
|
|
6
|
+
const logger = require('../utils/logger');
|
|
6
7
|
|
|
7
8
|
const TestResult = require('test-results-parser/src/models/TestResult');
|
|
8
9
|
const PerformanceTestResult = require('performance-results-parser/src/models/PerformanceTestResult');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
* @param {object} param0
|
|
12
|
-
* @param {PerformanceTestResult | TestResult} param0.result
|
|
13
|
-
* @returns
|
|
12
|
+
* @param {object} param0
|
|
13
|
+
* @param {PerformanceTestResult | TestResult} param0.result
|
|
14
|
+
* @returns
|
|
14
15
|
*/
|
|
15
16
|
async function run({ result, target }) {
|
|
16
17
|
setTargetInputs(target);
|
|
@@ -22,6 +23,7 @@ async function run({ result, target }) {
|
|
|
22
23
|
await setFunctionalPayload({ result, target, payload, root_payload });
|
|
23
24
|
}
|
|
24
25
|
setRootPayload(root_payload, payload);
|
|
26
|
+
logger.info(`🔔 Publishing results to Teams...`);
|
|
25
27
|
return request.post({
|
|
26
28
|
url: target.inputs.url,
|
|
27
29
|
body: root_payload
|
|
@@ -209,8 +211,8 @@ function setRootPayload(root_payload, payload) {
|
|
|
209
211
|
}
|
|
210
212
|
|
|
211
213
|
/**
|
|
212
|
-
* @param {object} param0
|
|
213
|
-
* @param {PerformanceTestResult} param0.result
|
|
214
|
+
* @param {object} param0
|
|
215
|
+
* @param {PerformanceTestResult} param0.result
|
|
214
216
|
*/
|
|
215
217
|
async function setMainBlockForPerformance({ result, target, payload }) {
|
|
216
218
|
const total = result.transactions.length;
|
|
@@ -245,8 +247,8 @@ async function getFactMetrics({ metrics, target, result }) {
|
|
|
245
247
|
}
|
|
246
248
|
|
|
247
249
|
/**
|
|
248
|
-
* @param {object} param0
|
|
249
|
-
* @param {PerformanceTestResult} param0.result
|
|
250
|
+
* @param {object} param0
|
|
251
|
+
* @param {PerformanceTestResult} param0.result
|
|
250
252
|
*/
|
|
251
253
|
async function setTransactionBlock({ result, target, payload }) {
|
|
252
254
|
if (target.inputs.include_suites) {
|
|
@@ -298,7 +300,40 @@ const default_inputs = {
|
|
|
298
300
|
]
|
|
299
301
|
}
|
|
300
302
|
|
|
303
|
+
async function handleErrors({ target, errors }) {
|
|
304
|
+
let title = 'Error: Reporting Test Results';
|
|
305
|
+
title = target.inputs.title ? title + ' - ' + target.inputs.title : title;
|
|
306
|
+
|
|
307
|
+
const root_payload = getRootPayload();
|
|
308
|
+
const payload = getMainPayload(target);
|
|
309
|
+
|
|
310
|
+
payload.body.push({
|
|
311
|
+
"type": "TextBlock",
|
|
312
|
+
"text": title,
|
|
313
|
+
"size": "medium",
|
|
314
|
+
"weight": "bolder",
|
|
315
|
+
"wrap": true
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
payload.body.push({
|
|
319
|
+
"type": "TextBlock",
|
|
320
|
+
"text": errors.join('\n'),
|
|
321
|
+
"size": "medium",
|
|
322
|
+
"weight": "bolder",
|
|
323
|
+
"wrap": true
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
setRootPayload(root_payload, payload);
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
return request.post({
|
|
330
|
+
url: target.inputs.url,
|
|
331
|
+
body: root_payload
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
301
335
|
module.exports = {
|
|
302
336
|
run,
|
|
337
|
+
handleErrors,
|
|
303
338
|
default_options
|
|
304
339
|
}
|