@allurereport/web-commons 3.20.1 → 3.22.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/dist/attachments.d.ts +3 -2
- package/dist/attachments.js +9 -3
- package/dist/charts/config.d.ts +2 -0
- package/dist/charts/config.js +15 -0
- package/dist/charts/generateDurationDynamicsChart.js +3 -2
- package/dist/charts/generateDurationsChart.js +1 -1
- package/dist/charts/generateStabilityDistributionChart.d.ts +0 -2
- package/dist/charts/generateStabilityDistributionChart.js +37 -132
- package/dist/charts/generators.js +15 -2
- package/dist/charts/index.d.ts +1 -0
- package/dist/charts/index.js +1 -0
- package/dist/contentTypes.d.ts +5 -0
- package/dist/contentTypes.js +11 -0
- package/dist/httpAttachments.d.ts +79 -0
- package/dist/httpAttachments.js +160 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/platform.d.ts +1 -0
- package/dist/platform.js +8 -0
- package/dist/stores/url/store.d.ts +0 -3
- package/dist/stores/url/store.js +0 -3
- package/dist/treeSubtreeToggle.d.ts +6 -7
- package/dist/treeSubtreeToggle.js +19 -10
- package/package.json +15 -10
package/dist/attachments.d.ts
CHANGED
|
@@ -31,8 +31,9 @@ export declare const fetchAttachment: (id: string, ext: string, contentType?: st
|
|
|
31
31
|
export declare const blobAttachment: (id: string, ext: string, contentType: string) => Promise<Blob>;
|
|
32
32
|
export declare const downloadAttachment: (id: string, ext: string, contentType: string) => Promise<void>;
|
|
33
33
|
export declare const openAttachmentInNewTab: (id: string, ext: string, contentType: string) => Promise<void>;
|
|
34
|
-
export type AttachmentType = "css" | "json" | "image" | "svg" | "code" | "text" | "markdown" | "html" | "table" | "video" | "uri" | "archive" | "image-diff" | "http";
|
|
35
|
-
export declare const
|
|
34
|
+
export type AttachmentType = "css" | "json" | "image" | "svg" | "code" | "text" | "markdown" | "html" | "table" | "video" | "uri" | "archive" | "image-diff" | "http" | "mermaid";
|
|
35
|
+
export declare const MERMAID_CONTENT_TYPE = "application/vnd.allure.diagrams.mermaid";
|
|
36
|
+
export declare const PREVIEWABLE_CONTENT_TYPES: readonly ["application/vnd.allure.diagrams.mermaid", "text/html", "text/csv", "text/markdown", "text/tab-separated-values", "text/uri-list"];
|
|
36
37
|
export declare const isPreviewableContentType: (type?: string) => boolean;
|
|
37
38
|
export declare const extname: (str: string) => string | undefined;
|
|
38
39
|
export declare const isSyntaxHighlightSupported: (payload?: {
|
package/dist/attachments.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { normalizeAttachmentContentType } from "./contentTypes.js";
|
|
1
2
|
import { fetchReportAttachment } from "./data.js";
|
|
2
3
|
const PLAYWRIGHT_TRACE_CONTENT_TYPE = "application/vnd.allure.playwright-trace";
|
|
3
4
|
const PLAYWRIGHT_TRACE_DOWNLOAD_CONTENT_TYPE = "application/zip";
|
|
4
5
|
const downloadContentType = (contentType) => {
|
|
5
|
-
return contentType
|
|
6
|
+
return normalizeAttachmentContentType(contentType) === PLAYWRIGHT_TRACE_CONTENT_TYPE
|
|
6
7
|
? PLAYWRIGHT_TRACE_DOWNLOAD_CONTENT_TYPE
|
|
7
8
|
: contentType;
|
|
8
9
|
};
|
|
@@ -30,6 +31,7 @@ export const fetchAttachment = async (id, ext, contentType) => {
|
|
|
30
31
|
case "code":
|
|
31
32
|
case "html":
|
|
32
33
|
case "markdown":
|
|
34
|
+
case "mermaid":
|
|
33
35
|
case "table":
|
|
34
36
|
case "text": {
|
|
35
37
|
const text = await response.text();
|
|
@@ -77,14 +79,16 @@ export const openAttachmentInNewTab = async (id, ext, contentType) => {
|
|
|
77
79
|
const linkUrl = URL.createObjectURL(blob);
|
|
78
80
|
globalThis.open(linkUrl, "_blank");
|
|
79
81
|
};
|
|
82
|
+
export const MERMAID_CONTENT_TYPE = "application/vnd.allure.diagrams.mermaid";
|
|
80
83
|
export const PREVIEWABLE_CONTENT_TYPES = [
|
|
84
|
+
MERMAID_CONTENT_TYPE,
|
|
81
85
|
"text/html",
|
|
82
86
|
"text/csv",
|
|
83
87
|
"text/markdown",
|
|
84
88
|
"text/tab-separated-values",
|
|
85
89
|
"text/uri-list",
|
|
86
90
|
];
|
|
87
|
-
export const isPreviewableContentType = (type) => !!type && PREVIEWABLE_CONTENT_TYPES.includes(type);
|
|
91
|
+
export const isPreviewableContentType = (type) => !!type && PREVIEWABLE_CONTENT_TYPES.includes(normalizeAttachmentContentType(type));
|
|
88
92
|
const HIGHLIGHT_EXTS = new Set([
|
|
89
93
|
"js",
|
|
90
94
|
"mjs",
|
|
@@ -193,7 +197,7 @@ export const isSyntaxHighlightSupported = (payload) => {
|
|
|
193
197
|
return !!ext && HIGHLIGHT_EXTS.has(ext);
|
|
194
198
|
};
|
|
195
199
|
export const attachmentType = (type) => {
|
|
196
|
-
const normalizedType = type
|
|
200
|
+
const normalizedType = normalizeAttachmentContentType(type);
|
|
197
201
|
switch (normalizedType) {
|
|
198
202
|
case "image/bmp":
|
|
199
203
|
case "image/gif":
|
|
@@ -263,6 +267,8 @@ export const attachmentType = (type) => {
|
|
|
263
267
|
return "image-diff";
|
|
264
268
|
case "application/vnd.allure.http+json":
|
|
265
269
|
return "http";
|
|
270
|
+
case MERMAID_CONTENT_TYPE:
|
|
271
|
+
return "mermaid";
|
|
266
272
|
default:
|
|
267
273
|
return null;
|
|
268
274
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ChartType } from "@allurereport/charts-api";
|
|
2
|
+
export const validateChartsOptions = (charts) => {
|
|
3
|
+
for (const options of charts) {
|
|
4
|
+
if (options.type !== ChartType.StabilityDistribution) {
|
|
5
|
+
continue;
|
|
6
|
+
}
|
|
7
|
+
const { limit, stabilizationPeriod } = options;
|
|
8
|
+
if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {
|
|
9
|
+
throw new RangeError("stabilityDistribution.limit must be a non-negative integer");
|
|
10
|
+
}
|
|
11
|
+
if (stabilizationPeriod !== undefined && (!Number.isInteger(stabilizationPeriod) || stabilizationPeriod < 1)) {
|
|
12
|
+
throw new RangeError("stabilityDistribution.stabilizationPeriod must be a positive integer");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -43,7 +43,8 @@ const getStats = (testResults) => {
|
|
|
43
43
|
export const generateDurationDynamicsChart = (props) => {
|
|
44
44
|
const { options, storeData } = props;
|
|
45
45
|
const { title, limit = DEFAULT_CHART_HISTORY_LIMIT } = options;
|
|
46
|
-
const { historyDataPoints
|
|
46
|
+
const { historyDataPoints } = storeData;
|
|
47
|
+
const testResults = storeData.allTestResults ?? storeData.testResults;
|
|
47
48
|
const limitedHistoryPoints = limitHistoryDataPoints(historyDataPoints, limit - 1).sort((a, b) => a.timestamp - b.timestamp);
|
|
48
49
|
const currentReportTimestamp = testResults.reduce((acc, testResult) => Math.max(acc, testResult.stop ?? 0), 0);
|
|
49
50
|
if (limitedHistoryPoints.length === 0) {
|
|
@@ -63,7 +64,7 @@ export const generateDurationDynamicsChart = (props) => {
|
|
|
63
64
|
...limitedHistoryPoints,
|
|
64
65
|
{
|
|
65
66
|
testResults: testResults.reduce((acc, testResult) => {
|
|
66
|
-
acc[testResult.
|
|
67
|
+
acc[testResult.id] = testResult;
|
|
67
68
|
return acc;
|
|
68
69
|
}, {}),
|
|
69
70
|
uuid: "current",
|
|
@@ -51,7 +51,7 @@ const getBucketsFromDurations = (durations) => {
|
|
|
51
51
|
export const generateDurationsChart = (props) => {
|
|
52
52
|
const { options, storeData } = props;
|
|
53
53
|
const { groupBy = BY_NONE, title } = options;
|
|
54
|
-
const
|
|
54
|
+
const testResults = storeData.allTestResults ?? storeData.testResults;
|
|
55
55
|
const enrichedTrs = enrichAndFilterTrs(testResults, groupBy);
|
|
56
56
|
const durations = getDurations(enrichedTrs);
|
|
57
57
|
const buckets = durations.size > 0 ? getBucketsFromDurations(Array.from(durations)) : [];
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import type { AllureChartsStoreData, StabilityDistributionChartData, StabilityDistributionChartOptions } from "@allurereport/charts-api";
|
|
2
|
-
import { type TestStatus } from "@allurereport/core-api";
|
|
3
|
-
export declare const getStabilityScore: (statuses: TestStatus[], historyDepth: number, stabilizationPeriod: number) => number | undefined;
|
|
4
2
|
export declare const generateStabilityDistributionChart: (props: {
|
|
5
3
|
options: StabilityDistributionChartOptions;
|
|
6
4
|
storeData: AllureChartsStoreData;
|
|
@@ -1,158 +1,63 @@
|
|
|
1
1
|
import { ChartType } from "@allurereport/charts-api";
|
|
2
|
-
import { createHistoryTestResultLookup, } from "@allurereport/core-api";
|
|
3
|
-
import { createHashStorage
|
|
4
|
-
const DEFAULT_LIMIT = 10;
|
|
5
|
-
const DEFAULT_STABILIZATION_PERIOD = 5;
|
|
2
|
+
import { createHistoryTestResultLookup, getTestFlakiness } from "@allurereport/core-api";
|
|
3
|
+
import { createHashStorage } from "./utils.js";
|
|
6
4
|
const DEFAULT_THRESHOLD = 90;
|
|
7
5
|
const DEFAULT_GROUP_BY = "feature";
|
|
8
6
|
const CUSTOM_LABEL_NAME_PREFIX = "label-name:";
|
|
9
7
|
const SIGNIFICANT_STATUSES = new Set(["passed", "failed", "broken"]);
|
|
10
|
-
const isSignificantStatus = (status) => SIGNIFICANT_STATUSES.has(status);
|
|
11
|
-
const getTrLabelValue = (testResult, labelName) => {
|
|
12
|
-
return testResult.labels?.find((label) => label.name === labelName)?.value;
|
|
13
|
-
};
|
|
14
8
|
const NON_SIGNIFICANT_STATUSES = ["unknown", "skipped"];
|
|
15
|
-
const findLatestConsecutiveSameBlock = (statuses, sequenceLength, blockSize) => {
|
|
16
|
-
for (let start = sequenceLength - blockSize; start >= 0; start--) {
|
|
17
|
-
const first = statuses[start];
|
|
18
|
-
let allSame = true;
|
|
19
|
-
for (let k = 1; k < blockSize; k++) {
|
|
20
|
-
if (statuses[start + k] !== first) {
|
|
21
|
-
allSame = false;
|
|
22
|
-
break;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
if (allSame) {
|
|
26
|
-
return { endIndex: start + blockSize - 1, status: first };
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return null;
|
|
30
|
-
};
|
|
31
|
-
const computeTransitionsInRange = (statuses, firstEdge, lastEdge) => {
|
|
32
|
-
let transitionCount = 0;
|
|
33
|
-
let firstTransitionIdx = 0;
|
|
34
|
-
let secondTransitionIdx = 0;
|
|
35
|
-
let instabilitySumNumerator = 0;
|
|
36
|
-
for (let i = firstEdge; i <= lastEdge; i++) {
|
|
37
|
-
if (statuses[i - 1] === statuses[i]) {
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
transitionCount++;
|
|
41
|
-
if (transitionCount === 1) {
|
|
42
|
-
firstTransitionIdx = i;
|
|
43
|
-
}
|
|
44
|
-
else if (transitionCount === 2) {
|
|
45
|
-
secondTransitionIdx = i;
|
|
46
|
-
}
|
|
47
|
-
instabilitySumNumerator += i;
|
|
48
|
-
}
|
|
49
|
-
return { transitionCount, firstTransitionIdx, secondTransitionIdx, instabilitySumNumerator };
|
|
50
|
-
};
|
|
51
|
-
const isRecoveryTolerancePattern = (statuses, firstTransitionIdx, secondTransitionIdx) => {
|
|
52
|
-
const s0 = statuses[firstTransitionIdx - 1];
|
|
53
|
-
const s1 = statuses[firstTransitionIdx];
|
|
54
|
-
const s2 = statuses[secondTransitionIdx - 1];
|
|
55
|
-
const s3 = statuses[secondTransitionIdx];
|
|
56
|
-
return ((s0 === "passed" && s1 === "broken" && s2 === "broken" && s3 === "passed") ||
|
|
57
|
-
(s0 === "passed" && s1 === "failed" && s2 === "failed" && s3 === "passed"));
|
|
58
|
-
};
|
|
59
|
-
export const getStabilityScore = (statuses, historyDepth, stabilizationPeriod) => {
|
|
60
|
-
const effectiveSequenceLength = Math.min(statuses.length, historyDepth);
|
|
61
|
-
if (effectiveSequenceLength === 0) {
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
if (effectiveSequenceLength === 1) {
|
|
65
|
-
return 1;
|
|
66
|
-
}
|
|
67
|
-
const stabilBlock = findLatestConsecutiveSameBlock(statuses, effectiveSequenceLength, stabilizationPeriod);
|
|
68
|
-
if (stabilBlock !== null && stabilBlock.endIndex === effectiveSequenceLength - 1) {
|
|
69
|
-
return 1;
|
|
70
|
-
}
|
|
71
|
-
const firstEdgeAfterStabilBlock = stabilBlock !== null ? stabilBlock.endIndex + 1 : 1;
|
|
72
|
-
const lastEdge = effectiveSequenceLength - 1;
|
|
73
|
-
const denominator = ((effectiveSequenceLength - 1) * effectiveSequenceLength) / 2;
|
|
74
|
-
const { transitionCount, firstTransitionIdx, secondTransitionIdx, instabilitySumNumerator } = computeTransitionsInRange(statuses, firstEdgeAfterStabilBlock, lastEdge);
|
|
75
|
-
if (transitionCount === 1) {
|
|
76
|
-
return 1;
|
|
77
|
-
}
|
|
78
|
-
if (transitionCount === 2 && isRecoveryTolerancePattern(statuses, firstTransitionIdx, secondTransitionIdx)) {
|
|
79
|
-
return 1;
|
|
80
|
-
}
|
|
81
|
-
return instabilitySumNumerator / denominator;
|
|
82
|
-
};
|
|
83
|
-
const getStatusSequence = (historyDataPoints, tr, lookupHistoryTestResult) => {
|
|
84
|
-
let block = [];
|
|
85
|
-
for (const hdp of historyDataPoints) {
|
|
86
|
-
const htr = lookupHistoryTestResult(hdp, tr);
|
|
87
|
-
if (!htr) {
|
|
88
|
-
block = [];
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
if (isSignificantStatus(htr.status)) {
|
|
92
|
-
block.push(htr.status);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (isSignificantStatus(tr.status)) {
|
|
96
|
-
block.push(tr.status);
|
|
97
|
-
}
|
|
98
|
-
return block;
|
|
99
|
-
};
|
|
100
9
|
export const generateStabilityDistributionChart = (props) => {
|
|
101
10
|
const { options, storeData } = props;
|
|
102
|
-
const { title, limit
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
threshold,
|
|
113
|
-
};
|
|
11
|
+
const { title, limit, stabilizationPeriod, threshold = DEFAULT_THRESHOLD, skipStatuses: skipStatusesList = NON_SIGNIFICANT_STATUSES, groupBy = DEFAULT_GROUP_BY, groupValues = [], } = options;
|
|
12
|
+
const chart = {
|
|
13
|
+
data: [],
|
|
14
|
+
keys: {},
|
|
15
|
+
type: ChartType.StabilityDistribution,
|
|
16
|
+
title,
|
|
17
|
+
threshold,
|
|
18
|
+
};
|
|
19
|
+
if (limit === 0) {
|
|
20
|
+
return chart;
|
|
114
21
|
}
|
|
115
|
-
const
|
|
22
|
+
const { testResults, historyDataPoints } = storeData;
|
|
23
|
+
const lookup = createHistoryTestResultLookup(storeData.allTestResults ?? testResults);
|
|
24
|
+
const history = [...historyDataPoints].sort((a, b) => b.timestamp - a.timestamp);
|
|
116
25
|
const labelName = groupBy.startsWith(CUSTOM_LABEL_NAME_PREFIX)
|
|
117
26
|
? groupBy.slice(CUSTOM_LABEL_NAME_PREFIX.length)
|
|
118
27
|
: groupBy;
|
|
119
28
|
const groupValuesSet = new Set(groupValues ?? []);
|
|
120
|
-
const
|
|
121
|
-
const keys = {};
|
|
29
|
+
const groups = new Map();
|
|
122
30
|
const hashes = createHashStorage();
|
|
123
31
|
const skipStatuses = new Set(skipStatusesList);
|
|
124
32
|
for (const tr of testResults) {
|
|
125
|
-
if (!tr.
|
|
33
|
+
if (!SIGNIFICANT_STATUSES.has(tr.status) || skipStatuses.has(tr.status)) {
|
|
126
34
|
continue;
|
|
127
35
|
}
|
|
128
|
-
|
|
36
|
+
const labelValue = tr.labels?.find((label) => label.name === labelName)?.value;
|
|
37
|
+
if (!labelValue || (groupValuesSet.size > 0 && !groupValuesSet.has(labelValue))) {
|
|
129
38
|
continue;
|
|
130
39
|
}
|
|
131
|
-
|
|
40
|
+
const flaky = tr.sourceMetadata?.reportedFlaky === true
|
|
41
|
+
? true
|
|
42
|
+
: getTestFlakiness(tr, history.map((point) => lookup(point, tr)), { historyDepth: limit, stabilizationPeriod });
|
|
43
|
+
if (flaky === undefined) {
|
|
132
44
|
continue;
|
|
133
45
|
}
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
46
|
+
const id = hashes.get(labelValue);
|
|
47
|
+
let counts = groups.get(id);
|
|
48
|
+
if (!counts) {
|
|
49
|
+
counts = { stable: 0, assessed: 0 };
|
|
50
|
+
groups.set(id, counts);
|
|
51
|
+
chart.keys[id] = labelValue;
|
|
137
52
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
continue;
|
|
53
|
+
counts.assessed++;
|
|
54
|
+
if (!flaky) {
|
|
55
|
+
counts.stable++;
|
|
142
56
|
}
|
|
143
|
-
const labelValueHash = hashes.get(labelValue);
|
|
144
|
-
keys[labelValueHash] = labelValue;
|
|
145
|
-
stabilityScoresByGroup.get(labelValueHash).push(stabilityScore);
|
|
146
57
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}),
|
|
153
|
-
keys,
|
|
154
|
-
type: ChartType.StabilityDistribution,
|
|
155
|
-
title,
|
|
156
|
-
threshold,
|
|
157
|
-
};
|
|
58
|
+
chart.data = [...groups].map(([id, counts]) => ({
|
|
59
|
+
id,
|
|
60
|
+
stabilityRate: Math.floor((counts.stable * 10000) / counts.assessed) / 100,
|
|
61
|
+
}));
|
|
62
|
+
return chart;
|
|
158
63
|
};
|
|
@@ -28,6 +28,19 @@ const generateChartData = async (props) => {
|
|
|
28
28
|
}
|
|
29
29
|
return trs;
|
|
30
30
|
};
|
|
31
|
+
const getAllTrs = async () => {
|
|
32
|
+
let trs = [];
|
|
33
|
+
if (envId) {
|
|
34
|
+
trs = await store.testResultsByEnvironmentId(envId, { includeRetries: true });
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
trs = await store.allTestResults({ includeRetries: true });
|
|
38
|
+
}
|
|
39
|
+
if (filter) {
|
|
40
|
+
trs = trs.filter(filter);
|
|
41
|
+
}
|
|
42
|
+
return trs;
|
|
43
|
+
};
|
|
31
44
|
const getHistoryDataPoints = async () => {
|
|
32
45
|
let historyDataPoints = [];
|
|
33
46
|
if (envId) {
|
|
@@ -43,7 +56,7 @@ const generateChartData = async (props) => {
|
|
|
43
56
|
try {
|
|
44
57
|
return filter(tr);
|
|
45
58
|
}
|
|
46
|
-
catch
|
|
59
|
+
catch {
|
|
47
60
|
return false;
|
|
48
61
|
}
|
|
49
62
|
});
|
|
@@ -66,7 +79,7 @@ const generateChartData = async (props) => {
|
|
|
66
79
|
getHistoryDataPoints(),
|
|
67
80
|
getTrs(),
|
|
68
81
|
getStatistic(),
|
|
69
|
-
|
|
82
|
+
getAllTrs(),
|
|
70
83
|
]).then(([historyDataPoints, testResults, statistic, allTestResults]) => ({
|
|
71
84
|
historyDataPoints,
|
|
72
85
|
testResults,
|
package/dist/charts/index.d.ts
CHANGED
package/dist/charts/index.js
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function normalizeAttachmentContentType(type: string): string;
|
|
2
|
+
export declare function normalizeAttachmentContentType(type?: null | undefined): undefined;
|
|
3
|
+
export declare function normalizeAttachmentContentType(type?: string | null): string | undefined;
|
|
4
|
+
export declare const isJsonAttachmentContentType: (type: string) => boolean;
|
|
5
|
+
export declare const isHtmlAttachmentContentType: (type: string) => boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function normalizeAttachmentContentType(type) {
|
|
2
|
+
return type?.split(";")[0].trim().toLowerCase();
|
|
3
|
+
}
|
|
4
|
+
export const isJsonAttachmentContentType = (type) => {
|
|
5
|
+
const normalized = normalizeAttachmentContentType(type);
|
|
6
|
+
return normalized === "application/json" || normalized === "text/json" || normalized.endsWith("+json");
|
|
7
|
+
};
|
|
8
|
+
export const isHtmlAttachmentContentType = (type) => {
|
|
9
|
+
const normalized = normalizeAttachmentContentType(type);
|
|
10
|
+
return normalized === "text/html" || normalized === "application/xhtml+xml";
|
|
11
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export declare const HTTP_EXCHANGE_REDACTED_VALUE = "__ALLURE_REDACTED__";
|
|
2
|
+
export type HttpPair = {
|
|
3
|
+
name: string;
|
|
4
|
+
value: string;
|
|
5
|
+
masked: boolean;
|
|
6
|
+
};
|
|
7
|
+
export type HttpCookie = HttpPair & {
|
|
8
|
+
domain: string;
|
|
9
|
+
expires: string;
|
|
10
|
+
httpOnly: boolean;
|
|
11
|
+
maxAge?: number;
|
|
12
|
+
path: string;
|
|
13
|
+
sameSite: string;
|
|
14
|
+
secure: boolean;
|
|
15
|
+
};
|
|
16
|
+
export type HttpStream = {
|
|
17
|
+
type: string;
|
|
18
|
+
complete: string;
|
|
19
|
+
chunkCount?: number;
|
|
20
|
+
};
|
|
21
|
+
export type HttpBodyPart = {
|
|
22
|
+
name: string;
|
|
23
|
+
fileName: string;
|
|
24
|
+
headers: HttpPair[];
|
|
25
|
+
contentType: string;
|
|
26
|
+
encoding: string;
|
|
27
|
+
value: string;
|
|
28
|
+
hasValue: boolean;
|
|
29
|
+
size?: number;
|
|
30
|
+
truncated: boolean;
|
|
31
|
+
};
|
|
32
|
+
export type HttpBody = {
|
|
33
|
+
contentType: string;
|
|
34
|
+
encoding: string;
|
|
35
|
+
value: string;
|
|
36
|
+
hasValue: boolean;
|
|
37
|
+
size?: number;
|
|
38
|
+
truncated: boolean;
|
|
39
|
+
form: HttpPair[];
|
|
40
|
+
parts: HttpBodyPart[];
|
|
41
|
+
stream: HttpStream | null;
|
|
42
|
+
};
|
|
43
|
+
export type HttpRequest = {
|
|
44
|
+
method: string;
|
|
45
|
+
url: string;
|
|
46
|
+
httpVersion: string;
|
|
47
|
+
headers: HttpPair[];
|
|
48
|
+
cookies: HttpCookie[];
|
|
49
|
+
query: HttpPair[];
|
|
50
|
+
body: HttpBody | null;
|
|
51
|
+
trailers: HttpPair[];
|
|
52
|
+
};
|
|
53
|
+
export type HttpInformationalResponse = {
|
|
54
|
+
status: string;
|
|
55
|
+
reason: string;
|
|
56
|
+
headers: HttpPair[];
|
|
57
|
+
};
|
|
58
|
+
export type HttpResponse = {
|
|
59
|
+
status: string;
|
|
60
|
+
reason: string;
|
|
61
|
+
httpVersion: string;
|
|
62
|
+
headers: HttpPair[];
|
|
63
|
+
cookies: HttpCookie[];
|
|
64
|
+
body: HttpBody | null;
|
|
65
|
+
trailers: HttpPair[];
|
|
66
|
+
informationalResponses: HttpInformationalResponse[];
|
|
67
|
+
};
|
|
68
|
+
export type HttpError = {
|
|
69
|
+
name: string;
|
|
70
|
+
message: string;
|
|
71
|
+
stack: string;
|
|
72
|
+
};
|
|
73
|
+
export type NormalizedHttpPayload = {
|
|
74
|
+
request: HttpRequest | null;
|
|
75
|
+
response: HttpResponse | null;
|
|
76
|
+
error: HttpError | null;
|
|
77
|
+
durationMs: string;
|
|
78
|
+
};
|
|
79
|
+
export declare const normalizeHttpExchangePayload: (payload: unknown) => NormalizedHttpPayload;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
export const HTTP_EXCHANGE_REDACTED_VALUE = "__ALLURE_REDACTED__";
|
|
2
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
const toOptionalString = (value) => value === null || typeof value === "undefined" ? "" : String(value);
|
|
4
|
+
const toFiniteNumber = (value) => {
|
|
5
|
+
const num = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : NaN;
|
|
6
|
+
return Number.isFinite(num) ? num : undefined;
|
|
7
|
+
};
|
|
8
|
+
const headerValue = (headers, name) => {
|
|
9
|
+
const normalizedName = name.toLowerCase();
|
|
10
|
+
return headers.find((header) => header.name.toLowerCase() === normalizedName)?.value ?? "";
|
|
11
|
+
};
|
|
12
|
+
const normalizeArray = (value, normalize) => Array.isArray(value) ? value.map(normalize).filter((item) => item !== null) : [];
|
|
13
|
+
const normalizePair = (value) => {
|
|
14
|
+
if (!isRecord(value)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const name = toOptionalString(value.name).trim();
|
|
18
|
+
const pairValue = toOptionalString(value.value);
|
|
19
|
+
const masked = pairValue === HTTP_EXCHANGE_REDACTED_VALUE;
|
|
20
|
+
if (!name && !pairValue) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return { name, value: pairValue, masked };
|
|
24
|
+
};
|
|
25
|
+
const normalizePairs = (value) => normalizeArray(value, normalizePair);
|
|
26
|
+
const normalizeCookie = (value) => {
|
|
27
|
+
if (!isRecord(value)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const pair = normalizePair(value);
|
|
31
|
+
if (!pair) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
...pair,
|
|
36
|
+
domain: toOptionalString(value.domain).trim(),
|
|
37
|
+
expires: toOptionalString(value.expires).trim(),
|
|
38
|
+
httpOnly: value.httpOnly === true,
|
|
39
|
+
maxAge: toFiniteNumber(value.maxAge),
|
|
40
|
+
path: toOptionalString(value.path).trim(),
|
|
41
|
+
sameSite: toOptionalString(value.sameSite).trim(),
|
|
42
|
+
secure: value.secure === true,
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
const normalizeCookies = (value) => normalizeArray(value, normalizeCookie);
|
|
46
|
+
const normalizeStream = (value) => {
|
|
47
|
+
if (!isRecord(value)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
type: toOptionalString(value.type).trim(),
|
|
52
|
+
complete: typeof value.complete === "boolean" ? String(value.complete) : "",
|
|
53
|
+
chunkCount: toFiniteNumber(value.chunkCount),
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
const normalizeBodyPart = (value) => {
|
|
57
|
+
if (!isRecord(value)) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const headers = normalizePairs(value.headers);
|
|
61
|
+
const hasValue = typeof value.value !== "undefined" && value.value !== null;
|
|
62
|
+
return {
|
|
63
|
+
name: toOptionalString(value.name).trim(),
|
|
64
|
+
fileName: toOptionalString(value.fileName).trim(),
|
|
65
|
+
headers,
|
|
66
|
+
contentType: toOptionalString(value.contentType).trim() || headerValue(headers, "content-type"),
|
|
67
|
+
encoding: toOptionalString(value.encoding).trim().toLowerCase() || "utf8",
|
|
68
|
+
value: hasValue ? String(value.value) : "",
|
|
69
|
+
hasValue,
|
|
70
|
+
size: toFiniteNumber(value.size),
|
|
71
|
+
truncated: value.truncated === true,
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
const normalizeBodyParts = (value) => normalizeArray(value, normalizeBodyPart);
|
|
75
|
+
const normalizeBody = (value, fallbackContentType = "") => {
|
|
76
|
+
if (!isRecord(value)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const hasValue = typeof value.value !== "undefined" && value.value !== null;
|
|
80
|
+
return {
|
|
81
|
+
contentType: toOptionalString(value.contentType).trim() || fallbackContentType,
|
|
82
|
+
encoding: toOptionalString(value.encoding).trim().toLowerCase() || "utf8",
|
|
83
|
+
value: hasValue ? String(value.value) : "",
|
|
84
|
+
hasValue,
|
|
85
|
+
size: toFiniteNumber(value.size),
|
|
86
|
+
truncated: value.truncated === true,
|
|
87
|
+
form: normalizePairs(value.form),
|
|
88
|
+
parts: normalizeBodyParts(value.parts),
|
|
89
|
+
stream: normalizeStream(value.stream),
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
const normalizeRequest = (value) => {
|
|
93
|
+
if (!isRecord(value)) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const headers = normalizePairs(value.headers);
|
|
97
|
+
return {
|
|
98
|
+
method: toOptionalString(value.method).trim().toUpperCase() || "GET",
|
|
99
|
+
url: toOptionalString(value.url).trim(),
|
|
100
|
+
httpVersion: toOptionalString(value.httpVersion).trim(),
|
|
101
|
+
headers,
|
|
102
|
+
cookies: normalizeCookies(value.cookies),
|
|
103
|
+
query: normalizePairs(value.query),
|
|
104
|
+
body: normalizeBody(value.body, headerValue(headers, "content-type")),
|
|
105
|
+
trailers: normalizePairs(value.trailers),
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
const normalizeInformationalResponse = (value) => {
|
|
109
|
+
if (!isRecord(value)) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
status: toOptionalString(value.status).trim(),
|
|
114
|
+
reason: toOptionalString(value.statusText).trim(),
|
|
115
|
+
headers: normalizePairs(value.headers),
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
const normalizeInformationalResponses = (value) => normalizeArray(value, normalizeInformationalResponse);
|
|
119
|
+
const normalizeResponse = (value) => {
|
|
120
|
+
if (!isRecord(value)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const headers = normalizePairs(value.headers);
|
|
124
|
+
return {
|
|
125
|
+
status: toOptionalString(value.status).trim(),
|
|
126
|
+
reason: toOptionalString(value.statusText).trim(),
|
|
127
|
+
httpVersion: toOptionalString(value.httpVersion).trim(),
|
|
128
|
+
headers,
|
|
129
|
+
cookies: normalizeCookies(value.cookies),
|
|
130
|
+
body: normalizeBody(value.body, headerValue(headers, "content-type")),
|
|
131
|
+
trailers: normalizePairs(value.trailers),
|
|
132
|
+
informationalResponses: normalizeInformationalResponses(value.informationalResponses),
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
const normalizeError = (value) => {
|
|
136
|
+
if (!isRecord(value)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const name = toOptionalString(value.name).trim();
|
|
140
|
+
const message = toOptionalString(value.message).trim();
|
|
141
|
+
const stack = toOptionalString(value.stack).trim();
|
|
142
|
+
if (!name && !message && !stack) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
return { name, message, stack };
|
|
146
|
+
};
|
|
147
|
+
export const normalizeHttpExchangePayload = (payload) => {
|
|
148
|
+
if (!isRecord(payload)) {
|
|
149
|
+
return { request: null, response: null, error: null, durationMs: "" };
|
|
150
|
+
}
|
|
151
|
+
const start = toFiniteNumber(payload.start);
|
|
152
|
+
const stop = toFiniteNumber(payload.stop);
|
|
153
|
+
const durationMs = start !== undefined && stop !== undefined && stop >= start ? `${stop - start} ms` : "";
|
|
154
|
+
return {
|
|
155
|
+
request: normalizeRequest(payload.request),
|
|
156
|
+
response: normalizeResponse(payload.response),
|
|
157
|
+
error: normalizeError(payload.error),
|
|
158
|
+
durationMs,
|
|
159
|
+
};
|
|
160
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -19,3 +19,6 @@ export * from "./treeSubtreeToggle.js";
|
|
|
19
19
|
export * from "./subtreeExpansion.js";
|
|
20
20
|
export * from "./hotkeys/index.js";
|
|
21
21
|
export * from "./treeNavigation/index.js";
|
|
22
|
+
export * from "./platform.js";
|
|
23
|
+
export * from "./contentTypes.js";
|
|
24
|
+
export * from "./httpAttachments.js";
|
package/dist/index.js
CHANGED
|
@@ -19,3 +19,6 @@ export * from "./treeSubtreeToggle.js";
|
|
|
19
19
|
export * from "./subtreeExpansion.js";
|
|
20
20
|
export * from "./hotkeys/index.js";
|
|
21
21
|
export * from "./treeNavigation/index.js";
|
|
22
|
+
export * from "./platform.js";
|
|
23
|
+
export * from "./contentTypes.js";
|
|
24
|
+
export * from "./httpAttachments.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const testPlatform: (re: RegExp) => boolean;
|
package/dist/platform.js
ADDED
|
@@ -14,6 +14,3 @@ export declare const currentUrl: import("@preact/signals-core").ReadonlySignal<{
|
|
|
14
14
|
export declare const getParamValue: (key: string) => string | null;
|
|
15
15
|
export declare const getParamValues: (key: string) => string[];
|
|
16
16
|
export declare const hasParam: (key: string) => boolean;
|
|
17
|
-
export declare const getParamValueComputed: (key: string) => import("@preact/signals-core").ReadonlySignal<string | null>;
|
|
18
|
-
export declare const getParamValuesComputed: (key: string) => import("@preact/signals-core").ReadonlySignal<string[]>;
|
|
19
|
-
export declare const hasParamComputed: (key: string) => import("@preact/signals-core").ReadonlySignal<boolean>;
|
package/dist/stores/url/store.js
CHANGED
|
@@ -40,6 +40,3 @@ export const currentUrl = computed(() => {
|
|
|
40
40
|
export const getParamValue = (key) => urlSearchParams.value.get(key);
|
|
41
41
|
export const getParamValues = (key) => urlSearchParams.value.getAll(key);
|
|
42
42
|
export const hasParam = (key) => urlSearchParams.value.has(key);
|
|
43
|
-
export const getParamValueComputed = (key) => computed(() => getParamValue(key));
|
|
44
|
-
export const getParamValuesComputed = (key) => computed(() => getParamValues(key));
|
|
45
|
-
export const hasParamComputed = (key) => computed(() => hasParam(key));
|
|
@@ -7,19 +7,18 @@ export type SubtreeNodeState = {
|
|
|
7
7
|
};
|
|
8
8
|
export declare const isSubtreeFirstLevelOnlyOpened: (rootId: string, rootOpenedByDefault: boolean, subtreeNodes: SubtreeNodeState[], isNodeOpened: (id: string, openedByDefault: boolean) => boolean) => boolean;
|
|
9
9
|
export declare const isSubtreeExpandedAll: (subtreeNodes: SubtreeNodeState[], isNodeOpened: (id: string, openedByDefault: boolean) => boolean) => boolean;
|
|
10
|
-
export
|
|
10
|
+
export type SubtreeTogglePayload = {
|
|
11
11
|
hasOnlyLeafResults: boolean;
|
|
12
12
|
isSubtreeCollapsedAll: boolean;
|
|
13
13
|
isSubtreeFirstLevelOnly: boolean;
|
|
14
14
|
isSubtreeExpandedAll: boolean;
|
|
15
15
|
lastSubtreeToggle: SubtreeToggleState | null;
|
|
16
|
-
}
|
|
16
|
+
};
|
|
17
|
+
export declare const getNextSubtreeToggleState: (payload: SubtreeTogglePayload) => SubtreeToggleState;
|
|
18
|
+
export type SubtreeToggleAction = "expand-first-level" | "collapse-to-first-level" | "expand-all" | "collapse-all";
|
|
19
|
+
export declare const getSubtreeToggleAction: (payload: SubtreeTogglePayload) => SubtreeToggleAction;
|
|
17
20
|
export declare const resolveNextSubtreeToggleState: (subtreeNodes: SubtreeNodeState[], isNodeOpened: (id: string, openedByDefault: boolean) => boolean, lastSubtreeToggle: SubtreeToggleState | null) => {
|
|
18
21
|
nextState: SubtreeToggleState;
|
|
19
22
|
nextLastToggle: SubtreeToggleState | null;
|
|
20
23
|
};
|
|
21
|
-
export declare const getSubtreeToggleIcon: (payload:
|
|
22
|
-
hasOnlyLeafResults: boolean;
|
|
23
|
-
isSubtreeCollapsedAll: boolean;
|
|
24
|
-
isSubtreeFirstLevelOnly: boolean;
|
|
25
|
-
}) => SubtreeToggleIconState;
|
|
24
|
+
export declare const getSubtreeToggleIcon: (payload: SubtreeTogglePayload) => SubtreeToggleIconState;
|
|
@@ -21,6 +21,16 @@ export const getNextSubtreeToggleState = (payload) => {
|
|
|
21
21
|
}
|
|
22
22
|
return "all";
|
|
23
23
|
};
|
|
24
|
+
export const getSubtreeToggleAction = (payload) => {
|
|
25
|
+
const nextState = getNextSubtreeToggleState(payload);
|
|
26
|
+
if (nextState === "all") {
|
|
27
|
+
return "expand-all";
|
|
28
|
+
}
|
|
29
|
+
if (nextState === "none") {
|
|
30
|
+
return "collapse-all";
|
|
31
|
+
}
|
|
32
|
+
return payload.isSubtreeCollapsedAll ? "expand-first-level" : "collapse-to-first-level";
|
|
33
|
+
};
|
|
24
34
|
export const resolveNextSubtreeToggleState = (subtreeNodes, isNodeOpened, lastSubtreeToggle) => {
|
|
25
35
|
const root = subtreeNodes.find((node) => node.isRoot);
|
|
26
36
|
if (!root || subtreeNodes.length === 0) {
|
|
@@ -43,15 +53,14 @@ export const resolveNextSubtreeToggleState = (subtreeNodes, isNodeOpened, lastSu
|
|
|
43
53
|
};
|
|
44
54
|
};
|
|
45
55
|
export const getSubtreeToggleIcon = (payload) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
switch (getSubtreeToggleAction(payload)) {
|
|
57
|
+
case "expand-first-level":
|
|
58
|
+
return "single-down";
|
|
59
|
+
case "expand-all":
|
|
60
|
+
return payload.hasOnlyLeafResults ? "single-down" : "double-down";
|
|
61
|
+
case "collapse-to-first-level":
|
|
62
|
+
return "double-up";
|
|
63
|
+
case "collapse-all":
|
|
64
|
+
return "single-up";
|
|
55
65
|
}
|
|
56
|
-
return "double-up";
|
|
57
66
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allurereport/web-commons",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.22.0",
|
|
4
4
|
"description": "Collection of utilities used across the web Allure reports",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"allure",
|
|
@@ -21,28 +21,31 @@
|
|
|
21
21
|
"scripts": {
|
|
22
22
|
"build": "run clean && tsc --project ./tsconfig.json",
|
|
23
23
|
"clean": "rimraf ./dist",
|
|
24
|
-
"test": "
|
|
25
|
-
"
|
|
26
|
-
"lint
|
|
24
|
+
"test": "rimraf out && vp test run",
|
|
25
|
+
"test:unit": "yarn test",
|
|
26
|
+
"lint": "vp lint --import-plugin src test features stories",
|
|
27
|
+
"lint:fix": "vp lint --import-plugin --fix src test features stories"
|
|
27
28
|
},
|
|
28
29
|
"dependencies": {
|
|
29
|
-
"@allurereport/aql": "3.
|
|
30
|
-
"@allurereport/charts-api": "3.
|
|
31
|
-
"@allurereport/core-api": "3.
|
|
32
|
-
"@allurereport/plugin-api": "3.
|
|
30
|
+
"@allurereport/aql": "3.22.0",
|
|
31
|
+
"@allurereport/charts-api": "3.22.0",
|
|
32
|
+
"@allurereport/core-api": "3.22.0",
|
|
33
|
+
"@allurereport/plugin-api": "3.22.0",
|
|
33
34
|
"@preact/signals": "^2.6.1",
|
|
34
35
|
"@preact/signals-core": "^1.12.2",
|
|
35
36
|
"ansi-to-html": "^0.7.2",
|
|
36
37
|
"d3-interpolate": "^3.0.1",
|
|
37
38
|
"d3-scale": "^4.0.2",
|
|
38
39
|
"d3-shape": "^3.2.0",
|
|
39
|
-
"dompurify": "
|
|
40
|
+
"dompurify": "3.4.16",
|
|
40
41
|
"nanoid": "^5.1.16"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
44
|
+
"@allurereport/common-config": "3.22.0",
|
|
43
45
|
"@types/d3-interpolate": "^3.0.4",
|
|
44
46
|
"@types/d3-scale": "^4.0.9",
|
|
45
47
|
"@types/d3-shape": "^3.1.8",
|
|
48
|
+
"@vitest/coverage-istanbul": "5.0.4",
|
|
46
49
|
"@vitest/runner": "^2",
|
|
47
50
|
"allure-js-commons": "^3",
|
|
48
51
|
"allure-vitest": "^3",
|
|
@@ -50,6 +53,8 @@
|
|
|
50
53
|
"rimraf": "^6",
|
|
51
54
|
"tslib": "^2.7.0",
|
|
52
55
|
"typescript": "^5",
|
|
53
|
-
"
|
|
56
|
+
"vite": "@voidzero-dev/vite-plus-core@1.2.0",
|
|
57
|
+
"vite-plus": "1.2.0",
|
|
58
|
+
"vitest": "5.0.4"
|
|
54
59
|
}
|
|
55
60
|
}
|