@aqa-pulse/cli 0.1.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/README.md +43 -0
- package/bin/aqa-pulse.js +49 -0
- package/dist/backend/generate-source-facts.d.ts +2 -0
- package/dist/backend/generate-source-facts.js +607 -0
- package/dist/backend/merge-reports.d.ts +19 -0
- package/dist/backend/merge-reports.js +314 -0
- package/dist/backend/upload-report-artifacts.d.ts +9 -0
- package/dist/backend/upload-report-artifacts.js +772 -0
- package/dist/backend/upload-report.d.ts +13 -0
- package/dist/backend/upload-report.js +338 -0
- package/dist/dashboard-utils.d.ts +437 -0
- package/dist/dashboard-utils.js +2627 -0
- package/dist/history-utils.d.ts +72 -0
- package/dist/history-utils.js +267 -0
- package/dist/shared/business-assumptions.d.ts +14 -0
- package/dist/shared/business-assumptions.js +61 -0
- package/dist/shared/dashboard-helpers.d.ts +63 -0
- package/dist/shared/dashboard-helpers.js +429 -0
- package/dist/shared/dashboard-metric-info.d.ts +61 -0
- package/dist/shared/dashboard-metric-info.js +15 -0
- package/dist/shared/error-utils.d.ts +1 -0
- package/dist/shared/error-utils.js +6 -0
- package/dist/shared/formatting.d.ts +3 -0
- package/dist/shared/formatting.js +42 -0
- package/dist/shared/i18n/ru.d.ts +558 -0
- package/dist/shared/i18n/ru.js +577 -0
- package/dist/shared/metric-info.d.ts +5 -0
- package/dist/shared/metric-info.js +210 -0
- package/dist/shared/navigation.d.ts +31 -0
- package/dist/shared/navigation.js +99 -0
- package/dist/shared/test-history-helpers.d.ts +51 -0
- package/dist/shared/test-history-helpers.js +294 -0
- package/dist/shared/test-history-metric-info.d.ts +17 -0
- package/dist/shared/test-history-metric-info.js +20 -0
- package/dist/shared/text-utils.d.ts +2 -0
- package/dist/shared/text-utils.js +15 -0
- package/package.json +37 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ReporterRoot } from './dashboard-utils';
|
|
2
|
+
export interface DashboardHistoryEntry {
|
|
3
|
+
id: string;
|
|
4
|
+
reportTimestamp: string | null;
|
|
5
|
+
generatedAt: string;
|
|
6
|
+
sourceFile: string;
|
|
7
|
+
comparisonKey: string | null;
|
|
8
|
+
comparisonLabel: string | null;
|
|
9
|
+
branch: string | null;
|
|
10
|
+
commit: string | null;
|
|
11
|
+
author: string | null;
|
|
12
|
+
totalTests: number;
|
|
13
|
+
passedTests: number;
|
|
14
|
+
failedTests: number;
|
|
15
|
+
flakyTests: number;
|
|
16
|
+
skippedTests: number;
|
|
17
|
+
timedOutTests: number;
|
|
18
|
+
interruptedTests: number;
|
|
19
|
+
passRate: number;
|
|
20
|
+
flakyRatio: number;
|
|
21
|
+
totalDurationMs: number;
|
|
22
|
+
medianDurationMs: number;
|
|
23
|
+
errorClusterCount: number;
|
|
24
|
+
}
|
|
25
|
+
export interface DashboardHistory {
|
|
26
|
+
schemaVersion: number;
|
|
27
|
+
updatedAt: string;
|
|
28
|
+
runs: DashboardHistoryEntry[];
|
|
29
|
+
}
|
|
30
|
+
export interface ArchivedRunMetadata {
|
|
31
|
+
schemaVersion: number;
|
|
32
|
+
id: string;
|
|
33
|
+
runDirectory: string;
|
|
34
|
+
dataFile: string;
|
|
35
|
+
metadataFile: string;
|
|
36
|
+
reportTimestamp: string | null;
|
|
37
|
+
generatedAt: string;
|
|
38
|
+
sourceFile: string;
|
|
39
|
+
comparisonKey: string | null;
|
|
40
|
+
comparisonLabel: string | null;
|
|
41
|
+
branch: string | null;
|
|
42
|
+
commit: string | null;
|
|
43
|
+
author: string | null;
|
|
44
|
+
kpis: {
|
|
45
|
+
totalTests: number;
|
|
46
|
+
passedTests: number;
|
|
47
|
+
failedTests: number;
|
|
48
|
+
flakyTests: number;
|
|
49
|
+
skippedTests: number;
|
|
50
|
+
timedOutTests: number;
|
|
51
|
+
interruptedTests: number;
|
|
52
|
+
passRate: number;
|
|
53
|
+
flakyRatio: number;
|
|
54
|
+
totalDurationMs: number;
|
|
55
|
+
medianDurationMs: number;
|
|
56
|
+
errorClusterCount: number;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export interface ArchivedRunRecord {
|
|
60
|
+
metadata: ArchivedRunMetadata;
|
|
61
|
+
data: ReporterRoot;
|
|
62
|
+
}
|
|
63
|
+
export declare function readDashboardHistory(historyPath: string): DashboardHistory;
|
|
64
|
+
export declare function appendHistoryEntry(history: DashboardHistory, entry: DashboardHistoryEntry, limit?: number): DashboardHistory;
|
|
65
|
+
export declare function writeDashboardHistory(historyPath: string, history: DashboardHistory): void;
|
|
66
|
+
export declare function archiveHistoryRun(archiveRootPath: string, reportPayload: unknown, entry: DashboardHistoryEntry): ArchivedRunMetadata;
|
|
67
|
+
export declare function readArchivedRunMetadata(metadataFilePath: string): ArchivedRunMetadata;
|
|
68
|
+
export declare function readArchivedRunRecord(archiveRootPath: string, runDirectory: string): ArchivedRunRecord;
|
|
69
|
+
export declare function findArchivedRunDirectory(archiveRootPath: string, entryId: string): string | null;
|
|
70
|
+
export declare function listArchivedRunDirectories(archiveRootPath: string): string[];
|
|
71
|
+
export declare function buildHistoryEntryId(reportTimestamp: string | null, sourceFile: string): string;
|
|
72
|
+
export declare function createEmptyHistory(): DashboardHistory;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readDashboardHistory = readDashboardHistory;
|
|
37
|
+
exports.appendHistoryEntry = appendHistoryEntry;
|
|
38
|
+
exports.writeDashboardHistory = writeDashboardHistory;
|
|
39
|
+
exports.archiveHistoryRun = archiveHistoryRun;
|
|
40
|
+
exports.readArchivedRunMetadata = readArchivedRunMetadata;
|
|
41
|
+
exports.readArchivedRunRecord = readArchivedRunRecord;
|
|
42
|
+
exports.findArchivedRunDirectory = findArchivedRunDirectory;
|
|
43
|
+
exports.listArchivedRunDirectories = listArchivedRunDirectories;
|
|
44
|
+
exports.buildHistoryEntryId = buildHistoryEntryId;
|
|
45
|
+
exports.createEmptyHistory = createEmptyHistory;
|
|
46
|
+
const fs = __importStar(require("node:fs"));
|
|
47
|
+
const path = __importStar(require("node:path"));
|
|
48
|
+
const node_crypto_1 = require("node:crypto");
|
|
49
|
+
const DEFAULT_SCHEMA_VERSION = 2;
|
|
50
|
+
const ARCHIVE_SCHEMA_VERSION = 1;
|
|
51
|
+
const DEFAULT_HISTORY_LIMIT = 20;
|
|
52
|
+
function readDashboardHistory(historyPath) {
|
|
53
|
+
if (!fs.existsSync(historyPath)) {
|
|
54
|
+
return createEmptyHistory();
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const historyText = fs.readFileSync(historyPath, 'utf8');
|
|
58
|
+
const parsedHistory = JSON.parse(historyText);
|
|
59
|
+
return {
|
|
60
|
+
schemaVersion: typeof parsedHistory.schemaVersion === 'number' ? parsedHistory.schemaVersion : DEFAULT_SCHEMA_VERSION,
|
|
61
|
+
updatedAt: typeof parsedHistory.updatedAt === 'string' ? parsedHistory.updatedAt : new Date().toISOString(),
|
|
62
|
+
runs: Array.isArray(parsedHistory.runs)
|
|
63
|
+
? parsedHistory.runs.map(normalizeHistoryEntry)
|
|
64
|
+
: [],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
69
|
+
throw new Error(`Не удалось прочитать history-файл из \"${historyPath}\": ${errorMessage}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function normalizeHistoryEntry(run) {
|
|
73
|
+
return {
|
|
74
|
+
id: typeof run.id === 'string' ? run.id : buildHistoryEntryId(run.reportTimestamp ?? null, run.sourceFile ?? 'unknown'),
|
|
75
|
+
reportTimestamp: typeof run.reportTimestamp === 'string' ? run.reportTimestamp : null,
|
|
76
|
+
generatedAt: typeof run.generatedAt === 'string' ? run.generatedAt : new Date().toISOString(),
|
|
77
|
+
sourceFile: typeof run.sourceFile === 'string' ? run.sourceFile : 'unknown',
|
|
78
|
+
comparisonKey: typeof run.comparisonKey === 'string' ? run.comparisonKey : null,
|
|
79
|
+
comparisonLabel: typeof run.comparisonLabel === 'string' ? run.comparisonLabel : null,
|
|
80
|
+
branch: typeof run.branch === 'string' ? run.branch : null,
|
|
81
|
+
commit: typeof run.commit === 'string' ? run.commit : null,
|
|
82
|
+
author: typeof run.author === 'string' ? run.author : null,
|
|
83
|
+
totalTests: typeof run.totalTests === 'number' ? run.totalTests : 0,
|
|
84
|
+
passedTests: typeof run.passedTests === 'number' ? run.passedTests : 0,
|
|
85
|
+
failedTests: typeof run.failedTests === 'number' ? run.failedTests : 0,
|
|
86
|
+
flakyTests: typeof run.flakyTests === 'number' ? run.flakyTests : 0,
|
|
87
|
+
skippedTests: typeof run.skippedTests === 'number' ? run.skippedTests : 0,
|
|
88
|
+
timedOutTests: typeof run.timedOutTests === 'number' ? run.timedOutTests : 0,
|
|
89
|
+
interruptedTests: typeof run.interruptedTests === 'number' ? run.interruptedTests : 0,
|
|
90
|
+
passRate: typeof run.passRate === 'number' ? run.passRate : 0,
|
|
91
|
+
flakyRatio: typeof run.flakyRatio === 'number' ? run.flakyRatio : 0,
|
|
92
|
+
totalDurationMs: typeof run.totalDurationMs === 'number' ? run.totalDurationMs : 0,
|
|
93
|
+
medianDurationMs: typeof run.medianDurationMs === 'number' ? run.medianDurationMs : 0,
|
|
94
|
+
errorClusterCount: typeof run.errorClusterCount === 'number' ? run.errorClusterCount : 0,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function appendHistoryEntry(history, entry, limit = DEFAULT_HISTORY_LIMIT) {
|
|
98
|
+
const runsWithoutDuplicate = history.runs.filter((run) => run.id !== entry.id);
|
|
99
|
+
const nextRuns = [...runsWithoutDuplicate, entry]
|
|
100
|
+
.sort(compareHistoryEntries)
|
|
101
|
+
.slice(-limit);
|
|
102
|
+
return {
|
|
103
|
+
schemaVersion: history.schemaVersion,
|
|
104
|
+
updatedAt: new Date().toISOString(),
|
|
105
|
+
runs: nextRuns,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function writeDashboardHistory(historyPath, history) {
|
|
109
|
+
fs.mkdirSync(path.dirname(historyPath), { recursive: true });
|
|
110
|
+
fs.writeFileSync(historyPath, `${JSON.stringify(history, null, 2)}\n`, 'utf8');
|
|
111
|
+
}
|
|
112
|
+
function archiveHistoryRun(archiveRootPath, reportPayload, entry) {
|
|
113
|
+
fs.mkdirSync(archiveRootPath, { recursive: true });
|
|
114
|
+
const existingRunDirectory = findArchivedRunDirectoryById(archiveRootPath, entry.id);
|
|
115
|
+
const runDirectory = existingRunDirectory ?? buildUniqueRunDirectoryName(archiveRootPath, entry);
|
|
116
|
+
const runDirectoryPath = path.join(archiveRootPath, runDirectory);
|
|
117
|
+
const dataFileName = 'data.json';
|
|
118
|
+
const metadataFileName = 'metadata.json';
|
|
119
|
+
const dataFilePath = path.join(runDirectoryPath, dataFileName);
|
|
120
|
+
const metadataFilePath = path.join(runDirectoryPath, metadataFileName);
|
|
121
|
+
fs.mkdirSync(runDirectoryPath, { recursive: true });
|
|
122
|
+
fs.writeFileSync(dataFilePath, `${JSON.stringify(reportPayload, null, 2)}\n`, 'utf8');
|
|
123
|
+
const metadata = {
|
|
124
|
+
schemaVersion: ARCHIVE_SCHEMA_VERSION,
|
|
125
|
+
id: entry.id,
|
|
126
|
+
runDirectory,
|
|
127
|
+
dataFile: dataFileName,
|
|
128
|
+
metadataFile: metadataFileName,
|
|
129
|
+
reportTimestamp: entry.reportTimestamp,
|
|
130
|
+
generatedAt: entry.generatedAt,
|
|
131
|
+
sourceFile: entry.sourceFile,
|
|
132
|
+
comparisonKey: entry.comparisonKey,
|
|
133
|
+
comparisonLabel: entry.comparisonLabel,
|
|
134
|
+
branch: entry.branch,
|
|
135
|
+
commit: entry.commit,
|
|
136
|
+
author: entry.author,
|
|
137
|
+
kpis: {
|
|
138
|
+
totalTests: entry.totalTests,
|
|
139
|
+
passedTests: entry.passedTests,
|
|
140
|
+
failedTests: entry.failedTests,
|
|
141
|
+
flakyTests: entry.flakyTests,
|
|
142
|
+
skippedTests: entry.skippedTests,
|
|
143
|
+
timedOutTests: entry.timedOutTests,
|
|
144
|
+
interruptedTests: entry.interruptedTests,
|
|
145
|
+
passRate: entry.passRate,
|
|
146
|
+
flakyRatio: entry.flakyRatio,
|
|
147
|
+
totalDurationMs: entry.totalDurationMs,
|
|
148
|
+
medianDurationMs: entry.medianDurationMs,
|
|
149
|
+
errorClusterCount: entry.errorClusterCount,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
fs.writeFileSync(metadataFilePath, `${JSON.stringify(metadata, null, 2)}\n`, 'utf8');
|
|
153
|
+
return metadata;
|
|
154
|
+
}
|
|
155
|
+
function readArchivedRunMetadata(metadataFilePath) {
|
|
156
|
+
const metadataText = fs.readFileSync(metadataFilePath, 'utf8');
|
|
157
|
+
return JSON.parse(metadataText);
|
|
158
|
+
}
|
|
159
|
+
function readArchivedRunRecord(archiveRootPath, runDirectory) {
|
|
160
|
+
const runDirectoryPath = path.join(archiveRootPath, runDirectory);
|
|
161
|
+
const metadataFilePath = path.join(runDirectoryPath, 'metadata.json');
|
|
162
|
+
const dataFilePath = path.join(runDirectoryPath, 'data.json');
|
|
163
|
+
if (!fs.existsSync(metadataFilePath)) {
|
|
164
|
+
throw new Error(`Не найден metadata.json для архивного прогона: \"${runDirectoryPath}\".`);
|
|
165
|
+
}
|
|
166
|
+
if (!fs.existsSync(dataFilePath)) {
|
|
167
|
+
throw new Error(`Не найден data.json для архивного прогона: \"${runDirectoryPath}\".`);
|
|
168
|
+
}
|
|
169
|
+
const metadata = readArchivedRunMetadata(metadataFilePath);
|
|
170
|
+
const dataText = fs.readFileSync(dataFilePath, 'utf8');
|
|
171
|
+
const data = JSON.parse(dataText);
|
|
172
|
+
return {
|
|
173
|
+
metadata,
|
|
174
|
+
data,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function findArchivedRunDirectory(archiveRootPath, entryId) {
|
|
178
|
+
return findArchivedRunDirectoryById(archiveRootPath, entryId);
|
|
179
|
+
}
|
|
180
|
+
function listArchivedRunDirectories(archiveRootPath) {
|
|
181
|
+
if (!fs.existsSync(archiveRootPath)) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
return fs.readdirSync(archiveRootPath, { withFileTypes: true })
|
|
185
|
+
.filter((entry) => entry.isDirectory())
|
|
186
|
+
.map((entry) => entry.name)
|
|
187
|
+
.sort();
|
|
188
|
+
}
|
|
189
|
+
function buildHistoryEntryId(reportTimestamp, sourceFile) {
|
|
190
|
+
return `${reportTimestamp ?? 'no-timestamp'}::${sourceFile}`;
|
|
191
|
+
}
|
|
192
|
+
function createEmptyHistory() {
|
|
193
|
+
return {
|
|
194
|
+
schemaVersion: DEFAULT_SCHEMA_VERSION,
|
|
195
|
+
updatedAt: new Date().toISOString(),
|
|
196
|
+
runs: [],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function compareHistoryEntries(left, right) {
|
|
200
|
+
const leftTime = getEntryTime(left);
|
|
201
|
+
const rightTime = getEntryTime(right);
|
|
202
|
+
if (leftTime !== rightTime) {
|
|
203
|
+
return leftTime - rightTime;
|
|
204
|
+
}
|
|
205
|
+
return left.id.localeCompare(right.id);
|
|
206
|
+
}
|
|
207
|
+
function getEntryTime(entry) {
|
|
208
|
+
const reportTime = entry.reportTimestamp ? new Date(entry.reportTimestamp).getTime() : Number.NaN;
|
|
209
|
+
if (Number.isFinite(reportTime)) {
|
|
210
|
+
return reportTime;
|
|
211
|
+
}
|
|
212
|
+
const generatedTime = new Date(entry.generatedAt).getTime();
|
|
213
|
+
if (Number.isFinite(generatedTime)) {
|
|
214
|
+
return generatedTime;
|
|
215
|
+
}
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
function findArchivedRunDirectoryById(archiveRootPath, entryId) {
|
|
219
|
+
if (!fs.existsSync(archiveRootPath)) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
for (const directoryEntry of fs.readdirSync(archiveRootPath, { withFileTypes: true })) {
|
|
223
|
+
if (!directoryEntry.isDirectory()) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const metadataFilePath = path.join(archiveRootPath, directoryEntry.name, 'metadata.json');
|
|
227
|
+
if (!fs.existsSync(metadataFilePath)) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
const metadataText = fs.readFileSync(metadataFilePath, 'utf8');
|
|
232
|
+
const metadata = JSON.parse(metadataText);
|
|
233
|
+
if (metadata.id === entryId) {
|
|
234
|
+
return directoryEntry.name;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
function buildUniqueRunDirectoryName(archiveRootPath, entry) {
|
|
244
|
+
const baseDirectoryName = formatArchiveDirectoryName(entry.reportTimestamp ?? entry.generatedAt);
|
|
245
|
+
const preferredDirectoryPath = path.join(archiveRootPath, baseDirectoryName);
|
|
246
|
+
if (!fs.existsSync(preferredDirectoryPath)) {
|
|
247
|
+
return baseDirectoryName;
|
|
248
|
+
}
|
|
249
|
+
const hashedSuffix = (0, node_crypto_1.createHash)('sha1').update(entry.id).digest('hex').slice(0, 8);
|
|
250
|
+
return `${baseDirectoryName}--${hashedSuffix}`;
|
|
251
|
+
}
|
|
252
|
+
function formatArchiveDirectoryName(timestamp) {
|
|
253
|
+
const date = new Date(timestamp);
|
|
254
|
+
if (Number.isNaN(date.getTime())) {
|
|
255
|
+
return 'unknown-run';
|
|
256
|
+
}
|
|
257
|
+
const year = date.getFullYear();
|
|
258
|
+
const month = padNumber(date.getMonth() + 1);
|
|
259
|
+
const day = padNumber(date.getDate());
|
|
260
|
+
const hours = padNumber(date.getHours());
|
|
261
|
+
const minutes = padNumber(date.getMinutes());
|
|
262
|
+
const seconds = padNumber(date.getSeconds());
|
|
263
|
+
return `${year}-${month}-${day}--${hours}-${minutes}-${seconds}`;
|
|
264
|
+
}
|
|
265
|
+
function padNumber(value) {
|
|
266
|
+
return String(value).padStart(2, '0');
|
|
267
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DashboardSummary } from '../dashboard-utils';
|
|
2
|
+
export interface DashboardBusinessAssumptions {
|
|
3
|
+
ciMinuteCostRub: number | null;
|
|
4
|
+
developerHourlyCostRub: number | null;
|
|
5
|
+
analysisMinutesPerUnstable: number | null;
|
|
6
|
+
}
|
|
7
|
+
export declare function normalizeDashboardBusinessAssumptions(assumptions: Partial<DashboardBusinessAssumptions> | null | undefined): DashboardBusinessAssumptions;
|
|
8
|
+
export declare function applyBusinessAssumptionsToSummary(summary: DashboardSummary, assumptions: Partial<DashboardBusinessAssumptions> | null | undefined): DashboardSummary;
|
|
9
|
+
export declare function recalculateCostOfFlakinessMetrics(baseMetrics: {
|
|
10
|
+
extraRetryMinutes: number;
|
|
11
|
+
extraRetries: number;
|
|
12
|
+
unstableRuns: number;
|
|
13
|
+
activeDays: number;
|
|
14
|
+
}, assumptions: DashboardBusinessAssumptions): DashboardSummary['businessMetrics']['costOfFlakiness'];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeDashboardBusinessAssumptions = normalizeDashboardBusinessAssumptions;
|
|
4
|
+
exports.applyBusinessAssumptionsToSummary = applyBusinessAssumptionsToSummary;
|
|
5
|
+
exports.recalculateCostOfFlakinessMetrics = recalculateCostOfFlakinessMetrics;
|
|
6
|
+
function normalizeDashboardBusinessAssumptions(assumptions) {
|
|
7
|
+
return {
|
|
8
|
+
ciMinuteCostRub: normalizeOptionalNonNegativeNumber(assumptions?.ciMinuteCostRub),
|
|
9
|
+
developerHourlyCostRub: normalizeOptionalNonNegativeNumber(assumptions?.developerHourlyCostRub),
|
|
10
|
+
analysisMinutesPerUnstable: normalizeOptionalNonNegativeNumber(assumptions?.analysisMinutesPerUnstable),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function applyBusinessAssumptionsToSummary(summary, assumptions) {
|
|
14
|
+
const mergedAssumptions = normalizeDashboardBusinessAssumptions({
|
|
15
|
+
...summary.businessMetrics.costOfFlakiness.assumptions,
|
|
16
|
+
...(assumptions ?? {}),
|
|
17
|
+
});
|
|
18
|
+
return {
|
|
19
|
+
...summary,
|
|
20
|
+
businessMetrics: {
|
|
21
|
+
...summary.businessMetrics,
|
|
22
|
+
costOfFlakiness: recalculateCostOfFlakinessMetrics({
|
|
23
|
+
extraRetryMinutes: summary.businessMetrics.costOfFlakiness.extraRetryMinutes,
|
|
24
|
+
extraRetries: summary.businessMetrics.costOfFlakiness.extraRetries,
|
|
25
|
+
unstableRuns: summary.businessMetrics.costOfFlakiness.unstableRuns,
|
|
26
|
+
activeDays: summary.businessMetrics.costOfFlakiness.activeDays,
|
|
27
|
+
}, mergedAssumptions),
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function recalculateCostOfFlakinessMetrics(baseMetrics, assumptions) {
|
|
32
|
+
const ciCostRub = assumptions.ciMinuteCostRub === null
|
|
33
|
+
? null
|
|
34
|
+
: roundToTwoDigits(baseMetrics.extraRetryMinutes * assumptions.ciMinuteCostRub);
|
|
35
|
+
const developerCostRub = assumptions.developerHourlyCostRub === null || assumptions.analysisMinutesPerUnstable === null
|
|
36
|
+
? null
|
|
37
|
+
: roundToTwoDigits(baseMetrics.unstableRuns * (assumptions.analysisMinutesPerUnstable / 60) * assumptions.developerHourlyCostRub);
|
|
38
|
+
const totalRub = ciCostRub === null && developerCostRub === null
|
|
39
|
+
? null
|
|
40
|
+
: roundToTwoDigits((ciCostRub ?? 0) + (developerCostRub ?? 0));
|
|
41
|
+
const costPerActiveDayRub = totalRub === null || baseMetrics.activeDays === 0
|
|
42
|
+
? null
|
|
43
|
+
: roundToTwoDigits(totalRub / baseMetrics.activeDays);
|
|
44
|
+
return {
|
|
45
|
+
totalRub,
|
|
46
|
+
ciCostRub,
|
|
47
|
+
developerCostRub,
|
|
48
|
+
extraRetryMinutes: baseMetrics.extraRetryMinutes,
|
|
49
|
+
extraRetries: baseMetrics.extraRetries,
|
|
50
|
+
unstableRuns: baseMetrics.unstableRuns,
|
|
51
|
+
activeDays: baseMetrics.activeDays,
|
|
52
|
+
costPerActiveDayRub,
|
|
53
|
+
assumptions,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function normalizeOptionalNonNegativeNumber(value) {
|
|
57
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null;
|
|
58
|
+
}
|
|
59
|
+
function roundToTwoDigits(value) {
|
|
60
|
+
return Math.round(value * 100) / 100;
|
|
61
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { DashboardSummary } from '../dashboard-utils';
|
|
2
|
+
export type DashboardStatusTone = 'neutral' | 'good' | 'warn' | 'danger';
|
|
3
|
+
export type DashboardMetricTone = 'default' | 'good' | 'warn' | 'danger';
|
|
4
|
+
export declare function formatCurrency(value: number | null): string;
|
|
5
|
+
export declare function formatMinutes(value: number): string;
|
|
6
|
+
export declare function formatNullableMinutes(value: number | null): string;
|
|
7
|
+
export declare function formatDailyRatio(value: number): string;
|
|
8
|
+
export declare function formatRunsPerHundred(value: number): string;
|
|
9
|
+
export declare function formatScore(value: number): string;
|
|
10
|
+
export declare function formatNullableDays(value: number | null): string;
|
|
11
|
+
export declare function formatNullablePercent(value: number | null): string;
|
|
12
|
+
export declare function formatAssumptionValue(value: number | null, unit: string): string;
|
|
13
|
+
export declare function formatCommit(commit: string | null): string;
|
|
14
|
+
export declare function formatStatusLabel(status: string, flaky: boolean): string;
|
|
15
|
+
export declare function getStatusTone(status: string, flaky: boolean): DashboardStatusTone;
|
|
16
|
+
export declare function getScoreTone(value: number): DashboardMetricTone;
|
|
17
|
+
export declare function getInverseScoreTone(value: number): DashboardMetricTone;
|
|
18
|
+
export declare function formatDelta(value: number | null, label: string, comparisonMode?: DashboardSummary['comparison']['mode']): string;
|
|
19
|
+
export declare function formatPerformancePhaseLabel(label: string): string;
|
|
20
|
+
export declare function getManagerReadinessLabel(level: DashboardSummary['managerSummary']['releaseReadiness']['level']): string;
|
|
21
|
+
export declare function getManagerRiskLabel(level: DashboardSummary['managerSummary']['qualityRisk']['level']): string;
|
|
22
|
+
export declare function getManagerChangeLabel(direction: DashboardSummary['managerSummary']['changes'][number]['direction']): string;
|
|
23
|
+
export declare function buildFlakyHistoryInsight(summary: DashboardSummary): {
|
|
24
|
+
tone: 'default' | 'warn' | 'info';
|
|
25
|
+
title: string;
|
|
26
|
+
body: string;
|
|
27
|
+
};
|
|
28
|
+
export declare function getFlakyTopTestsEmptyState(summary: DashboardSummary): string;
|
|
29
|
+
export declare function getCodeQualityFailureConcentration(summary: DashboardSummary): number | null;
|
|
30
|
+
export declare function buildReleaseConfidenceBreakdown(summary: DashboardSummary): {
|
|
31
|
+
total: number;
|
|
32
|
+
components: Array<{
|
|
33
|
+
label: string;
|
|
34
|
+
formula: string;
|
|
35
|
+
width: string;
|
|
36
|
+
tone: string;
|
|
37
|
+
}>;
|
|
38
|
+
};
|
|
39
|
+
export declare function buildBusinessMetricReadiness(summary: DashboardSummary): Array<{
|
|
40
|
+
label: string;
|
|
41
|
+
status: 'ready' | 'partial' | 'pending';
|
|
42
|
+
statusLabel: string;
|
|
43
|
+
hint: string;
|
|
44
|
+
}>;
|
|
45
|
+
export declare function averageDashboardNumber(values: number[]): number;
|
|
46
|
+
export declare function getBusinessReadinessStatusLabel(status: 'ready' | 'partial' | 'pending'): string;
|
|
47
|
+
export declare function getBusinessAssumptionsState(assumptions: DashboardSummary['businessMetrics']['costOfFlakiness']['assumptions']): 'ready' | 'partial' | 'empty';
|
|
48
|
+
export declare function getBusinessScenarioStatusLabel(assumptions: DashboardSummary['businessMetrics']['costOfFlakiness']['assumptions']): string;
|
|
49
|
+
export declare function getBusinessScenarioStatusHint(assumptions: DashboardSummary['businessMetrics']['costOfFlakiness']['assumptions']): string;
|
|
50
|
+
export declare function getBusinessImpactLevel(totalCost: number | null, costPerDay: number | null): 'high' | 'medium' | 'low' | 'unknown';
|
|
51
|
+
export declare function getBusinessImpactClass(totalCost: number | null, costPerDay: number | null): string;
|
|
52
|
+
export declare function getBusinessImpactLabel(totalCost: number | null, costPerDay: number | null): string;
|
|
53
|
+
export declare function getBusinessDriverType(ciCost: number | null, developerCost: number | null, totalCost: number | null): 'ci' | 'development' | 'balanced' | 'missing';
|
|
54
|
+
export declare function getBusinessBreakdownItemClass(ciCost: number | null, developerCost: number | null, totalCost: number | null, target: 'ci' | 'development'): string;
|
|
55
|
+
export declare function getBusinessDriverSignalClass(ciCost: number | null, developerCost: number | null, totalCost: number | null, target: 'ci' | 'development'): string;
|
|
56
|
+
export declare function getBusinessDriverInsightTitle(ciCost: number | null, developerCost: number | null, totalCost: number | null): string;
|
|
57
|
+
export declare function getBusinessDriverInsightBody(ciCost: number | null, developerCost: number | null, totalCost: number | null): string;
|
|
58
|
+
export declare function formatCostShare(value: number | null, total: number | null): string;
|
|
59
|
+
export declare function formatCostShareWidth(value: number | null, total: number | null): string;
|
|
60
|
+
export declare function getDashboardScoreTone(value: number): string;
|
|
61
|
+
export declare function clampDashboardScore(value: number): number;
|
|
62
|
+
export declare function roundToOneDigit(value: number): number;
|
|
63
|
+
export declare function roundOne(value: number): number;
|