monocart-reporter 2.9.6 → 2.9.7

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/lib/merge-data.js CHANGED
@@ -1,405 +1,405 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const Util = require('./utils/util.js');
4
- const generateReport = require('./generate-report.js');
5
- const getDefaultOptions = require('./default/options.js');
6
- const { calculateSummary, getTrends } = require('./common.js');
7
- const { mergeGlobalCoverageReport } = require('./plugins/coverage/coverage.js');
8
- const { StreamZip } = require('./packages/monocart-reporter-vendor.js');
9
-
10
- const checkReportData = (item) => {
11
- if (item && typeof item === 'object') {
12
- if (item.rows && item.columns && item.summary) {
13
- return true;
14
- }
15
- }
16
- };
17
-
18
- const unzipDataFile = async (item, num, options) => {
19
-
20
- if (!options.cacheDir) {
21
- options.cacheDir = path.resolve(options.outputDir, '.cache');
22
- }
23
-
24
- const unzipDir = path.resolve(options.cacheDir, `extracted-${num}`);
25
-
26
- fs.mkdirSync(unzipDir, {
27
- recursive: true
28
- });
29
-
30
- const zip = new StreamZip({
31
- file: item
32
- });
33
-
34
- await zip.extract(null, unzipDir);
35
-
36
- // Do not forget to close the file once you're done
37
- await zip.close();
38
-
39
- const filename = path.basename(item, '.zip');
40
- let jsonPath = path.resolve(unzipDir, `${filename}.json`);
41
- if (fs.existsSync(jsonPath)) {
42
- return jsonPath;
43
- }
44
-
45
- // filename for both html and json
46
- const htmlMap = {};
47
- const jsonMap = {};
48
- Util.forEachFile(unzipDir, (name, dir) => {
49
- if (name.endsWith('.html')) {
50
- htmlMap[path.basename(name, '.html')] = true;
51
- } else if (name.endsWith('.json')) {
52
- jsonMap[path.basename(name, '.json')] = true;
53
- }
54
- }, true);
55
-
56
- const keys = Object.keys(htmlMap);
57
- for (const fn of keys) {
58
- if (jsonMap[fn]) {
59
- jsonPath = path.resolve(unzipDir, `${fn}.json`);
60
- break;
61
- }
62
- }
63
-
64
- return jsonPath;
65
- };
66
-
67
- const getReportData = async (item, num, options) => {
68
- if (typeof item === 'string') {
69
-
70
- // json or zip path
71
- const extname = path.extname(item);
72
- if (extname === '.zip') {
73
- item = await unzipDataFile(item, num, options);
74
- // console.log(item);
75
- }
76
-
77
- // json path
78
- const data = Util.readJSONSync(item);
79
- if (!data) {
80
- Util.logError(`failed to read report data file: ${item}`);
81
- return;
82
- }
83
-
84
- // for finding attachments
85
- const jsonDir = Util.relativePath(path.dirname(item));
86
-
87
- Util.logInfo(`report data loaded: ${Util.relativePath(item)}`);
88
- return {
89
- jsonDir,
90
- data
91
- };
92
- }
93
-
94
- if (!checkReportData(item)) {
95
- Util.logError(`unmatched report data format: ${item}`);
96
- return;
97
- }
98
-
99
- // json
100
- return {
101
- jsonDir: item.outputDir,
102
- data: item
103
- };
104
- };
105
-
106
- const initDataList = async (reportDataList, options) => {
107
- if (!Util.isList(reportDataList)) {
108
- Util.logError(`invalid report data list: ${reportDataList}`);
109
- return;
110
- }
111
-
112
- let hasError = false;
113
- const list = [];
114
-
115
- let num = 1;
116
- for (const item of reportDataList) {
117
-
118
- const data = await getReportData(item, num, options);
119
- if (!data) {
120
- hasError = true;
121
- break;
122
- }
123
- num += 1;
124
-
125
- list.push(data);
126
- }
127
-
128
- if (hasError) {
129
- return;
130
- }
131
-
132
- // sort list
133
-
134
- list.sort((a, b) => {
135
- if (a.jsonDir > b.jsonDir) {
136
- return 1;
137
- }
138
- return -1;
139
- });
140
-
141
- // console.log(list.map((it) => it.jsonDir));
142
-
143
- return list;
144
- };
145
-
146
- const copyTarget = (targetPath, fromDir, toDir) => {
147
- const oldPath = path.resolve(fromDir, targetPath);
148
- if (fs.existsSync(oldPath)) {
149
- const newPath = path.resolve(toDir, targetPath);
150
- if (oldPath !== newPath) {
151
- fs.cpSync(oldPath, newPath, {
152
- force: true,
153
- recursive: true
154
- });
155
- return true;
156
- }
157
- }
158
- };
159
-
160
- const attachmentsHandler = (data, jsonDir, outputDir, attachmentPathHandler) => {
161
-
162
- const extras = Util.getAttachmentPathExtras(data);
163
- Util.forEach(data.rows, (row) => {
164
- if (row.type !== 'case' || !row.attachments) {
165
- return;
166
- }
167
- row.attachments.forEach((attachment) => {
168
- if (!attachment.path) {
169
- return;
170
- }
171
-
172
- // copy attachment
173
- const done = copyTarget(attachment.path, jsonDir, outputDir);
174
- if (!done) {
175
- Util.logError(`failed to copy: ${attachment.path}`);
176
- }
177
-
178
- if (attachmentPathHandler) {
179
- const newPath = attachmentPathHandler(attachment.path, extras);
180
- if (newPath) {
181
- attachment.path = newPath;
182
- }
183
- }
184
- });
185
- });
186
- };
187
-
188
- const mergeArtifacts = async (artifactsList, options) => {
189
-
190
- const outputDir = options.outputDir;
191
-
192
- const artifacts = [];
193
- const coverageList = [];
194
- const coverageRawList = [];
195
-
196
- const jsonDirMap = {};
197
-
198
- artifactsList.forEach((item) => {
199
- const jsonDir = item.jsonDir;
200
- let hasAssets = false;
201
- item.artifacts.forEach((art) => {
202
-
203
- // merge global coverage
204
- if (art.global && art.type === 'coverage') {
205
- coverageList.push({
206
- jsonDir,
207
- art
208
- });
209
- if (art.rawDir) {
210
- const rawDir = path.resolve(jsonDir, art.rawDir);
211
- if (fs.existsSync(rawDir)) {
212
- coverageRawList.push(rawDir);
213
- }
214
- }
215
- return;
216
- }
217
-
218
- const targetDirName = path.dirname(art.path);
219
- // copy all files in art dir, like network, audit
220
- copyTarget(targetDirName, jsonDir, outputDir);
221
- hasAssets = true;
222
-
223
- // update path relative to cwd/root
224
- art.path = Util.relativePath(path.resolve(outputDir, art.path));
225
-
226
- artifacts.push(art);
227
-
228
- });
229
-
230
- // copy assets dir
231
- if (hasAssets) {
232
- copyTarget('assets', jsonDir, outputDir);
233
- jsonDirMap[jsonDir] = true;
234
- }
235
-
236
- });
237
-
238
- // merge global coverage raws
239
- if (coverageRawList.length) {
240
- const coverage = await mergeGlobalCoverageReport(coverageRawList, options);
241
- if (coverage) {
242
- artifacts.push(coverage);
243
- }
244
- } else {
245
-
246
- // copy single one art
247
- const item = coverageList.find((it) => it.art.path);
248
- if (item) {
249
- const { art, jsonDir } = item;
250
-
251
- const targetDirName = path.dirname(art.path);
252
- // copy all files in art dir, like network, audit
253
- copyTarget(targetDirName, jsonDir, outputDir);
254
-
255
- if (!jsonDirMap[jsonDir]) {
256
- copyTarget('assets', jsonDir, outputDir);
257
- }
258
-
259
- // update path relative to cwd/root
260
- art.path = Util.relativePath(path.resolve(outputDir, art.path));
261
-
262
- artifacts.push(art);
263
- }
264
- }
265
-
266
- return artifacts;
267
- };
268
-
269
- const mergeDataList = async (dataList, options) => {
270
-
271
- const { outputFile, outputDir } = options;
272
-
273
- const trends = await getTrends(options.trend);
274
-
275
- const attachmentPathHandler = typeof options.attachmentPath === 'function' ? options.attachmentPath : null;
276
-
277
- const startDates = [];
278
- const dateRanges = [];
279
- const metadata = {};
280
- const system = [];
281
- const rows = [];
282
- const artifactsList = [];
283
-
284
- for (const dataItem of dataList) {
285
-
286
- const { data, jsonDir } = dataItem;
287
-
288
- attachmentsHandler(data, jsonDir, outputDir, attachmentPathHandler);
289
-
290
- startDates.push(data.date);
291
- dateRanges.push({
292
- start: data.date,
293
- end: data.date + data.duration
294
- });
295
-
296
- // merge metadata (may collect from diff shard)
297
- Object.assign(metadata, data.metadata);
298
-
299
- // merge system
300
- system.push(data.system);
301
-
302
- // merge rows
303
- rows.push({
304
- // add shard level suite
305
- title: data.system.hostname,
306
- type: 'suite',
307
- suiteType: 'shard',
308
- caseNum: data.summary.tests.value,
309
- subs: data.rows
310
- });
311
-
312
- if (data.artifacts) {
313
- artifactsList.push({
314
- jsonDir,
315
- artifacts: data.artifacts
316
- });
317
- }
318
-
319
- }
320
-
321
- const artifacts = await mergeArtifacts(artifactsList, options);
322
-
323
- // base on first one, do not change dataList (need for onData)
324
- const mergedData = {
325
- ... dataList[0].data
326
- };
327
-
328
- // merge new options
329
- ['traceViewerUrl', 'mermaid'].forEach((k) => {
330
- if (Util.hasOwn(options, k)) {
331
- mergedData[k] = options[k];
332
- }
333
- });
334
-
335
- const reportName = options.name || mergedData.name;
336
-
337
- const date = Math.min.apply(null, startDates);
338
- const dateH = new Date(date).toLocaleString();
339
-
340
- const duration = Util.getDuration(dateRanges, options.durationStrategy);
341
- const durationH = Util.TF(duration);
342
-
343
- Object.assign(mergedData, {
344
- name: reportName,
345
-
346
- date,
347
- dateH,
348
- duration,
349
- durationH,
350
-
351
- // for current merge process
352
- cwd: Util.formatPath(process.cwd()),
353
- outputFile: Util.relativePath(outputFile),
354
- outputDir: Util.relativePath(outputDir),
355
-
356
- metadata,
357
- // system is array in shard mode, because have multiple machines
358
- system,
359
- trends,
360
-
361
- rows,
362
-
363
- artifacts
364
- });
365
-
366
- // tag style can be rewrite with new options tags
367
- options.tags = options.tags || mergedData.tags;
368
- calculateSummary(mergedData, options);
369
-
370
- return mergedData;
371
- };
372
-
373
- module.exports = async (reportDataList, userOptions = {}) => {
374
-
375
- Util.initLoggingLevel(userOptions.logging, 'merge');
376
-
377
- Util.logInfo('merging report data ...');
378
-
379
- const options = {
380
- ... getDefaultOptions(),
381
- ... userOptions
382
- };
383
-
384
- // init options
385
- const outputFile = Util.resolveOutputFile(options.outputFile);
386
- options.outputFile = outputFile;
387
- // init outputDir
388
- const outputDir = path.dirname(outputFile);
389
- // clean
390
- Util.initDir(outputDir);
391
- // for attachment path handler
392
- options.outputDir = outputDir;
393
-
394
- const dataList = await initDataList(reportDataList, options);
395
- if (!dataList) {
396
- return;
397
- }
398
-
399
- const reportData = await mergeDataList(dataList, options);
400
- // console.log(reportData.artifacts);
401
-
402
- const rawData = dataList.map((it) => it.data);
403
- return generateReport(reportData, options, rawData);
404
-
405
- };
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const Util = require('./utils/util.js');
4
+ const generateReport = require('./generate-report.js');
5
+ const getDefaultOptions = require('./default/options.js');
6
+ const { calculateSummary, getTrends } = require('./common.js');
7
+ const { mergeGlobalCoverageReport } = require('./plugins/coverage/coverage.js');
8
+ const { StreamZip } = require('./packages/monocart-reporter-vendor.js');
9
+
10
+ const checkReportData = (item) => {
11
+ if (item && typeof item === 'object') {
12
+ if (item.rows && item.columns && item.summary) {
13
+ return true;
14
+ }
15
+ }
16
+ };
17
+
18
+ const unzipDataFile = async (item, num, options) => {
19
+
20
+ if (!options.cacheDir) {
21
+ options.cacheDir = path.resolve(options.outputDir, '.cache');
22
+ }
23
+
24
+ const unzipDir = path.resolve(options.cacheDir, `extracted-${num}`);
25
+
26
+ fs.mkdirSync(unzipDir, {
27
+ recursive: true
28
+ });
29
+
30
+ const zip = new StreamZip({
31
+ file: item
32
+ });
33
+
34
+ await zip.extract(null, unzipDir);
35
+
36
+ // Do not forget to close the file once you're done
37
+ await zip.close();
38
+
39
+ const filename = path.basename(item, '.zip');
40
+ let jsonPath = path.resolve(unzipDir, `${filename}.json`);
41
+ if (fs.existsSync(jsonPath)) {
42
+ return jsonPath;
43
+ }
44
+
45
+ // filename for both html and json
46
+ const htmlMap = {};
47
+ const jsonMap = {};
48
+ Util.forEachFile(unzipDir, (name, dir) => {
49
+ if (name.endsWith('.html')) {
50
+ htmlMap[path.basename(name, '.html')] = true;
51
+ } else if (name.endsWith('.json')) {
52
+ jsonMap[path.basename(name, '.json')] = true;
53
+ }
54
+ }, true);
55
+
56
+ const keys = Object.keys(htmlMap);
57
+ for (const fn of keys) {
58
+ if (jsonMap[fn]) {
59
+ jsonPath = path.resolve(unzipDir, `${fn}.json`);
60
+ break;
61
+ }
62
+ }
63
+
64
+ return jsonPath;
65
+ };
66
+
67
+ const getReportData = async (item, num, options) => {
68
+ if (typeof item === 'string') {
69
+
70
+ // json or zip path
71
+ const extname = path.extname(item);
72
+ if (extname === '.zip') {
73
+ item = await unzipDataFile(item, num, options);
74
+ // console.log(item);
75
+ }
76
+
77
+ // json path
78
+ const data = Util.readJSONSync(item);
79
+ if (!data) {
80
+ Util.logError(`failed to read report data file: ${item}`);
81
+ return;
82
+ }
83
+
84
+ // for finding attachments
85
+ const jsonDir = Util.relativePath(path.dirname(item));
86
+
87
+ Util.logInfo(`report data loaded: ${Util.relativePath(item)}`);
88
+ return {
89
+ jsonDir,
90
+ data
91
+ };
92
+ }
93
+
94
+ if (!checkReportData(item)) {
95
+ Util.logError(`unmatched report data format: ${item}`);
96
+ return;
97
+ }
98
+
99
+ // json
100
+ return {
101
+ jsonDir: item.outputDir,
102
+ data: item
103
+ };
104
+ };
105
+
106
+ const initDataList = async (reportDataList, options) => {
107
+ if (!Util.isList(reportDataList)) {
108
+ Util.logError(`invalid report data list: ${reportDataList}`);
109
+ return;
110
+ }
111
+
112
+ let hasError = false;
113
+ const list = [];
114
+
115
+ let num = 1;
116
+ for (const item of reportDataList) {
117
+
118
+ const data = await getReportData(item, num, options);
119
+ if (!data) {
120
+ hasError = true;
121
+ break;
122
+ }
123
+ num += 1;
124
+
125
+ list.push(data);
126
+ }
127
+
128
+ if (hasError) {
129
+ return;
130
+ }
131
+
132
+ // sort list
133
+
134
+ list.sort((a, b) => {
135
+ if (a.jsonDir > b.jsonDir) {
136
+ return 1;
137
+ }
138
+ return -1;
139
+ });
140
+
141
+ // console.log(list.map((it) => it.jsonDir));
142
+
143
+ return list;
144
+ };
145
+
146
+ const copyTarget = (targetPath, fromDir, toDir) => {
147
+ const oldPath = path.resolve(fromDir, targetPath);
148
+ if (fs.existsSync(oldPath)) {
149
+ const newPath = path.resolve(toDir, targetPath);
150
+ if (oldPath !== newPath) {
151
+ fs.cpSync(oldPath, newPath, {
152
+ force: true,
153
+ recursive: true
154
+ });
155
+ return true;
156
+ }
157
+ }
158
+ };
159
+
160
+ const attachmentsHandler = (data, jsonDir, outputDir, attachmentPathHandler) => {
161
+
162
+ const extras = Util.getAttachmentPathExtras(data);
163
+ Util.forEach(data.rows, (row) => {
164
+ if (row.type !== 'case' || !row.attachments) {
165
+ return;
166
+ }
167
+ row.attachments.forEach((attachment) => {
168
+ if (!attachment.path) {
169
+ return;
170
+ }
171
+
172
+ // copy attachment
173
+ const done = copyTarget(attachment.path, jsonDir, outputDir);
174
+ if (!done) {
175
+ Util.logError(`failed to copy: ${attachment.path}`);
176
+ }
177
+
178
+ if (attachmentPathHandler) {
179
+ const newPath = attachmentPathHandler(attachment.path, extras);
180
+ if (newPath) {
181
+ attachment.path = newPath;
182
+ }
183
+ }
184
+ });
185
+ });
186
+ };
187
+
188
+ const mergeArtifacts = async (artifactsList, options) => {
189
+
190
+ const outputDir = options.outputDir;
191
+
192
+ const artifacts = [];
193
+ const coverageList = [];
194
+ const coverageRawList = [];
195
+
196
+ const jsonDirMap = {};
197
+
198
+ artifactsList.forEach((item) => {
199
+ const jsonDir = item.jsonDir;
200
+ let hasAssets = false;
201
+ item.artifacts.forEach((art) => {
202
+
203
+ // merge global coverage
204
+ if (art.global && art.type === 'coverage') {
205
+ coverageList.push({
206
+ jsonDir,
207
+ art
208
+ });
209
+ if (art.rawDir) {
210
+ const rawDir = path.resolve(jsonDir, art.rawDir);
211
+ if (fs.existsSync(rawDir)) {
212
+ coverageRawList.push(rawDir);
213
+ }
214
+ }
215
+ return;
216
+ }
217
+
218
+ const targetDirName = path.dirname(art.path);
219
+ // copy all files in art dir, like network, audit
220
+ copyTarget(targetDirName, jsonDir, outputDir);
221
+ hasAssets = true;
222
+
223
+ // update path relative to cwd/root
224
+ art.path = Util.relativePath(path.resolve(outputDir, art.path));
225
+
226
+ artifacts.push(art);
227
+
228
+ });
229
+
230
+ // copy assets dir
231
+ if (hasAssets) {
232
+ copyTarget('assets', jsonDir, outputDir);
233
+ jsonDirMap[jsonDir] = true;
234
+ }
235
+
236
+ });
237
+
238
+ // merge global coverage raws
239
+ if (coverageRawList.length) {
240
+ const coverage = await mergeGlobalCoverageReport(coverageRawList, options);
241
+ if (coverage) {
242
+ artifacts.push(coverage);
243
+ }
244
+ } else {
245
+
246
+ // copy single one art
247
+ const item = coverageList.find((it) => it.art.path);
248
+ if (item) {
249
+ const { art, jsonDir } = item;
250
+
251
+ const targetDirName = path.dirname(art.path);
252
+ // copy all files in art dir, like network, audit
253
+ copyTarget(targetDirName, jsonDir, outputDir);
254
+
255
+ if (!jsonDirMap[jsonDir]) {
256
+ copyTarget('assets', jsonDir, outputDir);
257
+ }
258
+
259
+ // update path relative to cwd/root
260
+ art.path = Util.relativePath(path.resolve(outputDir, art.path));
261
+
262
+ artifacts.push(art);
263
+ }
264
+ }
265
+
266
+ return artifacts;
267
+ };
268
+
269
+ const mergeDataList = async (dataList, options) => {
270
+
271
+ const { outputFile, outputDir } = options;
272
+
273
+ const trends = await getTrends(options.trend);
274
+
275
+ const attachmentPathHandler = typeof options.attachmentPath === 'function' ? options.attachmentPath : null;
276
+
277
+ const startDates = [];
278
+ const dateRanges = [];
279
+ const metadata = {};
280
+ const system = [];
281
+ const rows = [];
282
+ const artifactsList = [];
283
+
284
+ for (const dataItem of dataList) {
285
+
286
+ const { data, jsonDir } = dataItem;
287
+
288
+ attachmentsHandler(data, jsonDir, outputDir, attachmentPathHandler);
289
+
290
+ startDates.push(data.date);
291
+ dateRanges.push({
292
+ start: data.date,
293
+ end: data.date + data.duration
294
+ });
295
+
296
+ // merge metadata (may collect from diff shard)
297
+ Object.assign(metadata, data.metadata);
298
+
299
+ // merge system
300
+ system.push(data.system);
301
+
302
+ // merge rows
303
+ rows.push({
304
+ // add shard level suite
305
+ title: data.system.hostname,
306
+ type: 'suite',
307
+ suiteType: 'shard',
308
+ caseNum: data.summary.tests.value,
309
+ subs: data.rows
310
+ });
311
+
312
+ if (data.artifacts) {
313
+ artifactsList.push({
314
+ jsonDir,
315
+ artifacts: data.artifacts
316
+ });
317
+ }
318
+
319
+ }
320
+
321
+ const artifacts = await mergeArtifacts(artifactsList, options);
322
+
323
+ // base on first one, do not change dataList (need for onData)
324
+ const mergedData = {
325
+ ... dataList[0].data
326
+ };
327
+
328
+ // merge new options
329
+ ['traceViewerUrl', 'mermaid'].forEach((k) => {
330
+ if (Util.hasOwn(options, k)) {
331
+ mergedData[k] = options[k];
332
+ }
333
+ });
334
+
335
+ const reportName = options.name || mergedData.name;
336
+
337
+ const date = Math.min.apply(null, startDates);
338
+ const dateH = new Date(date).toLocaleString();
339
+
340
+ const duration = Util.getDuration(dateRanges, options.durationStrategy);
341
+ const durationH = Util.TF(duration);
342
+
343
+ Object.assign(mergedData, {
344
+ name: reportName,
345
+
346
+ date,
347
+ dateH,
348
+ duration,
349
+ durationH,
350
+
351
+ // for current merge process
352
+ cwd: Util.formatPath(process.cwd()),
353
+ outputFile: Util.relativePath(outputFile),
354
+ outputDir: Util.relativePath(outputDir),
355
+
356
+ metadata,
357
+ // system is array in shard mode, because have multiple machines
358
+ system,
359
+ trends,
360
+
361
+ rows,
362
+
363
+ artifacts
364
+ });
365
+
366
+ // tag style can be rewrite with new options tags
367
+ options.tags = options.tags || mergedData.tags;
368
+ calculateSummary(mergedData, options);
369
+
370
+ return mergedData;
371
+ };
372
+
373
+ module.exports = async (reportDataList, userOptions = {}) => {
374
+
375
+ Util.initLoggingLevel(userOptions.logging, 'merge');
376
+
377
+ Util.logInfo('merging report data ...');
378
+
379
+ const options = {
380
+ ... getDefaultOptions(),
381
+ ... userOptions
382
+ };
383
+
384
+ // init options
385
+ const outputFile = Util.resolveOutputFile(options.outputFile);
386
+ options.outputFile = outputFile;
387
+ // init outputDir
388
+ const outputDir = path.dirname(outputFile);
389
+ // clean
390
+ Util.initDir(outputDir);
391
+ // for attachment path handler
392
+ options.outputDir = outputDir;
393
+
394
+ const dataList = await initDataList(reportDataList, options);
395
+ if (!dataList) {
396
+ return;
397
+ }
398
+
399
+ const reportData = await mergeDataList(dataList, options);
400
+ // console.log(reportData.artifacts);
401
+
402
+ const rawData = dataList.map((it) => it.data);
403
+ return generateReport(reportData, options, rawData);
404
+
405
+ };