@allurereport/plugin-api 3.0.0-beta.17 → 3.0.0-beta.19
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/dist/charts/accessors/coverageDiffTreeMapAccessor.d.ts +17 -0
- package/dist/charts/accessors/coverageDiffTreeMapAccessor.js +181 -0
- package/dist/charts/accessors/problemsDistributionHeatMap.d.ts +2 -0
- package/dist/charts/accessors/problemsDistributionHeatMap.js +65 -0
- package/dist/charts/accessors/severityTrendAccessor.d.ts +3 -0
- package/dist/charts/accessors/severityTrendAccessor.js +21 -0
- package/dist/charts/accessors/statusBySeverityBarAccessor.d.ts +3 -0
- package/dist/charts/accessors/statusBySeverityBarAccessor.js +33 -0
- package/dist/charts/accessors/statusChangeTrendBarAccessor.d.ts +4 -0
- package/dist/charts/accessors/statusChangeTrendBarAccessor.js +92 -0
- package/dist/charts/accessors/statusTrendAccessor.d.ts +3 -0
- package/dist/charts/accessors/statusTrendAccessor.js +19 -0
- package/dist/charts/accessors/statusTrendBarAccessor.d.ts +5 -0
- package/dist/charts/accessors/statusTrendBarAccessor.js +63 -0
- package/dist/charts/accessors/successRateDistributionTreeMapAccessor.d.ts +14 -0
- package/dist/charts/accessors/successRateDistributionTreeMapAccessor.js +111 -0
- package/dist/charts/accessors/utils/behavior.d.ts +5 -0
- package/dist/charts/accessors/utils/behavior.js +4 -0
- package/dist/charts/bar.d.ts +3 -0
- package/dist/charts/bar.js +50 -0
- package/dist/charts/comingSoon.d.ts +2 -0
- package/dist/charts/comingSoon.js +7 -0
- package/dist/charts/heatmap.d.ts +3 -0
- package/dist/charts/heatmap.js +9 -0
- package/dist/charts/line.d.ts +8 -0
- package/dist/charts/line.js +140 -0
- package/dist/charts/pie.d.ts +6 -0
- package/dist/charts/pie.js +10 -0
- package/dist/charts/treeMap.d.ts +6 -0
- package/dist/charts/treeMap.js +88 -0
- package/dist/charts.d.ts +130 -0
- package/dist/charts.js +83 -0
- package/dist/config.d.ts +3 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +8 -0
- package/dist/plugin.d.ts +30 -6
- package/dist/qualityGate.d.ts +28 -37
- package/dist/store.d.ts +41 -1
- package/dist/store.js +18 -1
- package/package.json +3 -3
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TreeMapNode } from "@allurereport/core-api";
|
|
2
|
+
import type { TreeMapDataAccessor } from "../../charts.js";
|
|
3
|
+
type ChangeType = "new" | "deleted" | "enabled" | "disabled" | "unchanged";
|
|
4
|
+
type SubtreeMetrics = {
|
|
5
|
+
totalTests: number;
|
|
6
|
+
newCount: number;
|
|
7
|
+
deletedCount: number;
|
|
8
|
+
disabledCount: number;
|
|
9
|
+
enabledCount: number;
|
|
10
|
+
};
|
|
11
|
+
type LeafMetrics = {
|
|
12
|
+
changeType: ChangeType;
|
|
13
|
+
};
|
|
14
|
+
type GroupMetrics = Omit<SubtreeMetrics, "totalTests">;
|
|
15
|
+
type ExtendedTreeMapNode = TreeMapNode<GroupMetrics & Partial<LeafMetrics>>;
|
|
16
|
+
export declare const coverageDiffTreeMapAccessor: TreeMapDataAccessor<ExtendedTreeMapNode>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { isChildrenLeavesOnly } from "../../charts.js";
|
|
2
|
+
import { md5 } from "../../utils/misc.js";
|
|
3
|
+
import { createTreeByLabels } from "../../utils/tree.js";
|
|
4
|
+
import { convertTreeDataToTreeMapNode, transformTreeMapNode } from "../treeMap.js";
|
|
5
|
+
import { behaviorLabels, filterTestsWithBehaviorLabels } from "./utils/behavior.js";
|
|
6
|
+
const groupFactoryFn = (parentId, groupClassifier) => ({
|
|
7
|
+
nodeId: md5((parentId ? `${parentId}.` : "") + groupClassifier),
|
|
8
|
+
name: groupClassifier,
|
|
9
|
+
value: 0,
|
|
10
|
+
newCount: 0,
|
|
11
|
+
deletedCount: 0,
|
|
12
|
+
disabledCount: 0,
|
|
13
|
+
enabledCount: 0,
|
|
14
|
+
});
|
|
15
|
+
const addLeafToGroupFn = (group, leaf) => {
|
|
16
|
+
group.value += leaf.value;
|
|
17
|
+
switch (leaf.changeType) {
|
|
18
|
+
case "new":
|
|
19
|
+
group.newCount++;
|
|
20
|
+
break;
|
|
21
|
+
case "deleted":
|
|
22
|
+
group.deletedCount++;
|
|
23
|
+
break;
|
|
24
|
+
case "enabled":
|
|
25
|
+
group.enabledCount++;
|
|
26
|
+
break;
|
|
27
|
+
case "disabled":
|
|
28
|
+
group.disabledCount++;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const calculateColorValue = (metrics) => {
|
|
33
|
+
const netChange = metrics.newCount + metrics.enabledCount - (metrics.deletedCount + metrics.disabledCount);
|
|
34
|
+
const normalizedChange = netChange / metrics.totalTests;
|
|
35
|
+
return Math.max(0, Math.min(1, (normalizedChange + 1) / 2));
|
|
36
|
+
};
|
|
37
|
+
const isSkipped = (tr) => tr.status === "skipped";
|
|
38
|
+
const getNewTestResults = (trs, closestHtrs) => {
|
|
39
|
+
return trs.filter((tr) => !closestHtrs[tr.historyId]);
|
|
40
|
+
};
|
|
41
|
+
const getRemovedTestResults = (trs, closestHtrs) => {
|
|
42
|
+
const historyPointTestResultsAsArray = Object.values(closestHtrs);
|
|
43
|
+
const testResultsAsDictionary = Object.fromEntries(trs.map((tr) => [tr.historyId, tr]));
|
|
44
|
+
return historyPointTestResultsAsArray.filter((htr) => !testResultsAsDictionary[htr.historyId]);
|
|
45
|
+
};
|
|
46
|
+
const getEnabledTestResults = (trs, closestHtrs) => {
|
|
47
|
+
return trs.filter((tr) => {
|
|
48
|
+
const historyPointTestResult = closestHtrs[tr.historyId];
|
|
49
|
+
return historyPointTestResult && isSkipped(historyPointTestResult) && !isSkipped(tr);
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
const getDisabledTestResults = (trs, closestHtrs) => {
|
|
53
|
+
return trs.filter((tr) => {
|
|
54
|
+
const historyPointTestResult = closestHtrs[tr.historyId];
|
|
55
|
+
return historyPointTestResult && !isSkipped(historyPointTestResult) && isSkipped(tr);
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
const calculateSubtreeMetrics = (node) => {
|
|
59
|
+
if (!node.children || node.children.length === 0) {
|
|
60
|
+
const changeType = node?.changeType;
|
|
61
|
+
return {
|
|
62
|
+
totalTests: 1,
|
|
63
|
+
newCount: changeType === "new" ? 1 : 0,
|
|
64
|
+
deletedCount: changeType === "deleted" ? 1 : 0,
|
|
65
|
+
disabledCount: changeType === "disabled" ? 1 : 0,
|
|
66
|
+
enabledCount: changeType === "enabled" ? 1 : 0,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
let totalTests = 0;
|
|
70
|
+
let newCount = 0;
|
|
71
|
+
let deletedCount = 0;
|
|
72
|
+
let disabledCount = 0;
|
|
73
|
+
let enabledCount = 0;
|
|
74
|
+
for (const child of node.children) {
|
|
75
|
+
const childMetrics = calculateSubtreeMetrics(child);
|
|
76
|
+
totalTests += childMetrics.totalTests;
|
|
77
|
+
newCount += childMetrics.newCount;
|
|
78
|
+
deletedCount += childMetrics.deletedCount;
|
|
79
|
+
disabledCount += childMetrics.disabledCount;
|
|
80
|
+
enabledCount += childMetrics.enabledCount;
|
|
81
|
+
}
|
|
82
|
+
return { totalTests, newCount, deletedCount, disabledCount, enabledCount };
|
|
83
|
+
};
|
|
84
|
+
const createCoverageDiffTreeMap = (trs, closestHtrs) => {
|
|
85
|
+
const newTrs = getNewTestResults(trs, closestHtrs);
|
|
86
|
+
const removedHtrs = getRemovedTestResults(trs, closestHtrs);
|
|
87
|
+
const enabledTrs = getEnabledTestResults(trs, closestHtrs);
|
|
88
|
+
const disabledTrs = getDisabledTestResults(trs, closestHtrs);
|
|
89
|
+
const newTestsById = new Map(newTrs.map((tr) => [tr.historyId, tr]));
|
|
90
|
+
const deletedTestsById = new Map(removedHtrs.map((htr) => [htr.historyId, htr]));
|
|
91
|
+
const enabledTestsById = new Map(enabledTrs.map((tr) => [tr.historyId, tr]));
|
|
92
|
+
const disabledTestsById = new Map(disabledTrs.map((tr) => [tr.historyId, tr]));
|
|
93
|
+
const allTests = [...trs, ...removedHtrs];
|
|
94
|
+
const getChangeType = (historyId) => {
|
|
95
|
+
if (newTestsById.has(historyId)) {
|
|
96
|
+
return "new";
|
|
97
|
+
}
|
|
98
|
+
if (deletedTestsById.has(historyId)) {
|
|
99
|
+
return "deleted";
|
|
100
|
+
}
|
|
101
|
+
if (enabledTestsById.has(historyId)) {
|
|
102
|
+
return "enabled";
|
|
103
|
+
}
|
|
104
|
+
if (disabledTestsById.has(historyId)) {
|
|
105
|
+
return "disabled";
|
|
106
|
+
}
|
|
107
|
+
return "unchanged";
|
|
108
|
+
};
|
|
109
|
+
const leafFactoryFnWithMaps = (test) => {
|
|
110
|
+
const changeType = getChangeType(test.historyId);
|
|
111
|
+
return {
|
|
112
|
+
nodeId: test.id,
|
|
113
|
+
name: test.name,
|
|
114
|
+
value: 1,
|
|
115
|
+
changeType,
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
const treeByLabels = createTreeByLabels(allTests, behaviorLabels, leafFactoryFnWithMaps, groupFactoryFn, addLeafToGroupFn);
|
|
119
|
+
const convertedTree = convertTreeDataToTreeMapNode(treeByLabels, (node, isGroup) => {
|
|
120
|
+
const baseNode = {
|
|
121
|
+
id: node.name,
|
|
122
|
+
value: isGroup ? undefined : node.value,
|
|
123
|
+
};
|
|
124
|
+
if (isGroup) {
|
|
125
|
+
const group = node;
|
|
126
|
+
return {
|
|
127
|
+
...baseNode,
|
|
128
|
+
newCount: group.newCount,
|
|
129
|
+
deletedCount: group.deletedCount,
|
|
130
|
+
disabledCount: group.disabledCount,
|
|
131
|
+
enabledCount: group.enabledCount,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
const leaf = node;
|
|
136
|
+
return {
|
|
137
|
+
...baseNode,
|
|
138
|
+
changeType: leaf.changeType,
|
|
139
|
+
newCount: leaf.changeType === "new" ? 1 : 0,
|
|
140
|
+
deletedCount: leaf.changeType === "deleted" ? 1 : 0,
|
|
141
|
+
disabledCount: leaf.changeType === "disabled" ? 1 : 0,
|
|
142
|
+
enabledCount: leaf.changeType === "enabled" ? 1 : 0,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}, () => ({
|
|
146
|
+
id: "root",
|
|
147
|
+
newCount: 0,
|
|
148
|
+
deletedCount: 0,
|
|
149
|
+
disabledCount: 0,
|
|
150
|
+
enabledCount: 0,
|
|
151
|
+
}));
|
|
152
|
+
return transformTreeMapNode(convertedTree, (node) => {
|
|
153
|
+
const subtreeMetrics = calculateSubtreeMetrics(node);
|
|
154
|
+
const colorValue = calculateColorValue(subtreeMetrics);
|
|
155
|
+
const { totalTests, ...restSubtreeMetrics } = subtreeMetrics;
|
|
156
|
+
if (isChildrenLeavesOnly(node)) {
|
|
157
|
+
return {
|
|
158
|
+
...node,
|
|
159
|
+
value: totalTests,
|
|
160
|
+
children: undefined,
|
|
161
|
+
colorValue,
|
|
162
|
+
...restSubtreeMetrics,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
...node,
|
|
167
|
+
colorValue,
|
|
168
|
+
...restSubtreeMetrics,
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
export const coverageDiffTreeMapAccessor = {
|
|
173
|
+
getTreeMap: ({ testResults, historyDataPoints }) => {
|
|
174
|
+
const testsWithBehaviorLabels = filterTestsWithBehaviorLabels(testResults);
|
|
175
|
+
const closestHdp = historyDataPoints[0];
|
|
176
|
+
const closestHtrs = closestHdp.testResults;
|
|
177
|
+
const closestHtrsWithBehaviorLabels = filterTestsWithBehaviorLabels(Object.values(closestHtrs));
|
|
178
|
+
const closestHtrsWithBehaviorLabelsById = Object.fromEntries(closestHtrsWithBehaviorLabels.map((htr) => [htr.historyId, htr]));
|
|
179
|
+
return createCoverageDiffTreeMap(testsWithBehaviorLabels, closestHtrsWithBehaviorLabelsById);
|
|
180
|
+
},
|
|
181
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { filterIncludedInSuccessRate } from "@allurereport/core-api";
|
|
2
|
+
const groupTestsByEnvironment = (testResults) => {
|
|
3
|
+
return testResults.reduce((acc, testResult) => {
|
|
4
|
+
const key = testResult.environment;
|
|
5
|
+
if (key) {
|
|
6
|
+
const bucket = acc[key] || (acc[key] = []);
|
|
7
|
+
bucket.push(testResult);
|
|
8
|
+
}
|
|
9
|
+
return acc;
|
|
10
|
+
}, {});
|
|
11
|
+
};
|
|
12
|
+
const groupByExactLabel = (testResults, labelNames) => {
|
|
13
|
+
return testResults.reduce((acc, testResult) => {
|
|
14
|
+
const labels = testResult.labels;
|
|
15
|
+
if (!labels) {
|
|
16
|
+
return acc;
|
|
17
|
+
}
|
|
18
|
+
for (const label of labels) {
|
|
19
|
+
const key = label.value;
|
|
20
|
+
if (labelNames.includes(label.name) && key) {
|
|
21
|
+
const bucket = acc[key] || (acc[key] = []);
|
|
22
|
+
bucket.push(testResult);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return acc;
|
|
26
|
+
}, {});
|
|
27
|
+
};
|
|
28
|
+
const makeHeatMapSerie = (env, testResults) => {
|
|
29
|
+
const testResultsByExactLabel = groupByExactLabel(testResults, ["feature"]);
|
|
30
|
+
const data = [];
|
|
31
|
+
for (const [labelValue, testsByLabelValue] of Object.entries(testResultsByExactLabel)) {
|
|
32
|
+
const testsTotal = testsByLabelValue.length;
|
|
33
|
+
const totalNegative = testsByLabelValue.reduce((acc, test) => acc + (test.status !== "passed" ? 1 : 0), 0);
|
|
34
|
+
data.push({
|
|
35
|
+
x: labelValue,
|
|
36
|
+
y: totalNegative / testsTotal,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
id: env,
|
|
41
|
+
data: data.sort((a, b) => (a.y || 0) - (b.y || 0)),
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
const makeHeatMapData = (testsByEnvironment) => {
|
|
45
|
+
return Object.entries(testsByEnvironment).map(([env, tests]) => makeHeatMapSerie(env, tests));
|
|
46
|
+
};
|
|
47
|
+
const filterTestResultsBySignificantStatus = (testResults) => {
|
|
48
|
+
return testResults.filter(filterIncludedInSuccessRate);
|
|
49
|
+
};
|
|
50
|
+
const filterTestResultsByLabelNames = (testResults, labelNames) => {
|
|
51
|
+
return testResults.filter((test) => test.labels?.some((l) => labelNames.includes(l.name)));
|
|
52
|
+
};
|
|
53
|
+
export const problemsDistributionHeatMapAccessor = {
|
|
54
|
+
getHeatMap: ({ testResults }) => {
|
|
55
|
+
const filteredTestResults = filterTestResultsBySignificantStatus(filterTestResultsByLabelNames(testResults, ["feature"]));
|
|
56
|
+
const testsResultsByEnvironment = groupTestsByEnvironment(filteredTestResults);
|
|
57
|
+
const data = makeHeatMapData(testsResultsByEnvironment);
|
|
58
|
+
const totals = new Map();
|
|
59
|
+
for (const serie of data) {
|
|
60
|
+
const total = serie.data.reduce((acc, sd) => acc + (sd.y || 0), 0);
|
|
61
|
+
totals.set(serie.id, total);
|
|
62
|
+
}
|
|
63
|
+
return data.sort((a, b) => totals.get(a.id) - totals.get(b.id));
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { severityLabelName, severityLevels } from "@allurereport/core-api";
|
|
2
|
+
import { createEmptyStats } from "../../charts.js";
|
|
3
|
+
const processTestResults = (testResults) => {
|
|
4
|
+
return testResults.reduce((acc, test) => {
|
|
5
|
+
const severityLabel = test.labels?.find((label) => label.name === severityLabelName);
|
|
6
|
+
const severity = severityLabel?.value?.toLowerCase();
|
|
7
|
+
if (severity) {
|
|
8
|
+
acc[severity] = (acc[severity] ?? 0) + 1;
|
|
9
|
+
}
|
|
10
|
+
return acc;
|
|
11
|
+
}, createEmptyStats(severityLevels));
|
|
12
|
+
};
|
|
13
|
+
export const severityTrendDataAccessor = {
|
|
14
|
+
getCurrentData: ({ testResults }) => {
|
|
15
|
+
return processTestResults(testResults);
|
|
16
|
+
},
|
|
17
|
+
getHistoricalData: (historyPoint) => {
|
|
18
|
+
return processTestResults(Object.values(historyPoint.testResults));
|
|
19
|
+
},
|
|
20
|
+
getAllValues: () => severityLevels,
|
|
21
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { BarGroupMode, severityLabelName, severityLevels, statusesList } from "@allurereport/core-api";
|
|
2
|
+
const processTestResults = (testResults) => {
|
|
3
|
+
const resultMap = {
|
|
4
|
+
blocker: undefined,
|
|
5
|
+
critical: undefined,
|
|
6
|
+
normal: undefined,
|
|
7
|
+
minor: undefined,
|
|
8
|
+
trivial: undefined,
|
|
9
|
+
};
|
|
10
|
+
severityLevels.forEach((severity) => {
|
|
11
|
+
resultMap[severity] = statusesList.reduce((acc, status) => ({ ...acc, [status]: 0 }), {});
|
|
12
|
+
});
|
|
13
|
+
testResults.forEach((test) => {
|
|
14
|
+
const severityLabel = test.labels?.find((label) => label.name === severityLabelName);
|
|
15
|
+
const severity = severityLabel?.value?.toLowerCase();
|
|
16
|
+
if (severity && resultMap[severity]) {
|
|
17
|
+
resultMap[severity][test.status] = (resultMap[severity][test.status] ?? 0) + 1;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return Object.entries(resultMap).reduce((acc, [severity, values]) => {
|
|
21
|
+
if (values) {
|
|
22
|
+
acc.push({ groupId: severity, ...values });
|
|
23
|
+
}
|
|
24
|
+
return acc;
|
|
25
|
+
}, []);
|
|
26
|
+
};
|
|
27
|
+
export const statusBySeverityBarDataAccessor = {
|
|
28
|
+
getItems: ({ testResults }) => {
|
|
29
|
+
return processTestResults(testResults);
|
|
30
|
+
},
|
|
31
|
+
getGroupKeys: () => statusesList,
|
|
32
|
+
getGroupMode: () => BarGroupMode.Grouped,
|
|
33
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { NewKey, RemovedKey, TestStatus } from "@allurereport/core-api";
|
|
2
|
+
import { type BarDataAccessor } from "../../charts.js";
|
|
3
|
+
export type StatusChangeTrendKeys = NewKey<TestStatus> | RemovedKey<TestStatus>;
|
|
4
|
+
export declare const statusChangeTrendBarAccessor: BarDataAccessor<string, StatusChangeTrendKeys>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { BarGroupMode, capitalize } from "@allurereport/core-api";
|
|
2
|
+
import { createEmptyStats } from "../../charts.js";
|
|
3
|
+
const newGroupKeys = ["newPassed", "newFailed", "newBroken", "newSkipped", "newUnknown"];
|
|
4
|
+
const removedGroupKeys = [
|
|
5
|
+
"removedPassed",
|
|
6
|
+
"removedFailed",
|
|
7
|
+
"removedBroken",
|
|
8
|
+
"removedSkipped",
|
|
9
|
+
"removedUnknown",
|
|
10
|
+
];
|
|
11
|
+
const groupKeys = [...newGroupKeys, ...removedGroupKeys];
|
|
12
|
+
const getNewKey = (status) => {
|
|
13
|
+
const capitalizedStatus = capitalize(status);
|
|
14
|
+
return capitalizedStatus ? `new${capitalizedStatus}` : undefined;
|
|
15
|
+
};
|
|
16
|
+
const getRemovedKey = (status) => {
|
|
17
|
+
const capitalizedStatus = capitalize(status);
|
|
18
|
+
return capitalizedStatus ? `removed${capitalizedStatus}` : undefined;
|
|
19
|
+
};
|
|
20
|
+
const isHistoryIdIn = (trs, historyId) => {
|
|
21
|
+
return trs.some((tr) => tr.historyId === historyId);
|
|
22
|
+
};
|
|
23
|
+
const getDeletedFrom = (trs, hdpTrs) => {
|
|
24
|
+
const stats = createEmptyStats(groupKeys);
|
|
25
|
+
for (const hdpTr of hdpTrs) {
|
|
26
|
+
if (!isHistoryIdIn(trs, hdpTr.historyId)) {
|
|
27
|
+
const key = getRemovedKey(hdpTr.status);
|
|
28
|
+
if (key) {
|
|
29
|
+
stats[key]--;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return stats;
|
|
34
|
+
};
|
|
35
|
+
const getNewFrom = (trs, hdpTrs) => {
|
|
36
|
+
const stats = createEmptyStats(groupKeys);
|
|
37
|
+
for (const tr of trs) {
|
|
38
|
+
if (!isHistoryIdIn(hdpTrs, tr.historyId)) {
|
|
39
|
+
const key = getNewKey(tr.status);
|
|
40
|
+
if (key) {
|
|
41
|
+
stats[key]++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return stats;
|
|
46
|
+
};
|
|
47
|
+
const getPointStats = (currentTrs, hdpTrs) => {
|
|
48
|
+
const emptyStats = createEmptyStats(groupKeys);
|
|
49
|
+
const newStats = getNewFrom(currentTrs, hdpTrs);
|
|
50
|
+
const deletedStats = getDeletedFrom(currentTrs, hdpTrs);
|
|
51
|
+
return Object.keys(emptyStats).reduce((acc, key) => {
|
|
52
|
+
const newStat = newStats[key] ?? 0;
|
|
53
|
+
const deletedStat = deletedStats[key] ?? 0;
|
|
54
|
+
acc[key] = newStat + deletedStat;
|
|
55
|
+
return acc;
|
|
56
|
+
}, {});
|
|
57
|
+
};
|
|
58
|
+
const getCurrentStats = (testResults, hdpTrs) => {
|
|
59
|
+
return {
|
|
60
|
+
groupId: "current",
|
|
61
|
+
...getPointStats(testResults, hdpTrs),
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
const getHistoricalStats = (hdps) => {
|
|
65
|
+
const trendData = [];
|
|
66
|
+
for (let i = 0; i < hdps.length; i++) {
|
|
67
|
+
const currentHdp = hdps[i];
|
|
68
|
+
const currentHdpTrs = Object.values(currentHdp.testResults);
|
|
69
|
+
const previousHdpTrs = i + 1 < hdps.length ? Object.values(hdps[i + 1].testResults) : [];
|
|
70
|
+
trendData.push({
|
|
71
|
+
groupId: `Point ${hdps.length - i - 1}`,
|
|
72
|
+
...getPointStats(currentHdpTrs, previousHdpTrs),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return trendData;
|
|
76
|
+
};
|
|
77
|
+
const getTrendData = (currentTrs, hdps) => {
|
|
78
|
+
const historicalStats = getHistoricalStats(hdps);
|
|
79
|
+
const currentStats = getCurrentStats(currentTrs, Object.values(hdps[0].testResults));
|
|
80
|
+
return [currentStats, ...historicalStats];
|
|
81
|
+
};
|
|
82
|
+
export const statusChangeTrendBarAccessor = {
|
|
83
|
+
getItems: ({ testResults }, limitedHdps, isFullHistory) => {
|
|
84
|
+
let trendData = getTrendData(testResults, limitedHdps);
|
|
85
|
+
if (!isFullHistory) {
|
|
86
|
+
trendData = trendData.slice(0, -1);
|
|
87
|
+
}
|
|
88
|
+
return trendData.reverse();
|
|
89
|
+
},
|
|
90
|
+
getGroupKeys: () => groupKeys,
|
|
91
|
+
getGroupMode: () => BarGroupMode.Stacked,
|
|
92
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { statusesList } from "@allurereport/core-api";
|
|
2
|
+
import { createEmptyStats } from "../../charts.js";
|
|
3
|
+
export const statusTrendDataAccessor = {
|
|
4
|
+
getCurrentData: ({ statistic }) => {
|
|
5
|
+
return {
|
|
6
|
+
...createEmptyStats(statusesList),
|
|
7
|
+
...statistic,
|
|
8
|
+
};
|
|
9
|
+
},
|
|
10
|
+
getHistoricalData: (historyPoint) => {
|
|
11
|
+
return Object.values(historyPoint.testResults).reduce((stat, test) => {
|
|
12
|
+
if (test.status) {
|
|
13
|
+
stat[test.status] = (stat[test.status] ?? 0) + 1;
|
|
14
|
+
}
|
|
15
|
+
return stat;
|
|
16
|
+
}, createEmptyStats(statusesList));
|
|
17
|
+
},
|
|
18
|
+
getAllValues: () => statusesList,
|
|
19
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { TestStatus } from "@allurereport/core-api";
|
|
2
|
+
import { type BarDataAccessor } from "../../charts.js";
|
|
3
|
+
type TrendKey = Extract<TestStatus, "passed" | "failed" | "broken">;
|
|
4
|
+
export declare const statusTrendBarAccessor: BarDataAccessor<string, TrendKey>;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { BarGroupMode, htrsByTr } from "@allurereport/core-api";
|
|
2
|
+
import { createEmptyStats } from "../../charts.js";
|
|
3
|
+
const groupKeys = ["passed", "failed", "broken"];
|
|
4
|
+
const isGroupKey = (key) => groupKeys.includes(key);
|
|
5
|
+
const getSignedValueByStatus = (status) => (status === "passed" ? 1 : -1);
|
|
6
|
+
const hasSignificantStatus = (htr) => isGroupKey(htr.status);
|
|
7
|
+
const getLastSignificantStatus = (history = []) => {
|
|
8
|
+
const significantHtr = history.find(hasSignificantStatus);
|
|
9
|
+
return significantHtr?.status;
|
|
10
|
+
};
|
|
11
|
+
const isDifferentStatuses = (currentStatus, lastSignificantStatus) => {
|
|
12
|
+
return (!!lastSignificantStatus &&
|
|
13
|
+
isGroupKey(currentStatus) &&
|
|
14
|
+
isGroupKey(lastSignificantStatus) &&
|
|
15
|
+
currentStatus !== lastSignificantStatus);
|
|
16
|
+
};
|
|
17
|
+
const getPointStats = (currentTrs, hdps) => {
|
|
18
|
+
const stats = createEmptyStats(groupKeys);
|
|
19
|
+
for (const tr of currentTrs) {
|
|
20
|
+
const htrs = htrsByTr(hdps, tr);
|
|
21
|
+
const currentStatus = tr.status;
|
|
22
|
+
const lastSignificantStatus = getLastSignificantStatus(htrs);
|
|
23
|
+
if (isDifferentStatuses(currentStatus, lastSignificantStatus)) {
|
|
24
|
+
stats[currentStatus] = stats[currentStatus] + getSignedValueByStatus(currentStatus);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return stats;
|
|
28
|
+
};
|
|
29
|
+
const getCurrentStats = (testResults, hdps) => {
|
|
30
|
+
return {
|
|
31
|
+
groupId: "current",
|
|
32
|
+
...getPointStats(testResults, hdps),
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
const getHistoricalStats = (hdps) => {
|
|
36
|
+
const trendData = [];
|
|
37
|
+
for (let i = 0; i < hdps.length; i++) {
|
|
38
|
+
const currentHdp = hdps[i];
|
|
39
|
+
const currentHdpTrs = Object.values(currentHdp.testResults);
|
|
40
|
+
const restHdps = i + 1 < hdps.length ? hdps.slice(i + 1, hdps.length) : [];
|
|
41
|
+
trendData.push({
|
|
42
|
+
groupId: `Point ${hdps.length - i - 1}`,
|
|
43
|
+
...getPointStats(currentHdpTrs, restHdps),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return trendData;
|
|
47
|
+
};
|
|
48
|
+
const getTrendData = (currentTrs, hdps) => {
|
|
49
|
+
const historicalStats = getHistoricalStats(hdps);
|
|
50
|
+
const currentStats = getCurrentStats(currentTrs, hdps);
|
|
51
|
+
return [currentStats, ...historicalStats];
|
|
52
|
+
};
|
|
53
|
+
export const statusTrendBarAccessor = {
|
|
54
|
+
getItems: ({ testResults }, limitedHdps, isFullHistory) => {
|
|
55
|
+
let trendData = getTrendData(testResults, limitedHdps);
|
|
56
|
+
if (!isFullHistory) {
|
|
57
|
+
trendData = trendData.slice(0, -1);
|
|
58
|
+
}
|
|
59
|
+
return trendData.reverse();
|
|
60
|
+
},
|
|
61
|
+
getGroupKeys: () => groupKeys,
|
|
62
|
+
getGroupMode: () => BarGroupMode.Stacked,
|
|
63
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TestResult, TreeMapNode } from "@allurereport/core-api";
|
|
2
|
+
import type { TreeMapDataAccessor } from "../../charts.js";
|
|
3
|
+
type SubtreeMetrics = {
|
|
4
|
+
totalTests: number;
|
|
5
|
+
passedTests: number;
|
|
6
|
+
failedTests: number;
|
|
7
|
+
otherTests: number;
|
|
8
|
+
};
|
|
9
|
+
type LeafMetrics = Pick<TestResult, "status">;
|
|
10
|
+
type GroupMetrics = Omit<SubtreeMetrics, "totalTests">;
|
|
11
|
+
type ExtendedTreeMapNode = TreeMapNode<GroupMetrics & Partial<LeafMetrics>>;
|
|
12
|
+
export declare const createSuccessRateDistributionTreeMap: (testResults: TestResult[]) => ExtendedTreeMapNode;
|
|
13
|
+
export declare const successRateDistributionTreeMapAccessor: TreeMapDataAccessor<ExtendedTreeMapNode>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { isChildrenLeavesOnly } from "../../charts.js";
|
|
2
|
+
import { md5 } from "../../utils/misc.js";
|
|
3
|
+
import { createTreeByLabels } from "../../utils/tree.js";
|
|
4
|
+
import { convertTreeDataToTreeMapNode, transformTreeMapNode } from "../treeMap.js";
|
|
5
|
+
import { behaviorLabels, filterTestsWithBehaviorLabels } from "./utils/behavior.js";
|
|
6
|
+
const leafFactoryFn = ({ id, name, status }) => ({
|
|
7
|
+
nodeId: id,
|
|
8
|
+
name,
|
|
9
|
+
status,
|
|
10
|
+
value: 1,
|
|
11
|
+
});
|
|
12
|
+
const groupFactoryFn = (parentId, groupClassifier) => ({
|
|
13
|
+
nodeId: md5((parentId ? `${parentId}.` : "") + groupClassifier),
|
|
14
|
+
name: groupClassifier,
|
|
15
|
+
value: 0,
|
|
16
|
+
passedTests: 0,
|
|
17
|
+
failedTests: 0,
|
|
18
|
+
otherTests: 0,
|
|
19
|
+
});
|
|
20
|
+
const addLeafToGroupFn = (group, leaf) => {
|
|
21
|
+
group.value += leaf.value;
|
|
22
|
+
group.passedTests += leaf.status === "passed" ? 1 : 0;
|
|
23
|
+
group.failedTests += leaf.status === "failed" ? 1 : 0;
|
|
24
|
+
group.otherTests += leaf?.status && !["passed", "failed"].includes(leaf.status) ? 1 : 0;
|
|
25
|
+
};
|
|
26
|
+
const calculateColorValue = ({ totalTests, passedTests }) => {
|
|
27
|
+
return totalTests > 0 ? passedTests / totalTests : 0;
|
|
28
|
+
};
|
|
29
|
+
const calculateSubtreeMetrics = (node) => {
|
|
30
|
+
if (!node.children || node.children.length === 0) {
|
|
31
|
+
return {
|
|
32
|
+
totalTests: 1,
|
|
33
|
+
passedTests: node?.status === "passed" ? 1 : 0,
|
|
34
|
+
failedTests: node?.status === "failed" ? 1 : 0,
|
|
35
|
+
otherTests: node?.status && !["passed", "failed"].includes(node.status) ? 1 : 0,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
let totalTests = 0;
|
|
39
|
+
let passedTests = 0;
|
|
40
|
+
let failedTests = 0;
|
|
41
|
+
let otherTests = 0;
|
|
42
|
+
for (const child of node.children) {
|
|
43
|
+
const childMetrics = calculateSubtreeMetrics(child);
|
|
44
|
+
totalTests += childMetrics.totalTests;
|
|
45
|
+
passedTests += childMetrics.passedTests;
|
|
46
|
+
failedTests += childMetrics.failedTests;
|
|
47
|
+
otherTests += childMetrics.otherTests;
|
|
48
|
+
}
|
|
49
|
+
return { totalTests, passedTests, failedTests, otherTests };
|
|
50
|
+
};
|
|
51
|
+
export const createSuccessRateDistributionTreeMap = (testResults) => {
|
|
52
|
+
const treeByLabels = createTreeByLabels(testResults, behaviorLabels, leafFactoryFn, groupFactoryFn, addLeafToGroupFn);
|
|
53
|
+
const convertedTree = convertTreeDataToTreeMapNode(treeByLabels, (node, isGroup) => {
|
|
54
|
+
const baseNode = {
|
|
55
|
+
id: node.name,
|
|
56
|
+
value: isGroup ? undefined : node.value,
|
|
57
|
+
};
|
|
58
|
+
if (isGroup) {
|
|
59
|
+
const group = node;
|
|
60
|
+
return {
|
|
61
|
+
...baseNode,
|
|
62
|
+
passedTests: group.passedTests,
|
|
63
|
+
failedTests: group.failedTests,
|
|
64
|
+
otherTests: group.otherTests,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const leaf = node;
|
|
69
|
+
return {
|
|
70
|
+
...baseNode,
|
|
71
|
+
status: leaf.status,
|
|
72
|
+
passedTests: leaf?.status === "passed" ? 1 : 0,
|
|
73
|
+
failedTests: leaf?.status === "failed" ? 1 : 0,
|
|
74
|
+
otherTests: leaf?.status && !["passed", "failed"].includes(leaf.status) ? 1 : 0,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}, () => ({
|
|
78
|
+
id: "root",
|
|
79
|
+
passedTests: 0,
|
|
80
|
+
failedTests: 0,
|
|
81
|
+
otherTests: 0,
|
|
82
|
+
}));
|
|
83
|
+
return transformTreeMapNode(convertedTree, (node) => {
|
|
84
|
+
const subtreeMetrics = calculateSubtreeMetrics(node);
|
|
85
|
+
const colorValue = calculateColorValue(subtreeMetrics);
|
|
86
|
+
const { totalTests, ...restSubtreeMetrics } = subtreeMetrics;
|
|
87
|
+
if (isChildrenLeavesOnly(node)) {
|
|
88
|
+
const value = node.children?.reduce((acc, child) => {
|
|
89
|
+
return acc + (child.value ?? 0);
|
|
90
|
+
}, 0);
|
|
91
|
+
return {
|
|
92
|
+
...node,
|
|
93
|
+
value,
|
|
94
|
+
children: undefined,
|
|
95
|
+
colorValue,
|
|
96
|
+
...restSubtreeMetrics,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
...node,
|
|
101
|
+
colorValue,
|
|
102
|
+
...restSubtreeMetrics,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
export const successRateDistributionTreeMapAccessor = {
|
|
107
|
+
getTreeMap: ({ testResults }) => {
|
|
108
|
+
const testsWithBehaviorLabels = filterTestsWithBehaviorLabels(testResults);
|
|
109
|
+
return createSuccessRateDistributionTreeMap(testsWithBehaviorLabels);
|
|
110
|
+
},
|
|
111
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { HistoryTestResult, TestResult } from "@allurereport/core-api";
|
|
2
|
+
export type BehaviorLabel = "epic" | "feature" | "story";
|
|
3
|
+
export declare const behaviorLabels: BehaviorLabel[];
|
|
4
|
+
export declare const hasBehaviorLabels: <T extends TestResult | HistoryTestResult>(test: T) => boolean;
|
|
5
|
+
export declare const filterTestsWithBehaviorLabels: <T extends TestResult | HistoryTestResult>(tests: T[]) => T[];
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { hasLabels } from "../../../charts.js";
|
|
2
|
+
export const behaviorLabels = ["epic", "feature", "story"];
|
|
3
|
+
export const hasBehaviorLabels = (test) => hasLabels(test, behaviorLabels);
|
|
4
|
+
export const filterTestsWithBehaviorLabels = (tests) => tests.filter(hasBehaviorLabels);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { AllureChartsStoreData, BarChartData, BarChartOptions, BarDataAccessor } from "../charts.js";
|
|
2
|
+
export declare const generateBarChartGeneric: <P extends string, T extends string>(options: BarChartOptions, storeData: AllureChartsStoreData, dataAccessor: BarDataAccessor<P, T>) => BarChartData | undefined;
|
|
3
|
+
export declare const generateBarChart: (options: BarChartOptions, storeData: AllureChartsStoreData) => BarChartData | undefined;
|