@testomatio/reporter 1.6.0-beta-2-artifacts → 2.0.0-beta-esm

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.
Files changed (97) hide show
  1. package/lib/adapter/codecept.js +288 -330
  2. package/lib/adapter/cucumber/current.js +195 -203
  3. package/lib/adapter/cucumber/legacy.js +130 -155
  4. package/lib/adapter/cucumber.js +5 -16
  5. package/lib/adapter/cypress-plugin/index.js +91 -105
  6. package/lib/adapter/jasmine/jasmine.js +63 -0
  7. package/lib/adapter/jasmine.js +54 -53
  8. package/lib/adapter/jest.js +97 -99
  9. package/lib/adapter/mocha/mocha.js +125 -0
  10. package/lib/adapter/mocha.js +111 -140
  11. package/lib/adapter/playwright.js +168 -200
  12. package/lib/adapter/vitest.js +144 -143
  13. package/lib/adapter/webdriver.js +113 -97
  14. package/lib/bin/reportXml.js +49 -49
  15. package/lib/bin/startTest.js +80 -97
  16. package/lib/client.js +344 -385
  17. package/lib/config.js +16 -21
  18. package/lib/constants.js +49 -43
  19. package/lib/data-storage.js +206 -188
  20. package/lib/fileUploader.js +245 -0
  21. package/lib/junit-adapter/adapter.js +17 -20
  22. package/lib/junit-adapter/csharp.js +18 -14
  23. package/lib/junit-adapter/index.js +27 -25
  24. package/lib/junit-adapter/java.js +41 -53
  25. package/lib/junit-adapter/javascript.js +30 -27
  26. package/lib/junit-adapter/python.js +38 -37
  27. package/lib/junit-adapter/ruby.js +11 -8
  28. package/lib/output.js +44 -52
  29. package/lib/package.json +1 -0
  30. package/lib/pipe/bitbucket.js +208 -227
  31. package/lib/pipe/csv.js +111 -124
  32. package/lib/pipe/github.js +184 -211
  33. package/lib/pipe/gitlab.js +164 -205
  34. package/lib/pipe/html.js +253 -312
  35. package/lib/pipe/index.js +83 -63
  36. package/lib/pipe/testomatio.js +391 -454
  37. package/lib/reporter-functions.js +16 -20
  38. package/lib/reporter.js +47 -17
  39. package/lib/services/artifacts.js +55 -51
  40. package/lib/services/index.js +14 -12
  41. package/lib/services/key-values.js +56 -53
  42. package/lib/services/logger.js +227 -245
  43. package/lib/utils/chalk.js +10 -0
  44. package/lib/utils/pipe_utils.js +91 -84
  45. package/lib/utils/utils.js +289 -273
  46. package/lib/xmlReader.js +480 -519
  47. package/package.json +57 -19
  48. package/src/adapter/codecept.js +369 -0
  49. package/src/adapter/cucumber/current.js +228 -0
  50. package/src/adapter/cucumber/legacy.js +158 -0
  51. package/src/adapter/cucumber.js +4 -0
  52. package/src/adapter/cypress-plugin/index.js +110 -0
  53. package/src/adapter/jasmine.js +60 -0
  54. package/src/adapter/jest.js +107 -0
  55. package/src/adapter/mocha.cjs +2 -0
  56. package/src/adapter/mocha.js +156 -0
  57. package/src/adapter/playwright.js +222 -0
  58. package/src/adapter/vitest.js +183 -0
  59. package/src/adapter/webdriver.js +111 -0
  60. package/src/bin/reportXml.js +67 -0
  61. package/src/bin/startTest.js +119 -0
  62. package/src/client.js +423 -0
  63. package/src/config.js +30 -0
  64. package/src/constants.js +49 -0
  65. package/src/data-storage.js +204 -0
  66. package/src/fileUploader.js +307 -0
  67. package/src/junit-adapter/adapter.js +23 -0
  68. package/src/junit-adapter/csharp.js +16 -0
  69. package/src/junit-adapter/index.js +28 -0
  70. package/src/junit-adapter/java.js +58 -0
  71. package/src/junit-adapter/javascript.js +31 -0
  72. package/src/junit-adapter/python.js +42 -0
  73. package/src/junit-adapter/ruby.js +10 -0
  74. package/src/output.js +57 -0
  75. package/src/pipe/bitbucket.js +254 -0
  76. package/src/pipe/csv.js +140 -0
  77. package/src/pipe/github.js +234 -0
  78. package/src/pipe/gitlab.js +229 -0
  79. package/src/pipe/html.js +366 -0
  80. package/src/pipe/index.js +73 -0
  81. package/src/pipe/testomatio.js +498 -0
  82. package/src/reporter-functions.js +44 -0
  83. package/src/reporter.cjs +22 -0
  84. package/src/reporter.js +24 -0
  85. package/src/services/artifacts.js +59 -0
  86. package/src/services/index.js +13 -0
  87. package/src/services/key-values.js +59 -0
  88. package/src/services/logger.js +314 -0
  89. package/src/template/emptyData.svg +23 -0
  90. package/src/template/testomatio.hbs +1421 -0
  91. package/src/utils/chalk.js +13 -0
  92. package/src/utils/pipe_utils.js +127 -0
  93. package/src/utils/utils.js +341 -0
  94. package/src/xmlReader.js +551 -0
  95. package/lib/bin/cli.js +0 -216
  96. package/lib/bin/uploadArtifacts.js +0 -86
  97. package/lib/uploader.js +0 -312
