@allurereport/web-commons 3.3.0 → 3.4.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.
@@ -29,6 +29,15 @@ export declare const blobAttachment: (id: string, ext: string, contentType: stri
29
29
  export declare const downloadAttachment: (id: string, ext: string, contentType: string) => Promise<void>;
30
30
  export declare const openAttachmentInNewTab: (id: string, ext: string, contentType: string) => Promise<void>;
31
31
  export type AttachmentType = "css" | "json" | "image" | "svg" | "code" | "text" | "html" | "table" | "video" | "uri" | "archive" | "image-diff";
32
+ export declare const PREVIEWABLE_CONTENT_TYPES: readonly ["text/html", "text/csv", "text/markdown", "text/tab-separated-values", "text/uri-list"];
33
+ export declare const isPreviewableContentType: (type?: string) => boolean;
34
+ export declare const extname: (str: string) => string | undefined;
35
+ export declare const isSyntaxHighlightSupported: (payload?: {
36
+ contentType?: string;
37
+ ext?: string;
38
+ name?: string;
39
+ originalFileName?: string;
40
+ }) => boolean;
32
41
  export declare const attachmentType: (type?: string) => AttachmentType | null;
33
42
  export declare const restrictedContentTypes: string[];
34
43
  export {};
@@ -65,6 +65,121 @@ export const openAttachmentInNewTab = async (id, ext, contentType) => {
65
65
  const linkUrl = URL.createObjectURL(blob);
66
66
  globalThis.open(linkUrl, "_blank");
67
67
  };
68
+ export const PREVIEWABLE_CONTENT_TYPES = [
69
+ "text/html",
70
+ "text/csv",
71
+ "text/markdown",
72
+ "text/tab-separated-values",
73
+ "text/uri-list",
74
+ ];
75
+ export const isPreviewableContentType = (type) => !!type && PREVIEWABLE_CONTENT_TYPES.includes(type);
76
+ const HIGHLIGHT_EXTS = new Set([
77
+ "js",
78
+ "mjs",
79
+ "cjs",
80
+ "jsx",
81
+ "ts",
82
+ "mts",
83
+ "cts",
84
+ "tsx",
85
+ "json",
86
+ "html",
87
+ "htm",
88
+ "xml",
89
+ "css",
90
+ "csv",
91
+ "tsv",
92
+ "md",
93
+ "markdown",
94
+ "yaml",
95
+ "yml",
96
+ "java",
97
+ "py",
98
+ "rb",
99
+ "go",
100
+ "php",
101
+ "sql",
102
+ "kt",
103
+ "swift",
104
+ "rs",
105
+ "c",
106
+ "cpp",
107
+ "cs",
108
+ "scala",
109
+ "dart",
110
+ "lua",
111
+ "haskell",
112
+ "r",
113
+ "perl",
114
+ ]);
115
+ const NO_HIGHLIGHT_TYPES = new Set(["text/plain", "text/*", "text/uri-list"]);
116
+ const HIGHLIGHT_TYPES = new Set([
117
+ "text/markdown",
118
+ "text/html",
119
+ "text/csv",
120
+ "text/tab-separated-values",
121
+ "text/xml",
122
+ "text/json",
123
+ "text/yaml",
124
+ "text/javascript",
125
+ "text/typescript",
126
+ "text/ruby",
127
+ "text/python",
128
+ "text/php",
129
+ "text/java",
130
+ "text/csharp",
131
+ "text/cpp",
132
+ "text/c",
133
+ "text/go",
134
+ "text/rust",
135
+ "text/swift",
136
+ "text/kotlin",
137
+ "text/scala",
138
+ "text/perl",
139
+ "text/r",
140
+ "text/dart",
141
+ "text/lua",
142
+ "text/haskell",
143
+ "text/sql",
144
+ "text/x-yaml",
145
+ "text/css",
146
+ "application/yaml",
147
+ "application/x-yaml",
148
+ "application/xml",
149
+ "application/json",
150
+ ]);
151
+ export const extname = (str) => {
152
+ const i = str.lastIndexOf(".");
153
+ if (i <= 0 || i === str.length - 1)
154
+ return undefined;
155
+ return str.slice(i);
156
+ };
157
+ export const isSyntaxHighlightSupported = (payload) => {
158
+ if (!payload) {
159
+ return false;
160
+ }
161
+ const contentType = payload.contentType?.toLowerCase();
162
+ let ext;
163
+ for (const part of [payload.ext, payload.name, payload.originalFileName]) {
164
+ if (!part) {
165
+ continue;
166
+ }
167
+ const withDot = extname(part);
168
+ const normalized = (withDot ?? part.replace(/^\./, "")).toLowerCase();
169
+ const single = normalized.includes(".") ? normalized.split(".").pop() : normalized;
170
+ if (single) {
171
+ ext = single;
172
+ break;
173
+ }
174
+ }
175
+ if (contentType && NO_HIGHLIGHT_TYPES.has(contentType)) {
176
+ return false;
177
+ }
178
+ if (contentType && HIGHLIGHT_TYPES.has(contentType)) {
179
+ return true;
180
+ }
181
+ return !!ext && HIGHLIGHT_EXTS.has(ext);
182
+ };
68
183
  export const attachmentType = (type) => {
69
184
  switch (type) {
70
185
  case "image/bmp":
@@ -1,4 +1,6 @@
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;
2
4
  export declare const generateStabilityDistributionChart: (props: {
3
5
  options: StabilityDistributionChartOptions;
4
6
  storeData: AllureChartsStoreData;
@@ -1,61 +1,153 @@
1
1
  import { ChartType } from "@allurereport/charts-api";
2
2
  import { createHashStorage, createMapWithDefault } from "./utils.js";
3
+ const DEFAULT_LIMIT = 10;
4
+ const DEFAULT_STABILIZATION_PERIOD = 5;
3
5
  const DEFAULT_THRESHOLD = 90;
4
6
  const DEFAULT_GROUP_BY = "feature";
5
7
  const CUSTOM_LABEL_NAME_PREFIX = "label-name:";
8
+ const SIGNIFICANT_STATUSES = new Set(["passed", "failed", "broken"]);
9
+ const isSignificantStatus = (status) => SIGNIFICANT_STATUSES.has(status);
6
10
  const getTrLabelValue = (testResult, labelName) => {
7
- return testResult.labels.find((label) => label.name === labelName)?.value;
8
- };
9
- const getStabilityRate = (passed, total) => {
10
- return Math.floor((passed / total) * 10000) / 100;
11
+ return testResult.labels?.find((label) => label.name === labelName)?.value;
11
12
  };
12
13
  const NON_SIGNIFICANT_STATUSES = ["unknown", "skipped"];
13
- class IncludeAllArray extends Array {
14
- constructor() {
15
- super();
14
+ const findLatestConsecutiveSameBlock = (statuses, sequenceLength, blockSize) => {
15
+ for (let start = sequenceLength - blockSize; start >= 0; start--) {
16
+ const first = statuses[start];
17
+ let allSame = true;
18
+ for (let k = 1; k < blockSize; k++) {
19
+ if (statuses[start + k] !== first) {
20
+ allSame = false;
21
+ break;
22
+ }
23
+ }
24
+ if (allSame) {
25
+ return { endIndex: start + blockSize - 1, status: first };
26
+ }
27
+ }
28
+ return null;
29
+ };
30
+ const computeTransitionsInRange = (statuses, firstEdge, lastEdge) => {
31
+ let transitionCount = 0;
32
+ let firstTransitionIdx = 0;
33
+ let secondTransitionIdx = 0;
34
+ let instabilitySumNumerator = 0;
35
+ for (let i = firstEdge; i <= lastEdge; i++) {
36
+ if (statuses[i - 1] === statuses[i]) {
37
+ continue;
38
+ }
39
+ transitionCount++;
40
+ if (transitionCount === 1) {
41
+ firstTransitionIdx = i;
42
+ }
43
+ else if (transitionCount === 2) {
44
+ secondTransitionIdx = i;
45
+ }
46
+ instabilitySumNumerator += i;
47
+ }
48
+ return { transitionCount, firstTransitionIdx, secondTransitionIdx, instabilitySumNumerator };
49
+ };
50
+ const isRecoveryTolerancePattern = (statuses, firstTransitionIdx, secondTransitionIdx) => {
51
+ const s0 = statuses[firstTransitionIdx - 1];
52
+ const s1 = statuses[firstTransitionIdx];
53
+ const s2 = statuses[secondTransitionIdx - 1];
54
+ const s3 = statuses[secondTransitionIdx];
55
+ return ((s0 === "passed" && s1 === "broken" && s2 === "broken" && s3 === "passed") ||
56
+ (s0 === "passed" && s1 === "failed" && s2 === "failed" && s3 === "passed"));
57
+ };
58
+ export const getStabilityScore = (statuses, historyDepth, stabilizationPeriod) => {
59
+ const effectiveSequenceLength = Math.min(statuses.length, historyDepth);
60
+ if (effectiveSequenceLength === 0) {
61
+ return undefined;
62
+ }
63
+ if (effectiveSequenceLength === 1) {
64
+ return 1;
65
+ }
66
+ const stabilBlock = findLatestConsecutiveSameBlock(statuses, effectiveSequenceLength, stabilizationPeriod);
67
+ if (stabilBlock !== null && stabilBlock.endIndex === effectiveSequenceLength - 1) {
68
+ return 1;
69
+ }
70
+ const firstEdgeAfterStabilBlock = stabilBlock !== null ? stabilBlock.endIndex + 1 : 1;
71
+ const lastEdge = effectiveSequenceLength - 1;
72
+ const denominator = ((effectiveSequenceLength - 1) * effectiveSequenceLength) / 2;
73
+ const { transitionCount, firstTransitionIdx, secondTransitionIdx, instabilitySumNumerator } = computeTransitionsInRange(statuses, firstEdgeAfterStabilBlock, lastEdge);
74
+ if (transitionCount === 1) {
75
+ return 1;
76
+ }
77
+ if (transitionCount === 2 && isRecoveryTolerancePattern(statuses, firstTransitionIdx, secondTransitionIdx)) {
78
+ return 1;
16
79
  }
17
- includes() {
18
- return true;
80
+ return instabilitySumNumerator / denominator;
81
+ };
82
+ const getStatusSequence = (historyDataPoints, tr) => {
83
+ let block = [];
84
+ for (const hdp of historyDataPoints) {
85
+ const htr = hdp.testResults[tr.historyId];
86
+ if (!htr) {
87
+ block = [];
88
+ continue;
89
+ }
90
+ if (isSignificantStatus(htr.status)) {
91
+ block.push(htr.status);
92
+ }
19
93
  }
20
- }
21
- const allValuesList = new IncludeAllArray();
94
+ if (isSignificantStatus(tr.status)) {
95
+ block.push(tr.status);
96
+ }
97
+ return block;
98
+ };
22
99
  export const generateStabilityDistributionChart = (props) => {
23
100
  const { options, storeData } = props;
24
- const { title, threshold = DEFAULT_THRESHOLD, skipStatuses = NON_SIGNIFICANT_STATUSES, groupBy = DEFAULT_GROUP_BY, groupValues = [], } = options;
25
- const { testResults } = storeData;
101
+ const { title, limit = DEFAULT_LIMIT, stabilizationPeriod = DEFAULT_STABILIZATION_PERIOD, threshold = DEFAULT_THRESHOLD, skipStatuses: skipStatusesList = NON_SIGNIFICANT_STATUSES, groupBy = DEFAULT_GROUP_BY, groupValues = [], } = options;
102
+ const { testResults, historyDataPoints } = storeData;
103
+ const effectiveStabilizationPeriod = Math.min(stabilizationPeriod, limit);
104
+ if (historyDataPoints.length < effectiveStabilizationPeriod) {
105
+ return {
106
+ data: [],
107
+ keys: {},
108
+ type: ChartType.StabilityDistribution,
109
+ title,
110
+ threshold,
111
+ };
112
+ }
113
+ const limitedHistoryDataPoints = historyDataPoints.slice(0, limit).sort((a, b) => a.timestamp - b.timestamp);
26
114
  const labelName = groupBy.startsWith(CUSTOM_LABEL_NAME_PREFIX)
27
115
  ? groupBy.slice(CUSTOM_LABEL_NAME_PREFIX.length)
28
116
  : groupBy;
29
- const labelValuesList = groupValues.length > 0 ? groupValues : allValuesList;
30
- const trsStatsByLabelValues = createMapWithDefault({
31
- passed: 0,
32
- total: 0,
33
- });
117
+ const groupValuesSet = new Set(groupValues ?? []);
118
+ const stabilityScoresByGroup = createMapWithDefault([]);
34
119
  const keys = {};
35
120
  const hashes = createHashStorage();
121
+ const skipStatuses = new Set(skipStatusesList);
36
122
  for (const tr of testResults) {
37
- const labelValue = getTrLabelValue(tr, labelName);
38
- if (!labelValue) {
123
+ if (!tr.historyId) {
39
124
  continue;
40
125
  }
41
- if (!labelValuesList.includes(labelValue)) {
126
+ if (!isSignificantStatus(tr.status)) {
42
127
  continue;
43
128
  }
44
- const labelValueHash = hashes.get(labelValue);
45
- keys[labelValueHash] = labelValue;
46
- if (skipStatuses.includes(tr.status)) {
129
+ if (skipStatuses.has(tr.status)) {
130
+ continue;
131
+ }
132
+ const labelValue = getTrLabelValue(tr, labelName);
133
+ if (!labelValue || (groupValuesSet.size > 0 && !groupValuesSet.has(labelValue))) {
47
134
  continue;
48
135
  }
49
- trsStatsByLabelValues.get(labelValueHash).total++;
50
- if (tr.status === "passed") {
51
- trsStatsByLabelValues.get(labelValueHash).passed++;
136
+ const statusSequence = getStatusSequence(limitedHistoryDataPoints, tr);
137
+ const stabilityScore = getStabilityScore(statusSequence, limit, effectiveStabilizationPeriod);
138
+ if (stabilityScore === undefined) {
139
+ continue;
52
140
  }
141
+ const labelValueHash = hashes.get(labelValue);
142
+ keys[labelValueHash] = labelValue;
143
+ stabilityScoresByGroup.get(labelValueHash).push(stabilityScore);
53
144
  }
54
145
  return {
55
- data: trsStatsByLabelValues.entries.map(([id, { passed, total }]) => ({
56
- id,
57
- stabilityRate: total > 0 ? getStabilityRate(passed, total) : 0,
58
- })),
146
+ data: stabilityScoresByGroup.entries.map(([id, scores]) => {
147
+ const avg = scores.length > 0 ? scores.reduce((a, b) => a + b, 0) / scores.length : 0;
148
+ const stabilityRate = Math.floor(avg * 10000) / 100;
149
+ return { id, stabilityRate };
150
+ }),
59
151
  keys,
60
152
  type: ChartType.StabilityDistribution,
61
153
  title,
@@ -28,13 +28,14 @@ export const generateStatusAgePyramid = (props) => {
28
28
  statuses: STATUSES,
29
29
  };
30
30
  }
31
+ const currTrIds = new Set(testResults.map((tr) => tr.historyId ?? tr.id));
31
32
  const hdps = limitedHistoryPoints.map((datapoint) => ({
32
33
  ...datapoint,
33
34
  testResults: Object.values(datapoint.testResults).reduce((acc, testResult) => {
34
35
  if (!testResult.historyId) {
35
36
  return acc;
36
37
  }
37
- const isInCurrentRun = testResults.findIndex((tr) => tr.historyId === testResult.historyId) !== -1;
38
+ const isInCurrentRun = currTrIds.has(testResult.historyId);
38
39
  if (isInCurrentRun) {
39
40
  acc[testResult.historyId] = testResult;
40
41
  }
@@ -1,5 +1,5 @@
1
1
  import { type ChartOptions, type GeneratedChartsData } from "@allurereport/charts-api";
2
- import type { TestResult } from "@allurereport/core-api";
2
+ import { type TestResult } from "@allurereport/core-api";
3
3
  import { type AllureStore } from "@allurereport/plugin-api";
4
4
  type ChartsWidgetData = {
5
5
  general: GeneratedChartsData;
@@ -1,5 +1,5 @@
1
1
  import { ChartType, } from "@allurereport/charts-api";
2
- import { DEFAULT_ENVIRONMENT } from "@allurereport/core-api";
2
+ import { DEFAULT_ENVIRONMENT, emptyStatistic, getFallbackHistoryId, incrementStatistic, } from "@allurereport/core-api";
3
3
  import { generateCurrentStatusChart } from "./generateCurrentStatusChart.js";
4
4
  import { generateDurationDynamicsChart } from "./generateDurationDynamicsChart.js";
5
5
  import { generateDurationsChart } from "./generateDurationsChart.js";
@@ -12,13 +12,67 @@ import { generateTestingPyramidChart } from "./generateTestingPyramidChart.js";
12
12
  import { generateTrSeveritiesChart } from "./generateTrSeveritiesChart.js";
13
13
  import { generateHeatMapChart } from "./heatMap.js";
14
14
  import { generateTreeMapChart } from "./treeMap.js";
15
+ const buildFallbackHistoryAliases = (testResults) => {
16
+ const aliases = new Map();
17
+ const ambiguousAliases = new Set();
18
+ for (const testResult of testResults) {
19
+ if (!testResult.historyId) {
20
+ continue;
21
+ }
22
+ const fallbackHistoryId = getFallbackHistoryId(testResult);
23
+ if (!fallbackHistoryId || fallbackHistoryId === testResult.historyId) {
24
+ continue;
25
+ }
26
+ const existingAlias = aliases.get(fallbackHistoryId);
27
+ if (existingAlias && existingAlias !== testResult.historyId) {
28
+ aliases.delete(fallbackHistoryId);
29
+ ambiguousAliases.add(fallbackHistoryId);
30
+ continue;
31
+ }
32
+ if (!ambiguousAliases.has(fallbackHistoryId)) {
33
+ aliases.set(fallbackHistoryId, testResult.historyId);
34
+ }
35
+ }
36
+ return aliases;
37
+ };
38
+ const normalizeHistoryDataPointsByAliases = (historyDataPoints, aliases) => {
39
+ if (aliases.size === 0) {
40
+ return historyDataPoints;
41
+ }
42
+ return historyDataPoints.map((historyDataPoint) => {
43
+ let testResults = historyDataPoint.testResults;
44
+ for (const [legacyHistoryId, primaryHistoryId] of aliases) {
45
+ if (!Object.prototype.hasOwnProperty.call(testResults, legacyHistoryId)) {
46
+ continue;
47
+ }
48
+ if (Object.prototype.hasOwnProperty.call(testResults, primaryHistoryId)) {
49
+ continue;
50
+ }
51
+ if (testResults === historyDataPoint.testResults) {
52
+ testResults = { ...testResults };
53
+ }
54
+ testResults[primaryHistoryId] = {
55
+ ...testResults[legacyHistoryId],
56
+ historyId: primaryHistoryId,
57
+ };
58
+ delete testResults[legacyHistoryId];
59
+ }
60
+ if (testResults === historyDataPoint.testResults) {
61
+ return historyDataPoint;
62
+ }
63
+ return {
64
+ ...historyDataPoint,
65
+ testResults,
66
+ };
67
+ });
68
+ };
15
69
  const generateChartData = async (props) => {
16
- const { env, chartsOptions, store, generateUuid, filter } = props;
70
+ const { envId, chartsOptions, store, generateUuid, filter } = props;
17
71
  const result = {};
18
72
  const getTrs = async () => {
19
73
  let trs = [];
20
- if (env) {
21
- trs = await store.testResultsByEnvironment(env);
74
+ if (envId) {
75
+ trs = await store.testResultsByEnvironmentId(envId);
22
76
  }
23
77
  else {
24
78
  trs = await store.allTestResults();
@@ -30,10 +84,12 @@ const generateChartData = async (props) => {
30
84
  };
31
85
  const getHistoryDataPoints = async () => {
32
86
  let historyDataPoints = [];
33
- if (env) {
34
- historyDataPoints = await store.allHistoryDataPointsByEnvironment(env);
87
+ if (envId) {
88
+ historyDataPoints = await store.allHistoryDataPointsByEnvironmentId(envId);
89
+ }
90
+ else {
91
+ historyDataPoints = await store.allHistoryDataPoints();
35
92
  }
36
- historyDataPoints = await store.allHistoryDataPoints();
37
93
  if (typeof filter === "function") {
38
94
  historyDataPoints = historyDataPoints.map((hdp) => {
39
95
  const trsEntries = Object.entries(hdp.testResults);
@@ -53,26 +109,21 @@ const generateChartData = async (props) => {
53
109
  }
54
110
  return historyDataPoints;
55
111
  };
56
- const getStatistic = () => {
57
- return store.testsStatistic((tr) => {
58
- if (env && tr.environment !== env) {
59
- return false;
60
- }
61
- if (typeof filter === "function" && !filter(tr)) {
62
- return false;
63
- }
64
- return true;
65
- });
112
+ const getStatistic = async () => {
113
+ const statistic = emptyStatistic();
114
+ for (const tr of await getTrs()) {
115
+ incrementStatistic(statistic, tr.status);
116
+ }
117
+ return statistic;
66
118
  };
67
- const storeData = await Promise.all([
68
- await getHistoryDataPoints(),
69
- await getTrs(),
70
- await getStatistic(),
71
- ]).then(([historyDataPoints, testResults, statistic]) => ({
72
- historyDataPoints,
73
- testResults,
74
- statistic,
75
- }));
119
+ const storeData = await Promise.all([getHistoryDataPoints(), getTrs(), getStatistic()]).then(([historyDataPoints, testResults, statistic]) => {
120
+ const fallbackHistoryAliases = buildFallbackHistoryAliases(testResults);
121
+ return {
122
+ historyDataPoints: normalizeHistoryDataPointsByAliases(historyDataPoints, fallbackHistoryAliases),
123
+ testResults,
124
+ statistic,
125
+ };
126
+ });
76
127
  for (const chartOption of chartsOptions) {
77
128
  const chartId = generateUuid();
78
129
  switch (chartOption.type) {
@@ -122,10 +173,10 @@ const generateChartData = async (props) => {
122
173
  return result;
123
174
  };
124
175
  const hasOnlyDefaultEnvironment = (environments) => {
125
- return environments.length === 1 && environments[0] === DEFAULT_ENVIRONMENT;
176
+ return environments.length === 1 && environments[0].id === DEFAULT_ENVIRONMENT;
126
177
  };
127
178
  export const generateCharts = async (chartsOptions, store, reportName, generateUuid, filter) => {
128
- const environments = await store.allEnvironments();
179
+ const environments = await store.allEnvironmentIdentities();
129
180
  const chartsData = {
130
181
  general: await generateChartData({ chartsOptions, store, reportName, generateUuid, filter }),
131
182
  byEnv: {},
@@ -134,11 +185,11 @@ export const generateCharts = async (chartsOptions, store, reportName, generateU
134
185
  return chartsData;
135
186
  }
136
187
  for (const environment of environments) {
137
- chartsData.byEnv[environment] = await generateChartData({
188
+ chartsData.byEnv[environment.id] = await generateChartData({
138
189
  chartsOptions,
139
190
  store,
140
191
  reportName,
141
- env: environment,
192
+ envId: environment.id,
142
193
  generateUuid,
143
194
  filter,
144
195
  });
package/dist/data.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { toPosixPath } from "@allurereport/core-api";
1
2
  export const ALLURE_LIVE_RELOAD_HASH_STORAGE_KEY = "__allure_report_live_reload_hash__";
2
3
  export const ensureReportDataReady = () => new Promise((resolve) => {
3
4
  const waitForReady = () => {
@@ -11,12 +12,19 @@ export const ensureReportDataReady = () => new Promise((resolve) => {
11
12
  export const loadReportData = async (name) => {
12
13
  await ensureReportDataReady();
13
14
  return new Promise((resolve, reject) => {
14
- if (globalThis.allureReportData[name]) {
15
- return resolve(globalThis.allureReportData[name]);
15
+ const dataByName = globalThis.allureReportData ?? {};
16
+ if (name in dataByName) {
17
+ return resolve(dataByName[name]);
16
18
  }
17
- else {
18
- return reject(new Error(`Data "${name}" not found!`));
19
+ const posixKey = toPosixPath(name);
20
+ if (posixKey in dataByName) {
21
+ return resolve(dataByName[posixKey]);
19
22
  }
23
+ const legacyBackslashKey = posixKey.replace(/\//g, "\\");
24
+ if (legacyBackslashKey in dataByName) {
25
+ return resolve(dataByName[legacyBackslashKey]);
26
+ }
27
+ return reject(new Error(`Data "${name}" not found!`));
20
28
  });
21
29
  };
22
30
  export const reportDataUrl = async (path, contentType = "application/octet-stream", params) => {
@@ -44,7 +52,13 @@ export class ReportFetchError extends Error {
44
52
  }
45
53
  }
46
54
  export const fetchReportJsonData = async (path, params) => {
47
- const url = await reportDataUrl(path, undefined, params);
55
+ let url;
56
+ try {
57
+ url = await reportDataUrl(path, undefined, params);
58
+ }
59
+ catch {
60
+ throw new ReportFetchError(`Failed to fetch ${path}: data not found`, new Response(null, { status: 404, statusText: "Not Found" }));
61
+ }
48
62
  const res = await globalThis.fetch(url);
49
63
  if (!res.ok) {
50
64
  throw new ReportFetchError(`Failed to fetch ${url}, response status: ${res.status}`, res);
@@ -0,0 +1,3 @@
1
+ import type { EnvironmentIdentity } from "@allurereport/core-api";
2
+ export declare const environmentNameById: (environments: EnvironmentIdentity[], environmentId: string) => string;
3
+ export declare const migrateStoredEnvironmentSelection: (storedEnvironment: string, environments: EnvironmentIdentity[]) => string;
@@ -0,0 +1,14 @@
1
+ export const environmentNameById = (environments, environmentId) => environments.find(({ id }) => id === environmentId)?.name ?? environmentId;
2
+ export const migrateStoredEnvironmentSelection = (storedEnvironment, environments) => {
3
+ if (!storedEnvironment) {
4
+ return "";
5
+ }
6
+ if (environments.some(({ id }) => id === storedEnvironment)) {
7
+ return storedEnvironment;
8
+ }
9
+ const matches = environments.filter(({ name }) => name === storedEnvironment);
10
+ if (matches.length === 1) {
11
+ return matches[0].id;
12
+ }
13
+ return "";
14
+ };
package/dist/i18n.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const AVAILABLE_LOCALES: readonly ["en", "en-iso", "ru", "uk", "pl", "es", "pt", "de", "hy", "az", "fr", "it", "ja", "he", "ka", "kr", "nl", "sv", "tr", "zh"];
1
+ export declare const AVAILABLE_LOCALES: readonly ["en", "en-iso", "ru", "uk", "pl", "es", "pt", "de", "hy", "ar", "az", "fr", "it", "ja", "he", "ka", "kr", "nl", "sv", "tr", "zh", "zh-TW"];
2
2
  export declare const DEFAULT_LOCALE = "en";
3
3
  export type LangLocale = (typeof AVAILABLE_LOCALES)[number];
4
4
  export type DateTimeFormatKind = "date" | "dateTime" | "dateTimeNoSeconds";
package/dist/i18n.js CHANGED
@@ -8,6 +8,7 @@ export const AVAILABLE_LOCALES = [
8
8
  "pt",
9
9
  "de",
10
10
  "hy",
11
+ "ar",
11
12
  "az",
12
13
  "fr",
13
14
  "it",
@@ -19,6 +20,7 @@ export const AVAILABLE_LOCALES = [
19
20
  "sv",
20
21
  "tr",
21
22
  "zh",
23
+ "zh-TW",
22
24
  ];
23
25
  export const DEFAULT_LOCALE = "en";
24
26
  export const LOCALE_DATE_TIME_OVERRIDES = {
@@ -110,6 +112,11 @@ export const LANG_LOCALE = {
110
112
  full: "Հայերեն",
111
113
  iso: "hy-AM",
112
114
  },
115
+ "ar": {
116
+ short: "Ar",
117
+ full: "العربية",
118
+ iso: "ar-SA",
119
+ },
113
120
  "az": {
114
121
  short: "Az",
115
122
  full: "Azərbaycan",
@@ -165,4 +172,9 @@ export const LANG_LOCALE = {
165
172
  full: "中文",
166
173
  iso: "zh-CN",
167
174
  },
175
+ "zh-TW": {
176
+ short: "Zh-TW",
177
+ full: "繁體中文",
178
+ iso: "zh-TW",
179
+ },
168
180
  };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./data.js";
2
2
  export * from "./attachments.js";
3
+ export * from "./environments.js";
3
4
  export * from "./i18n.js";
4
5
  export * from "./charts/index.js";
5
6
  export * from "./strings.js";
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./data.js";
2
2
  export * from "./attachments.js";
3
+ export * from "./environments.js";
3
4
  export * from "./i18n.js";
4
5
  export * from "./charts/index.js";
5
6
  export * from "./strings.js";
package/package.json CHANGED
@@ -1,60 +1,51 @@
1
1
  {
2
2
  "name": "@allurereport/web-commons",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "Collection of utilities used across the web Allure reports",
5
5
  "keywords": [
6
6
  "allure",
7
7
  "testing"
8
8
  ],
9
- "repository": "https://github.com/allure-framework/allure3",
10
9
  "license": "Apache-2.0",
11
10
  "author": "Qameta Software",
11
+ "repository": "https://github.com/allure-framework/allure3",
12
+ "files": [
13
+ "dist"
14
+ ],
12
15
  "type": "module",
16
+ "module": "dist/index.js",
17
+ "types": "dist/index.d.ts",
13
18
  "exports": {
14
19
  ".": "./dist/index.js"
15
20
  },
16
- "module": "dist/index.js",
17
- "types": "dist/index.d.ts",
18
- "files": [
19
- "dist"
20
- ],
21
21
  "scripts": {
22
22
  "build": "run clean && tsc --project ./tsconfig.json",
23
23
  "clean": "rimraf ./dist",
24
- "test": "vitest run"
24
+ "test": "vitest run",
25
+ "lint": "oxlint --import-plugin src test features stories",
26
+ "lint:fix": "oxlint --import-plugin --fix src test features stories"
25
27
  },
26
28
  "dependencies": {
27
- "@allurereport/aql": "3.3.0",
28
- "@allurereport/charts-api": "3.3.0",
29
- "@allurereport/core-api": "3.3.0",
30
- "@allurereport/plugin-api": "3.3.0",
29
+ "@allurereport/aql": "3.4.0",
30
+ "@allurereport/charts-api": "3.4.0",
31
+ "@allurereport/core-api": "3.4.0",
32
+ "@allurereport/plugin-api": "3.4.0",
31
33
  "@preact/signals": "^2.6.1",
32
34
  "@preact/signals-core": "^1.12.2",
33
35
  "ansi-to-html": "^0.7.2",
34
36
  "d3-interpolate": "^3.0.1",
35
37
  "d3-scale": "^4.0.2",
36
38
  "d3-shape": "^3.2.0",
37
- "dompurify": "^3.2.6",
39
+ "dompurify": "^3.3.2",
38
40
  "nanoid": "^5.1.6"
39
41
  },
40
42
  "devDependencies": {
41
- "@stylistic/eslint-plugin": "^2.6.1",
42
43
  "@types/d3-interpolate": "^3.0.4",
43
44
  "@types/d3-scale": "^4.0.9",
44
45
  "@types/d3-shape": "^3.1.6",
45
- "@types/eslint": "^8.56.11",
46
- "@typescript-eslint/eslint-plugin": "^8.0.0",
47
- "@typescript-eslint/parser": "^8.0.0",
48
46
  "@vitest/runner": "^2.1.9",
49
47
  "allure-js-commons": "^3.3.0",
50
48
  "allure-vitest": "^3.3.0",
51
- "eslint": "^8.57.0",
52
- "eslint-config-prettier": "^9.1.0",
53
- "eslint-plugin-import": "^2.29.1",
54
- "eslint-plugin-jsdoc": "^50.0.0",
55
- "eslint-plugin-n": "^17.10.1",
56
- "eslint-plugin-no-null": "^1.0.2",
57
- "eslint-plugin-prefer-arrow": "^1.2.3",
58
49
  "jsdom": "^26.1.0",
59
50
  "rimraf": "^6.0.1",
60
51
  "tslib": "^2.7.0",