@moonrepo/report 0.0.1

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 ADDED
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 moonrepo LLC, Miles Johnson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction,
7
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
8
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
15
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
17
+ OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @moonrepo/report
2
+
3
+ ![build status](https://img.shields.io/github/workflow/status/moonrepo/moon/Moon)
4
+ ![npm version](https://img.shields.io/npm/v/@moonrepo/report)
5
+ ![npm license](https://img.shields.io/npm/l/@moonrepo/report)
6
+
7
+ Utilities for working with moon run reports.
@@ -0,0 +1,6 @@
1
+ // Bundled with Packemon: https://packemon.dev
2
+ // This is an MJS wrapper for a sibling CJS file
3
+
4
+ import data from './index.cjs';
5
+
6
+ export const { formatDuration, formatTime, getDurationInMillis, getIconForStatus, hasFailed, hasPassed, isFlaky, isSlow, prepareReportActions, sortReport } = data;
package/cjs/index.cjs ADDED
@@ -0,0 +1,169 @@
1
+ // Bundled with Packemon: https://packemon.dev
2
+ // Platform: node, Support: stable, Format: cjs
3
+
4
+ 'use strict';
5
+
6
+ Object.defineProperty(exports, '__esModule', {
7
+ value: true
8
+ });
9
+ function getDurationInMillis(duration) {
10
+ return duration.secs * 1000 + duration.nanos / 1_000_000;
11
+ }
12
+ function formatTime(mins, secs, millis) {
13
+ if (mins === 0 && secs === 0 && millis === 0) {
14
+ return '0s';
15
+ }
16
+ const format = val => {
17
+ let v = val.toFixed(1);
18
+ if (v.endsWith('.0')) {
19
+ v = v.slice(0, -2);
20
+ }
21
+ return v;
22
+ };
23
+
24
+ // When minutes, only show mins + secs
25
+ if (mins > 0) {
26
+ let value = `${mins}m`;
27
+ if (secs > 0) {
28
+ value += ` ${secs}s`;
29
+ }
30
+ return value;
31
+ }
32
+
33
+ // When seconds, only show secs + first milli digit
34
+ if (secs > 0) {
35
+ return `${format((secs * 1000 + millis) / 1000)}s`;
36
+ }
37
+
38
+ // When millis, show as is
39
+ if (millis > 0) {
40
+ return `${format(millis)}ms`;
41
+ }
42
+
43
+ // How did we get here?
44
+ return '0s';
45
+ }
46
+ function formatDuration(duration) {
47
+ if (!duration) {
48
+ return '--';
49
+ }
50
+ if (duration.secs === 0 && duration.nanos === 0) {
51
+ return '0s';
52
+ }
53
+ let mins = 0;
54
+ let secs = duration.secs;
55
+ const millis = duration.nanos / 1_000_000;
56
+ while (secs >= 60) {
57
+ mins += 1;
58
+ secs -= 60;
59
+ }
60
+ return formatTime(mins, secs, millis);
61
+ }
62
+ function getIconForStatus(status) {
63
+ // Use exhaustive checks!
64
+ // eslint-disable-next-line default-case
65
+ switch (status) {
66
+ case 'cached':
67
+ return '🟪';
68
+ // case 'cached-remote':
69
+ // return '🟦';
70
+ case 'failed':
71
+ case 'failed-and-abort':
72
+ return '🟥';
73
+ case 'invalid':
74
+ return '🟨';
75
+ case 'passed':
76
+ return '🟩';
77
+ case 'running':
78
+ case 'skipped':
79
+ return '⬛️';
80
+ }
81
+ return '⬜️';
82
+ }
83
+ function hasFailed(status) {
84
+ return status === 'failed' || status === 'failed-and-abort';
85
+ }
86
+ function hasPassed(status) {
87
+ return status === 'passed' || status === 'cached';
88
+ }
89
+ function isFlaky(action) {
90
+ if (action.flaky) {
91
+ return true;
92
+ }
93
+
94
+ // The flaky field above didn't always exist!
95
+ if (!action.attempts || action.attempts.length === 0) {
96
+ return false;
97
+ }
98
+ return hasPassed(action.status) && action.attempts.some(attempt => hasFailed(attempt.status));
99
+ }
100
+ function isSlow(action, slowThreshold) {
101
+ if (!action.duration) {
102
+ return false;
103
+ }
104
+ const millis = getDurationInMillis(action.duration);
105
+ const threshold = slowThreshold * 1000; // In seconds
106
+
107
+ return millis > threshold;
108
+ }
109
+ function sortReport(report, sortBy, sortDir) {
110
+ const isAsc = sortDir === 'asc';
111
+ report.actions.sort((a, d) => {
112
+ switch (sortBy) {
113
+ case 'time':
114
+ {
115
+ const am = getDurationInMillis(a.duration ?? {
116
+ nanos: 0,
117
+ secs: 0
118
+ });
119
+ const dm = getDurationInMillis(d.duration ?? {
120
+ nanos: 0,
121
+ secs: 0
122
+ });
123
+ return isAsc ? am - dm : dm - am;
124
+ }
125
+ case 'label':
126
+ {
127
+ const al = a.label ?? '';
128
+ const dl = d.label ?? '';
129
+ return isAsc ? al.localeCompare(dl) : dl.localeCompare(al);
130
+ }
131
+ default:
132
+ {
133
+ throw new Error(`Unknown sort by "${sortBy}".`);
134
+ }
135
+ }
136
+ });
137
+ }
138
+ function prepareReportActions(report, slowThreshold) {
139
+ return report.actions.map(action => {
140
+ const comments = [];
141
+ if (isFlaky(action)) {
142
+ comments.push('**FLAKY**');
143
+ }
144
+ if (action.attempts && action.attempts.length > 1) {
145
+ comments.push(`${action.attempts.length} attempts`);
146
+ }
147
+ if (isSlow(action, slowThreshold)) {
148
+ comments.push('**SLOW**');
149
+ }
150
+ return {
151
+ comments,
152
+ duration: action.duration,
153
+ icon: getIconForStatus(action.status),
154
+ label: action.label ?? '<unknown>',
155
+ time: formatDuration(action.duration)
156
+ };
157
+ });
158
+ }
159
+ exports.formatDuration = formatDuration;
160
+ exports.formatTime = formatTime;
161
+ exports.getDurationInMillis = getDurationInMillis;
162
+ exports.getIconForStatus = getIconForStatus;
163
+ exports.hasFailed = hasFailed;
164
+ exports.hasPassed = hasPassed;
165
+ exports.isFlaky = isFlaky;
166
+ exports.isSlow = isSlow;
167
+ exports.prepareReportActions = prepareReportActions;
168
+ exports.sortReport = sortReport;
169
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/time.ts","../src/action.ts","../src/report.ts"],"sourcesContent":null,"names":["getDurationInMillis","duration","secs","nanos","formatTime","mins","millis","format","val","v","toFixed","endsWith","slice","value","formatDuration","getIconForStatus","status","hasFailed","hasPassed","isFlaky","action","flaky","attempts","length","some","attempt","isSlow","slowThreshold","threshold","sortReport","report","sortBy","sortDir","isAsc","actions","sort","a","d","am","dm","al","label","dl","localeCompare","Error","prepareReportActions","map","comments","push","icon","time"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Action, ActionStatus } from '@moonrepo/types';
2
+ export declare function getIconForStatus(status: ActionStatus): string;
3
+ export declare function hasFailed(status: ActionStatus): boolean;
4
+ export declare function hasPassed(status: ActionStatus): boolean;
5
+ export declare function isFlaky(action: Action): boolean;
6
+ export declare function isSlow(action: Action, slowThreshold: number): boolean;
7
+ //# sourceMappingURL=action.d.ts.map
package/dts/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './action';
2
+ export * from './report';
3
+ export * from './time';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { Duration, RunReport } from '@moonrepo/types';
2
+ export declare function sortReport(report: RunReport, sortBy: 'label' | 'time', sortDir: 'asc' | 'desc'): void;
3
+ export interface PreparedAction {
4
+ comments: string[];
5
+ duration: Duration | null;
6
+ icon: string;
7
+ label: string;
8
+ time: string;
9
+ }
10
+ export declare function prepareReportActions(report: RunReport, slowThreshold: number): PreparedAction[];
11
+ //# sourceMappingURL=report.d.ts.map
package/dts/time.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { Duration } from '@moonrepo/types';
2
+ export declare function getDurationInMillis(duration: Duration): number;
3
+ export declare function formatTime(mins: number, secs: number, millis: number): string;
4
+ export declare function formatDuration(duration: Duration | null): string;
5
+ //# sourceMappingURL=time.d.ts.map
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@moonrepo/report",
3
+ "version": "0.0.1",
4
+ "type": "commonjs",
5
+ "description": "Utilities working with moon run reports.",
6
+ "keywords": [
7
+ "moon",
8
+ "repo",
9
+ "report",
10
+ "utils"
11
+ ],
12
+ "author": "Miles Johnson",
13
+ "license": "MIT",
14
+ "main": "./cjs/index.cjs",
15
+ "types": "./dts/index.d.ts",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/moonrepo/moon",
19
+ "directory": "packages/report"
20
+ },
21
+ "files": [
22
+ "cjs/**/*.{cjs,mjs,map}",
23
+ "dts/**/*.d.ts",
24
+ "src/**/*.{ts,tsx,json}"
25
+ ],
26
+ "packemon": {
27
+ "format": "cjs",
28
+ "platform": "node",
29
+ "bundle": true
30
+ },
31
+ "dependencies": {
32
+ "@moonrepo/types": "^0.0.5"
33
+ },
34
+ "engines": {
35
+ "node": ">=14.15.0",
36
+ "npm": ">=6.14.0"
37
+ },
38
+ "exports": {
39
+ "./package.json": "./package.json",
40
+ ".": {
41
+ "types": "./dts/index.d.ts",
42
+ "node": {
43
+ "import": "./cjs/index-wrapper.mjs",
44
+ "require": "./cjs/index.cjs"
45
+ }
46
+ }
47
+ }
48
+ }
package/src/action.ts ADDED
@@ -0,0 +1,57 @@
1
+ import { Action, ActionStatus } from '@moonrepo/types';
2
+ import { getDurationInMillis } from './time';
3
+
4
+ export function getIconForStatus(status: ActionStatus): string {
5
+ // Use exhaustive checks!
6
+ // eslint-disable-next-line default-case
7
+ switch (status) {
8
+ case 'cached':
9
+ return '🟪';
10
+ // case 'cached-remote':
11
+ // return '🟦';
12
+ case 'failed':
13
+ case 'failed-and-abort':
14
+ return '🟥';
15
+ case 'invalid':
16
+ return '🟨';
17
+ case 'passed':
18
+ return '🟩';
19
+ case 'running':
20
+ case 'skipped':
21
+ return '⬛️';
22
+ }
23
+
24
+ return '⬜️';
25
+ }
26
+
27
+ export function hasFailed(status: ActionStatus): boolean {
28
+ return status === 'failed' || status === 'failed-and-abort';
29
+ }
30
+
31
+ export function hasPassed(status: ActionStatus): boolean {
32
+ return status === 'passed' || status === 'cached';
33
+ }
34
+
35
+ export function isFlaky(action: Action): boolean {
36
+ if (action.flaky) {
37
+ return true;
38
+ }
39
+
40
+ // The flaky field above didn't always exist!
41
+ if (!action.attempts || action.attempts.length === 0) {
42
+ return false;
43
+ }
44
+
45
+ return hasPassed(action.status) && action.attempts.some((attempt) => hasFailed(attempt.status));
46
+ }
47
+
48
+ export function isSlow(action: Action, slowThreshold: number): boolean {
49
+ if (!action.duration) {
50
+ return false;
51
+ }
52
+
53
+ const millis = getDurationInMillis(action.duration);
54
+ const threshold = slowThreshold * 1000; // In seconds
55
+
56
+ return millis > threshold;
57
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './action';
2
+ export * from './report';
3
+ export * from './time';
package/src/report.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { Duration, RunReport } from '@moonrepo/types';
2
+ import { getIconForStatus, isFlaky, isSlow } from './action';
3
+ import { formatDuration, getDurationInMillis } from './time';
4
+
5
+ export function sortReport(report: RunReport, sortBy: 'label' | 'time', sortDir: 'asc' | 'desc') {
6
+ const isAsc = sortDir === 'asc';
7
+
8
+ report.actions.sort((a, d) => {
9
+ switch (sortBy) {
10
+ case 'time': {
11
+ const am = getDurationInMillis(a.duration ?? { nanos: 0, secs: 0 });
12
+ const dm = getDurationInMillis(d.duration ?? { nanos: 0, secs: 0 });
13
+
14
+ return isAsc ? am - dm : dm - am;
15
+ }
16
+
17
+ case 'label': {
18
+ const al = a.label ?? '';
19
+ const dl = d.label ?? '';
20
+
21
+ return isAsc ? al.localeCompare(dl) : dl.localeCompare(al);
22
+ }
23
+
24
+ default: {
25
+ throw new Error(`Unknown sort by "${sortBy}".`);
26
+ }
27
+ }
28
+ });
29
+ }
30
+
31
+ export interface PreparedAction {
32
+ comments: string[];
33
+ duration: Duration | null;
34
+ icon: string;
35
+ label: string;
36
+ time: string;
37
+ }
38
+
39
+ export function prepareReportActions(report: RunReport, slowThreshold: number): PreparedAction[] {
40
+ return report.actions.map((action) => {
41
+ const comments: string[] = [];
42
+
43
+ if (isFlaky(action)) {
44
+ comments.push('**FLAKY**');
45
+ }
46
+
47
+ if (action.attempts && action.attempts.length > 1) {
48
+ comments.push(`${action.attempts.length} attempts`);
49
+ }
50
+
51
+ if (isSlow(action, slowThreshold)) {
52
+ comments.push('**SLOW**');
53
+ }
54
+
55
+ return {
56
+ comments,
57
+ duration: action.duration,
58
+ icon: getIconForStatus(action.status),
59
+ label: action.label ?? '<unknown>',
60
+ time: formatDuration(action.duration),
61
+ };
62
+ });
63
+ }
package/src/time.ts ADDED
@@ -0,0 +1,66 @@
1
+ import { Duration } from '@moonrepo/types';
2
+
3
+ export function getDurationInMillis(duration: Duration): number {
4
+ return duration.secs * 1000 + duration.nanos / 1_000_000;
5
+ }
6
+
7
+ export function formatTime(mins: number, secs: number, millis: number): string {
8
+ if (mins === 0 && secs === 0 && millis === 0) {
9
+ return '0s';
10
+ }
11
+
12
+ const format = (val: number) => {
13
+ let v = val.toFixed(1);
14
+
15
+ if (v.endsWith('.0')) {
16
+ v = v.slice(0, -2);
17
+ }
18
+
19
+ return v;
20
+ };
21
+
22
+ // When minutes, only show mins + secs
23
+ if (mins > 0) {
24
+ let value = `${mins}m`;
25
+
26
+ if (secs > 0) {
27
+ value += ` ${secs}s`;
28
+ }
29
+
30
+ return value;
31
+ }
32
+
33
+ // When seconds, only show secs + first milli digit
34
+ if (secs > 0) {
35
+ return `${format((secs * 1000 + millis) / 1000)}s`;
36
+ }
37
+
38
+ // When millis, show as is
39
+ if (millis > 0) {
40
+ return `${format(millis)}ms`;
41
+ }
42
+
43
+ // How did we get here?
44
+ return '0s';
45
+ }
46
+
47
+ export function formatDuration(duration: Duration | null): string {
48
+ if (!duration) {
49
+ return '--';
50
+ }
51
+
52
+ if (duration.secs === 0 && duration.nanos === 0) {
53
+ return '0s';
54
+ }
55
+
56
+ let mins = 0;
57
+ let { secs } = duration;
58
+ const millis = duration.nanos / 1_000_000;
59
+
60
+ while (secs >= 60) {
61
+ mins += 1;
62
+ secs -= 60;
63
+ }
64
+
65
+ return formatTime(mins, secs, millis);
66
+ }