@@ -0,0 +1,234 @@
1
+ import createDebugMessages from 'debug';
2
+ import path from 'path';
3
+ import pc from 'picocolors';
4
+ import humanizeDuration from 'humanize-duration';
5
+ import merge from 'lodash.merge';
6
+ import { Octokit } from '@octokit/rest';
7
+ import { APP_PREFIX, testomatLogoURL } from '../constants.js';
8
+ import { ansiRegExp, isSameTest } from '../utils/utils.js';
9
+ import { statusEmoji, fullName } from '../utils/pipe_utils.js';
10
+
11
+ const debug = createDebugMessages('@testomatio/reporter:pipe:github');
12
+
13
+
14
+ /**
15
+ * @typedef {import('../../types').Pipe} Pipe
16
+ * @typedef {import('../../types').TestData} TestData
17
+ * @class GitHubPipe
18
+ * @implements {Pipe}
19
+ */
20
+ class GitHubPipe {
21
+ constructor(params, store = {}) {
22
+ this.isEnabled = false;
23
+ this.store = store;
24
+ this.tests = [];
25
+ this.token = params.GH_PAT || process.env.GH_PAT;
26
+ this.ref = process.env.GITHUB_REF;
27
+ this.repo = process.env.GITHUB_REPOSITORY;
28
+ this.jobKey = `${process.env.GITHUB_WORKFLOW || ''} / ${process.env.GITHUB_JOB || ''}`;
29
+ this.hiddenCommentData = `<!--- testomat.io report ${this.jobKey} -->`;
30
+
31
+ debug('GitHub Pipe: ', this.token ? 'TOKEN' : '*no token*', 'Ref:', this.ref, 'Repo:', this.repo);
32
+
33
+ if (!this.token || !this.ref || !this.repo) return;
34
+ this.isEnabled = true;
35
+ const matchedIssue = this.ref.match(/refs\/pull\/(\d+)\/merge/);
36
+ if (!matchedIssue) return;
37
+ this.issue = parseInt(matchedIssue[1], 10);
38
+
39
+ this.start = new Date();
40
+
41
+ debug('GitHub Pipe: Enabled');
42
+ }
43
+
44
+ // TODO: to using SET opts as argument => prepareRun(opts)
45
+ async prepareRun() {}
46
+
47
+ async createRun() {}
48
+
49
+ addTest(test) {
50
+ if (!this.isEnabled) return;
51
+ debug('Adding test:', test);
52
+
53
+ const index = this.tests.findIndex(t => isSameTest(t, test));
54
+ // update if they were already added
55
+ if (index >= 0) {
56
+ this.tests[index] = merge(this.tests[index], test);
57
+ return;
58
+ }
59
+
60
+ this.tests.push(test);
61
+ }
62
+
63
+ async finishRun(runParams) {
64
+ if (!this.isEnabled) return;
65
+ if (!this.issue) return;
66
+
67
+ if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
68
+
69
+ this.octokit = new Octokit({
70
+ auth: this.token,
71
+ });
72
+
73
+ const [owner, repo] = (this.repo || '').split('/');
74
+ if (!(owner || repo)) return;
75
+
76
+ // ... create a comment on GitHub
77
+ const passedCount = this.tests.filter(t => t.status === 'passed').length;
78
+ const failedCount = this.tests.filter(t => t.status === 'failed').length;
79
+ const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
80
+
81
+ let summary = `${this.hiddenCommentData}
82
+
83
+ | [![Testomat.io Report](${testomatLogoURL})](https://testomat.io) | ${statusEmoji(
84
+ runParams.status,
85
+ )} ${`${process.env.GITHUB_JOB} ${runParams.status}`.toUpperCase()} |
86
+ | --- | --- |
87
+ | Tests | ✔️ **${this.tests.length}** tests run |
88
+ | Summary | ${failedCount ? `${statusEmoji('failed')} **${failedCount}** failed; ` : ''} ${statusEmoji(
89
+ 'passed',
90
+ )} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
91
+ | Duration | 🕐 **${humanizeDuration(
92
+ parseInt(
93
+ this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
94
+ 10,
95
+ ),
96
+ {
97
+ maxDecimalPoints: 0,
98
+ },
99
+ )}** |`;
100
+
101
+ if (this.store.runUrl) {
102
+ summary += `\n| Testomat.io Report | 📊 [Run #${this.store.runId}](${this.store.runUrl}) | `;
103
+ }
104
+ if (process.env.GITHUB_WORKFLOW) {
105
+ summary += `\n| Job | 🗂️ [${this.jobKey}](${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${
106
+ this.repo
107
+ }/actions/runs/${process.env.GITHUB_RUN_ID}) | `;
108
+ }
109
+ if (process.env.RUNNER_OS) {
110
+ summary += `\n| Operating System | 🖥️ \`${process.env.RUNNER_OS}\` ${process.env.RUNNER_ARCH || ''} | `;
111
+ }
112
+
113
+ const failures = this.tests
114
+ .filter(t => t.status === 'failed')
115
+ .slice(0, 20)
116
+ .map(t => {
117
+ let text = `#### ${statusEmoji('failed')} ${fullName(t)} `;
118
+ text += '\n\n';
119
+ if (t.message)
120
+ text += `> ${t.message
121
+ .replace(/[^\x20-\x7E]/g, '')
122
+ .replace(ansiRegExp(), '')
123
+ .trim()}\n`;
124
+ if (t.stack) text += `\`\`\`diff\n${t.stack.replace(ansiRegExp(), '').trim()}\n\`\`\`\n`;
125
+
126
+ if (t.artifacts && t.artifacts.length && !process.env.TESTOMATIO_PRIVATE_ARTIFACTS) {
127
+ t.artifacts
128
+ .filter(f => !!f)
129
+ .filter(f => f.endsWith('.png'))
130
+ .forEach(f => {
131
+ if (f.endsWith('.png')) {
132
+ text += `![](${f})\n`;
133
+ return text;
134
+ }
135
+ text += `[📄 ${path.basename(f)}](${f})\n`;
136
+ return text;
137
+ });
138
+ }
139
+
140
+ text += '\n---\n';
141
+
142
+ return text;
143
+ });
144
+
145
+ let body = summary;
146
+
147
+ if (failures.length) {
148
+ body += `\n<details>\n<summary><h3>🟥 Failures (${failures.length})</h4></summary>\n\n${failures.join('\n')}\n`;
149
+ if (failures.length > 20) {
150
+ body += '\n> Notice\n> Only first 20 failures shown*';
151
+ }
152
+ body += '\n\n</details>';
153
+ }
154
+
155
+ if (this.tests.length > 0) {
156
+ body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
157
+ body += this.tests
158
+ // eslint-disable-next-line no-unsafe-optional-chaining
159
+ .sort((a, b) => b?.run_time - a?.run_time)
160
+ .slice(0, 5)
161
+ .map(t => `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`)
162
+ .join('\n');
163
+ body += '\n</details>';
164
+ }
165
+
166
+ await deletePreviousReport(this.octokit, owner, repo, this.issue, this.hiddenCommentData);
167
+
168
+ // add report as comment
169
+ try {
170
+ debug('Adding comment\n', body);
171
+ const resp = await this.octokit.rest.issues.createComment({
172
+ owner,
173
+ repo,
174
+ issue_number: this.issue,
175
+ body,
176
+ });
177
+
178
+ const url = resp.data?.html_url;
179
+ debug('Comment URL:', url);
180
+ this.store.githubUrl = url;
181
+
182
+ console.log(APP_PREFIX, pc.yellow('GitHub'), `Report created: ${pc.magenta(url)}`);
183
+ } catch (err) {
184
+ console.log(APP_PREFIX, pc.yellow('GitHub'), `Couldn't create GitHub report ${err}`);
185
+ }
186
+ }
187
+
188
+ toString() {
189
+ return 'GitHub Reporter';
190
+ }
191
+ }
192
+
193
+ async function deletePreviousReport(octokit, owner, repo, issue, hiddenCommentData) {
194
+ if (process.env.GH_KEEP_OUTDATED_REPORTS) return;
195
+
196
+ // get comments
197
+ let comments = [];
198
+ try {
199
+ const response = await octokit.rest.issues.listComments({
200
+ owner,
201
+ repo,
202
+ issue_number: issue,
203
+ });
204
+ comments = response.data;
205
+ } catch (e) {
206
+ console.error('Error while attempt to retrieve comments on GitHub Pull Request:\n', e);
207
+ }
208
+
209
+ if (!comments.length) return;
210
+
211
+ for (const comment of comments) {
212
+ // if comment was left by the same workflow
213
+ if (comment.body.includes(hiddenCommentData)) {
214
+ try {
215
+ // delete previous comment
216
+ await octokit.rest.issues.deleteComment({
217
+ owner,
218
+ repo,
219
+ issue_number: issue,
220
+ comment_id: comment.id,
221
+ });
222
+ } catch (e) {
223
+ console.warn(`Can't delete previously added comment with testomat.io report. Ignore.`);
224
+ }
225
+
226
+ // pass next env var if need to clear all previous reports;
227
+ // only the last one is removed by default
228
+ if (!process.env.GITHUB_REMOVE_ALL_OUTDATED_REPORTS) break;
229
+ // TODO: in case of many reports should implement pagination
230
+ }
231
+ }
232
+ }
233
+
234
+ export default GitHubPipe;
@@ -0,0 +1,229 @@
1
+ import createDebugMessages from 'debug';
2
+ import axios from 'axios';
3
+ import pc from 'picocolors';
4
+ import humanizeDuration from 'humanize-duration';
5
+ import merge from 'lodash.merge';
6
+ import path from 'path';
7
+ import { APP_PREFIX, testomatLogoURL } from '../constants.js';
8
+ import { ansiRegExp, isSameTest } from '../utils/utils.js';
9
+ import { statusEmoji, fullName } from '../utils/pipe_utils.js';
10
+
11
+ const debug = createDebugMessages('@testomatio/reporter:pipe:gitlab');
12
+
13
+ //! GITLAB_PAT environment variable is required for this functionality to work
14
+ //! and your pipeline trigger should be merge_request
15
+
16
+ /**
17
+ * @class GitLabPipe
18
+ * @typedef {import('../../types').Pipe} Pipe
19
+ * @typedef {import('../../types').TestData} TestData
20
+ */
21
+ class GitLabPipe {
22
+ constructor(params, store = {}) {
23
+ this.isEnabled = false;
24
+ this.ENV = process.env;
25
+ this.store = store;
26
+ this.tests = [];
27
+ // GitLab PAT looks like glpat-nKGdja3jsG4850sGksh7
28
+ this.token = params.GITLAB_PAT || process.env.GITLAB_PAT || this.ENV.GITLAB_PAT;
29
+ this.hiddenCommentData = `<!--- testomat.io report ${process.env.CI_JOB_NAME || ''} -->`;
30
+
31
+ debug(
32
+ pc.yellow('GitLab Pipe:'),
33
+ this.token ? 'TOKEN passed' : '*no token*',
34
+ `Project id: ${this.ENV.CI_PROJECT_ID}, MR id: ${this.ENV.CI_MERGE_REQUEST_IID}`,
35
+ );
36
+
37
+ if (!this.ENV.CI_PROJECT_ID || !this.ENV.CI_MERGE_REQUEST_IID) {
38
+ debug(`CI pipeline should be run in Merge Request to have ability to add the report comment.`);
39
+ return;
40
+ }
41
+
42
+ if (!this.token) {
43
+ debug(`Hint: GitLab CI variables are unavailable for unprotected branches by default.`);
44
+ return;
45
+ }
46
+
47
+ this.isEnabled = true;
48
+
49
+ debug('GitLab Pipe: Enabled');
50
+ }
51
+
52
+ // TODO: to using SET opts as argument => prepareRun(opts)
53
+ async prepareRun() {}
54
+
55
+ async createRun() {}
56
+
57
+ addTest(test) {
58
+ if (!this.isEnabled) return;
59
+
60
+ const index = this.tests.findIndex(t => isSameTest(t, test));
61
+ // update if they were already added
62
+ if (index >= 0) {
63
+ this.tests[index] = merge(this.tests[index], test);
64
+ return;
65
+ }
66
+
67
+ this.tests.push(test);
68
+ }
69
+
70
+ async finishRun(runParams) {
71
+ if (!this.isEnabled) return;
72
+
73
+ if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
74
+
75
+ // ... create a comment on GitLab
76
+ const passedCount = this.tests.filter(t => t.status === 'passed').length;
77
+ const failedCount = this.tests.filter(t => t.status === 'failed').length;
78
+ const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
79
+
80
+ // constructing the table
81
+ let summary = `${this.hiddenCommentData}
82
+
83
+ | [![Testomat.io Report](${testomatLogoURL})](https://testomat.io) | ${statusEmoji(
84
+ runParams.status,
85
+ )} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
86
+ | --- | --- |
87
+ | Tests | ✔️ **${this.tests.length}** tests run |
88
+ | Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
89
+ 'passed',
90
+ )} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
91
+ | Duration | 🕐 **${humanizeDuration(
92
+ parseInt(
93
+ this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
94
+ 10,
95
+ ),
96
+ {
97
+ maxDecimalPoints: 0,
98
+ },
99
+ )}** |
100
+ `;
101
+
102
+ if (this.ENV.CI_JOB_NAME && this.ENV.CI_JOB_ID) {
103
+ // eslint-disable-next-line max-len
104
+ summary += `| Job | 👷 [${this.ENV.CI_JOB_ID}](${this.ENV.CI_JOB_URL})<br>Name: **${this.ENV.CI_JOB_NAME}**<br>Stage: **${this.ENV.CI_JOB_STAGE}** | `;
105
+ }
106
+
107
+ const failures = this.tests
108
+ .filter(t => t.status === 'failed')
109
+ .slice(0, 20)
110
+ .map(t => {
111
+ let text = `#### ${statusEmoji('failed')} ${fullName(t)} `;
112
+ text += '\n\n';
113
+ if (t.message)
114
+ text += `> ${t.message
115
+ .replace(/[^\x20-\x7E]/g, '')
116
+ .replace(ansiRegExp(), '')
117
+ .trim()}\n`;
118
+ if (t.stack) text += `\`\`\`diff\n${t.stack.replace(ansiRegExp(), '').trim()}\n\`\`\`\n`;
119
+
120
+ if (t.artifacts && t.artifacts.length && !this.ENV.TESTOMATIO_PRIVATE_ARTIFACTS) {
121
+ t.artifacts
122
+ .filter(f => !!f)
123
+ .filter(f => f.endsWith('.png'))
124
+ .forEach(f => {
125
+ if (f.endsWith('.png')) {
126
+ text += `![](${f})\n`;
127
+ return text;
128
+ }
129
+ text += `[📄 ${path.basename(f)}](${f})\n`;
130
+ return text;
131
+ });
132
+ }
133
+
134
+ text += '\n---\n';
135
+
136
+ return text;
137
+ });
138
+
139
+ let body = summary;
140
+
141
+ if (failures.length) {
142
+ body += `\n<details>\n<summary><h3>🟥 Failures (${failures.length})</h4></summary>\n\n${failures.join('\n')}\n`;
143
+ if (failures.length > 20) {
144
+ body += '\n> Notice\n> Only first 20 failures shown*';
145
+ }
146
+ body += '\n\n</details>';
147
+ }
148
+
149
+ if (this.tests.length > 0) {
150
+ body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
151
+ body += this.tests
152
+ // eslint-disable-next-line no-unsafe-optional-chaining
153
+ .sort((a, b) => b?.run_time - a?.run_time)
154
+ .slice(0, 5)
155
+ .map(t => `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`)
156
+ .join('\n');
157
+ body += '\n</details>';
158
+ }
159
+
160
+ // eslint-disable-next-line max-len
161
+ const commentsRequestURL = `https://gitlab.com/api/v4/projects/${this.ENV.CI_PROJECT_ID}/merge_requests/${this.ENV.CI_MERGE_REQUEST_IID}/notes`;
162
+
163
+ // delete previous report
164
+ await deletePreviousReport(axios, commentsRequestURL, this.hiddenCommentData, this.token);
165
+
166
+ // add current report
167
+ debug(`Adding comment via url: ${commentsRequestURL}`);
168
+
169
+ try {
170
+ const addCommentResponse = await axios.post(`${commentsRequestURL}?access_token=${this.token}`, { body });
171
+
172
+ const commentID = addCommentResponse.data.id;
173
+ // eslint-disable-next-line max-len
174
+ const commentURL = `${this.ENV.CI_PROJECT_URL}/-/merge_requests/${this.ENV.CI_MERGE_REQUEST_IID}#note_${commentID}`;
175
+
176
+ console.log(APP_PREFIX, pc.yellow('GitLab'), `Report created: ${pc.magenta(commentURL)}`);
177
+ } catch (err) {
178
+ console.error(
179
+ APP_PREFIX,
180
+ pc.yellow('GitLab'),
181
+ `Couldn't create GitLab report\n${err}.
182
+ Request url: ${commentsRequestURL}
183
+ Request data: ${body}`,
184
+ );
185
+ }
186
+ }
187
+
188
+ toString() {
189
+ return 'GitLab Reporter';
190
+ }
191
+
192
+ updateRun() {}
193
+ }
194
+
195
+ async function deletePreviousReport(axiosInstance, commentsRequestURL, hiddenCommentData, token) {
196
+ if (process.env.GITLAB_KEEP_OUTDATED_REPORTS) return;
197
+
198
+ // get comments
199
+ let comments = [];
200
+
201
+ try {
202
+ const response = await axiosInstance.get(`${commentsRequestURL}?access_token=${token}`);
203
+ comments = response.data;
204
+ } catch (e) {
205
+ console.error('Error while attempt to retrieve comments on GitLab Merge Request:\n', e);
206
+ }
207
+
208
+ if (!comments.length) return;
209
+
210
+ for (const comment of comments) {
211
+ // if comment was left by the same workflow
212
+ if (comment.body.includes(hiddenCommentData)) {
213
+ try {
214
+ // delete previous comment
215
+ const deleteCommentURL = `${commentsRequestURL}/${comment.id}?access_token=${token}`;
216
+ await axiosInstance.delete(deleteCommentURL);
217
+ } catch (e) {
218
+ console.warn(`Can't delete previously added comment with testomat.io report. Ignore.`);
219
+ }
220
+
221
+ // pass next env var if need to clear all previous reports;
222
+ // only the last one is removed by default
223
+ if (!process.env.GITLAB_REMOVE_ALL_OUTDATED_REPORTS) break;
224
+ // TODO: in case of many reports should implement pagination
225
+ }
226
+ }
227
+ }
228
+
229
+ export default GitLabPipe;