@allurereport/web-commons 3.0.1 → 3.2.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.
Files changed (60) hide show
  1. package/dist/attachments.d.ts +27 -14
  2. package/dist/attachments.js +20 -45
  3. package/dist/charts/generateStatusAgePyramid.d.ts +5 -0
  4. package/dist/charts/{generateFBSUAgePyramid.js → generateStatusAgePyramid.js} +29 -17
  5. package/dist/charts/generators.d.ts +2 -1
  6. package/dist/charts/generators.js +59 -9
  7. package/dist/charts/types.d.ts +3 -3
  8. package/dist/charts/utils.js +1 -1
  9. package/dist/data.d.ts +5 -1
  10. package/dist/data.js +8 -2
  11. package/dist/filters/builders.d.ts +4 -0
  12. package/dist/filters/builders.js +160 -0
  13. package/dist/filters/index.d.ts +3 -0
  14. package/dist/filters/index.js +2 -0
  15. package/dist/filters/model.d.ts +39 -0
  16. package/dist/filters/model.js +1 -0
  17. package/dist/i18n.d.ts +10 -1
  18. package/dist/i18n.js +69 -20
  19. package/dist/index.d.ts +8 -0
  20. package/dist/index.js +8 -0
  21. package/dist/prose.d.ts +2 -0
  22. package/dist/prose.js +390 -0
  23. package/dist/sanitize.d.ts +1 -0
  24. package/dist/sanitize.js +4 -0
  25. package/dist/stores/loadableStore/constants.d.ts +1 -0
  26. package/dist/stores/loadableStore/constants.js +1 -0
  27. package/dist/stores/loadableStore/index.d.ts +3 -0
  28. package/dist/stores/loadableStore/index.js +2 -0
  29. package/dist/stores/loadableStore/store.d.ts +13 -0
  30. package/dist/stores/loadableStore/store.js +47 -0
  31. package/dist/stores/loadableStore/types.d.ts +7 -0
  32. package/dist/stores/loadableStore/types.js +1 -0
  33. package/dist/stores/loadableStore/utils.d.ts +2 -0
  34. package/dist/stores/loadableStore/utils.js +7 -0
  35. package/dist/stores/persister/index.d.ts +12 -0
  36. package/dist/stores/persister/index.js +36 -0
  37. package/dist/stores/router/index.d.ts +18 -0
  38. package/dist/stores/router/index.js +95 -0
  39. package/dist/stores/theme/actions.d.ts +3 -0
  40. package/dist/stores/theme/actions.js +30 -0
  41. package/dist/stores/theme/constants.d.ts +6 -0
  42. package/dist/stores/theme/constants.js +5 -0
  43. package/dist/stores/theme/index.d.ts +2 -0
  44. package/dist/stores/theme/index.js +2 -0
  45. package/dist/stores/theme/store.d.ts +8 -0
  46. package/dist/stores/theme/store.js +41 -0
  47. package/dist/stores/theme/types.d.ts +2 -0
  48. package/dist/stores/theme/types.js +1 -0
  49. package/dist/stores/theme/utils.d.ts +4 -0
  50. package/dist/stores/theme/utils.js +23 -0
  51. package/dist/stores/url/helpers.d.ts +12 -0
  52. package/dist/stores/url/helpers.js +74 -0
  53. package/dist/stores/url/index.d.ts +2 -0
  54. package/dist/stores/url/index.js +2 -0
  55. package/dist/stores/url/store.d.ts +19 -0
  56. package/dist/stores/url/store.js +45 -0
  57. package/dist/utils.d.ts +2 -0
  58. package/dist/utils.js +9 -0
  59. package/package.json +10 -5
  60. package/dist/charts/generateFBSUAgePyramid.d.ts +0 -5
@@ -1,21 +1,34 @@
1
- export interface Attachments {
1
+ type AttachmentImageData = {
2
+ img: string;
3
+ id?: string;
4
+ };
5
+ type AttachmentTextData = {
6
+ text: string;
7
+ };
8
+ type AttachmentVideoData = {
9
+ src: string;
10
+ id?: string;
11
+ contentType?: string;
12
+ };
13
+ type AttachmentImageDiffData = {
14
+ diff: {
15
+ actual?: string;
16
+ expected?: string;
17
+ diff?: string;
18
+ };
19
+ };
20
+ export type AttachmentData = AttachmentImageData | AttachmentTextData | AttachmentVideoData | AttachmentImageDiffData;
21
+ type AttachmentPayload = {
2
22
  id?: string;
3
23
  ext?: string;
4
24
  contentType?: string;
5
- text?: string;
6
- src?: string;
7
- img?: string;
8
- }
9
- export declare const fetchFromUrl: ({ id, ext, contentType }: Attachments) => Promise<Response>;
10
- export declare const fetchAttachment: (id: string, ext: string, contentType?: string) => Promise<Attachments | null>;
25
+ };
26
+ export declare const fetchFromUrl: ({ id, ext, contentType }: AttachmentPayload) => Promise<Response>;
27
+ export declare const fetchAttachment: (id: string, ext: string, contentType?: string) => Promise<AttachmentData | null>;
11
28
  export declare const blobAttachment: (id: string, ext: string, contentType: string) => Promise<Blob>;
12
29
  export declare const downloadAttachment: (id: string, ext: string, contentType: string) => Promise<void>;
13
30
  export declare const openAttachmentInNewTab: (id: string, ext: string, contentType: string) => Promise<void>;
14
- export declare const attachmentType: (type?: string) => {
15
- type: string;
16
- icon: string;
17
- } | {
18
- type: null;
19
- icon: string;
20
- };
31
+ export type AttachmentType = "css" | "json" | "image" | "svg" | "code" | "text" | "html" | "table" | "video" | "uri" | "archive" | "image-diff";
32
+ export declare const attachmentType: (type?: string) => AttachmentType | null;
21
33
  export declare const restrictedContentTypes: string[];
34
+ export {};
@@ -9,7 +9,10 @@ export const fetchAttachment = async (id, ext, contentType) => {
9
9
  }
10
10
  const response = await fetchFromUrl({ id, ext, contentType });
11
11
  const fileType = attachmentType(contentType);
12
- switch (fileType.type) {
12
+ if (!response.ok) {
13
+ throw new Error("Failed to fetch");
14
+ }
15
+ switch (fileType) {
13
16
  case "svg":
14
17
  case "image": {
15
18
  const blob = await response.blob();
@@ -29,6 +32,10 @@ export const fetchAttachment = async (id, ext, contentType) => {
29
32
  const src = URL.createObjectURL(blob);
30
33
  return { src, id, contentType };
31
34
  }
35
+ case "image-diff": {
36
+ const json = await response.json();
37
+ return { diff: json };
38
+ }
32
39
  default:
33
40
  return null;
34
41
  }
@@ -67,10 +74,7 @@ export const attachmentType = (type) => {
67
74
  case "image/jpg":
68
75
  case "image/png":
69
76
  case "image/*":
70
- return {
71
- type: "image",
72
- icon: "file",
73
- };
77
+ return "image";
74
78
  case "text/xml":
75
79
  case "text/json":
76
80
  case "text/yaml":
@@ -100,63 +104,34 @@ export const attachmentType = (type) => {
100
104
  case "application/x-yaml":
101
105
  case "application/xml":
102
106
  case "application/json":
103
- return {
104
- type: "code",
105
- icon: "file",
106
- };
107
+ return "code";
107
108
  case "text/plain":
108
109
  case "text/markdown":
109
110
  case "text/*":
110
- return {
111
- type: "text",
112
- icon: "txt",
113
- };
111
+ return "text";
114
112
  case "text/html":
115
- return {
116
- type: "html",
117
- icon: "file",
118
- };
113
+ return "html";
119
114
  case "text/csv":
120
- return {
121
- type: "table",
122
- icon: "csv",
123
- };
124
115
  case "text/tab-separated-values":
125
- return {
126
- type: "table",
127
- icon: "table",
128
- };
116
+ return "table";
129
117
  case "image/svg+xml":
130
- return {
131
- type: "svg",
132
- icon: "file",
133
- };
118
+ return "svg";
134
119
  case "video/mp4":
135
120
  case "video/ogg":
136
121
  case "video/webm":
137
- return {
138
- type: "video",
139
- icon: "file",
140
- };
122
+ return "video";
141
123
  case "text/uri-list":
142
- return {
143
- type: "uri",
144
- icon: "list",
145
- };
124
+ return "uri";
146
125
  case "application/x-tar":
147
126
  case "application/x-gtar":
148
127
  case "application/x-bzip2":
149
128
  case "application/gzip":
150
129
  case "application/zip":
151
- return {
152
- type: "archive",
153
- icon: "file",
154
- };
130
+ return "archive";
131
+ case "application/vnd.allure.image.diff":
132
+ return "image-diff";
155
133
  default:
156
- return {
157
- type: null,
158
- icon: "file",
159
- };
134
+ return null;
160
135
  }
161
136
  };
162
137
  export const restrictedContentTypes = ["application/gzip"];
@@ -0,0 +1,5 @@
1
+ import type { AllureChartsStoreData, StatusAgePyramidChartData, StatusAgePyramidChartOptions } from "@allurereport/charts-api";
2
+ export declare const generateStatusAgePyramid: (props: {
3
+ options: StatusAgePyramidChartOptions;
4
+ storeData: AllureChartsStoreData;
5
+ }) => StatusAgePyramidChartData;
@@ -1,15 +1,14 @@
1
1
  import { ChartType, DEFAULT_CHART_HISTORY_LIMIT } from "@allurereport/charts-api";
2
- import { htrsByTr } from "@allurereport/core-api";
3
2
  import { limitHistoryDataPoints } from "./chart-utils.js";
4
- const createEmptyStats = (statuses) => {
5
- return statuses.reduce((acc, status) => {
3
+ const createEmptyStats = () => {
4
+ return STATUSES.reduce((acc, status) => {
6
5
  acc[status] = 0;
7
6
  return acc;
8
7
  }, {});
9
8
  };
10
9
  const STATUSES = ["failed", "broken", "skipped", "unknown"];
11
10
  const isFBSUStatus = (status) => STATUSES.includes(status);
12
- export const generateFBSUAgePyramid = (props) => {
11
+ export const generateStatusAgePyramid = (props) => {
13
12
  const { options, storeData } = props;
14
13
  const { limit = DEFAULT_CHART_HISTORY_LIMIT } = options;
15
14
  const { historyDataPoints, testResults } = storeData;
@@ -17,23 +16,35 @@ export const generateFBSUAgePyramid = (props) => {
17
16
  const limitedHistoryPoints = limitHistoryDataPoints(historyDataPoints, limit).sort((a, b) => a.timestamp - b.timestamp);
18
17
  if (limitedHistoryPoints.length === 0) {
19
18
  return {
20
- type: ChartType.FBSUAgePyramid,
19
+ type: ChartType.StatusAgePyramid,
21
20
  title: options.title,
22
21
  data: [
23
22
  {
24
23
  id: "current",
25
24
  timestamp: currentReportTimestamp,
26
- ...createEmptyStats(STATUSES),
25
+ ...createEmptyStats(),
27
26
  },
28
27
  ],
29
28
  statuses: STATUSES,
30
29
  };
31
30
  }
32
- const [earliestHdp, ...hdps] = limitedHistoryPoints;
31
+ const hdps = limitedHistoryPoints.map((datapoint) => ({
32
+ ...datapoint,
33
+ testResults: Object.values(datapoint.testResults).reduce((acc, testResult) => {
34
+ if (!testResult.historyId) {
35
+ return acc;
36
+ }
37
+ const isInCurrentRun = testResults.findIndex((tr) => tr.historyId === testResult.historyId) !== -1;
38
+ if (isInCurrentRun) {
39
+ acc[testResult.historyId] = testResult;
40
+ }
41
+ return acc;
42
+ }, {}),
43
+ }));
33
44
  const dataPoints = [
34
45
  ...hdps.map((hdp) => ({
35
46
  ...hdp,
36
- ...createEmptyStats(STATUSES),
47
+ ...createEmptyStats(),
37
48
  })),
38
49
  {
39
50
  testResults: testResults.reduce((acc, testResult) => {
@@ -42,22 +53,23 @@ export const generateFBSUAgePyramid = (props) => {
42
53
  }, {}),
43
54
  uuid: "current",
44
55
  timestamp: currentReportTimestamp,
45
- ...createEmptyStats(STATUSES),
56
+ ...createEmptyStats(),
46
57
  },
47
58
  ];
48
- dataPoints.forEach((dp, index, dataPointsAscending) => {
59
+ dataPoints.forEach((dp, index, dps) => {
49
60
  const { testResults: trs } = dp;
50
- const isFirst = index === 0;
51
- const hpsPriorToCurrent = isFirst ? [earliestHdp] : dataPointsAscending.slice(0, index);
61
+ const historyAfter = dps.slice(index, dps.length - 1);
52
62
  const currentTrs = Object.values(trs);
53
63
  for (const cTr of currentTrs) {
54
- if (!isFBSUStatus(cTr.status)) {
64
+ const currentTrStatus = cTr.status;
65
+ if (!isFBSUStatus(currentTrStatus)) {
55
66
  continue;
56
67
  }
57
- const htrsPriortoCurr = htrsByTr(hpsPriorToCurrent, cTr);
58
- if (htrsPriortoCurr.length === 0) {
59
- dp[cTr.status]++;
68
+ const historyAfterTrsStatuses = historyAfter.map((hdp) => hdp.testResults[cTr.historyId]?.status ?? undefined);
69
+ if (historyAfterTrsStatuses.some((status) => status !== currentTrStatus)) {
70
+ continue;
60
71
  }
72
+ dp[currentTrStatus]++;
61
73
  }
62
74
  });
63
75
  const data = dataPoints.map(({ uuid, timestamp, ...stats }) => ({
@@ -69,7 +81,7 @@ export const generateFBSUAgePyramid = (props) => {
69
81
  unknown: stats.unknown ?? 0,
70
82
  }));
71
83
  return {
72
- type: ChartType.FBSUAgePyramid,
84
+ type: ChartType.StatusAgePyramid,
73
85
  title: options.title,
74
86
  data: data,
75
87
  statuses: STATUSES,
@@ -1,4 +1,5 @@
1
1
  import { type ChartOptions, type GeneratedChartsData } from "@allurereport/charts-api";
2
+ import type { TestResult } from "@allurereport/core-api";
2
3
  import { type AllureStore } from "@allurereport/plugin-api";
3
4
  type ChartsWidgetData = {
4
5
  general: GeneratedChartsData;
@@ -6,5 +7,5 @@ type ChartsWidgetData = {
6
7
  [env: string]: GeneratedChartsData;
7
8
  };
8
9
  };
9
- export declare const generateCharts: (chartsOptions: ChartOptions[], store: AllureStore, reportName: string, generateUuid: () => string) => Promise<ChartsWidgetData>;
10
+ export declare const generateCharts: (chartsOptions: ChartOptions[], store: AllureStore, reportName: string, generateUuid: () => string, filter?: (testResult: TestResult) => boolean) => Promise<ChartsWidgetData>;
10
11
  export {};
@@ -3,8 +3,8 @@ import { DEFAULT_ENVIRONMENT } 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";
6
- import { generateFBSUAgePyramid } from "./generateFBSUAgePyramid.js";
7
6
  import { generateStabilityDistributionChart } from "./generateStabilityDistributionChart.js";
7
+ import { generateStatusAgePyramid } from "./generateStatusAgePyramid.js";
8
8
  import { generateStatusDynamicsChart } from "./generateStatusDynamicsChart.js";
9
9
  import { generateStatusTransitionsChart } from "./generateStatusTransitionsChart.js";
10
10
  import { generateTestBaseGrowthDynamicsChart } from "./generateTestBaseGrowthDynamicsChart.js";
@@ -13,12 +13,61 @@ import { generateTrSeveritiesChart } from "./generateTrSeveritiesChart.js";
13
13
  import { generateHeatMapChart } from "./heatMap.js";
14
14
  import { generateTreeMapChart } from "./treeMap.js";
15
15
  const generateChartData = async (props) => {
16
- const { env, chartsOptions, store, reportName, generateUuid } = props;
16
+ const { env, chartsOptions, store, generateUuid, filter } = props;
17
17
  const result = {};
18
+ const getTrs = async () => {
19
+ let trs = [];
20
+ if (env) {
21
+ trs = await store.testResultsByEnvironment(env);
22
+ }
23
+ else {
24
+ trs = await store.allTestResults();
25
+ }
26
+ if (filter) {
27
+ trs = trs.filter(filter);
28
+ }
29
+ return trs;
30
+ };
31
+ const getHistoryDataPoints = async () => {
32
+ let historyDataPoints = [];
33
+ if (env) {
34
+ historyDataPoints = await store.allHistoryDataPointsByEnvironment(env);
35
+ }
36
+ historyDataPoints = await store.allHistoryDataPoints();
37
+ if (typeof filter === "function") {
38
+ historyDataPoints = historyDataPoints.map((hdp) => {
39
+ const trsEntries = Object.entries(hdp.testResults);
40
+ const filteredTrsEntries = trsEntries.filter(([, tr]) => {
41
+ try {
42
+ return filter(tr);
43
+ }
44
+ catch (error) {
45
+ return false;
46
+ }
47
+ });
48
+ return {
49
+ ...hdp,
50
+ testResults: Object.fromEntries(filteredTrsEntries),
51
+ };
52
+ });
53
+ }
54
+ return historyDataPoints;
55
+ };
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
+ });
66
+ };
18
67
  const storeData = await Promise.all([
19
- env ? await store.allHistoryDataPointsByEnvironment(env) : await store.allHistoryDataPoints(),
20
- env ? await store.testResultsByEnvironment(env) : await store.allTestResults(),
21
- env ? await store.testsStatistic((tr) => tr.environment === env) : await store.testsStatistic(),
68
+ await getHistoryDataPoints(),
69
+ await getTrs(),
70
+ await getStatistic(),
22
71
  ]).then(([historyDataPoints, testResults, statistic]) => ({
23
72
  historyDataPoints,
24
73
  testResults,
@@ -48,8 +97,8 @@ const generateChartData = async (props) => {
48
97
  case ChartType.TestBaseGrowthDynamics:
49
98
  result[chartId] = generateTestBaseGrowthDynamicsChart({ options: chartOption, storeData });
50
99
  break;
51
- case ChartType.FBSUAgePyramid:
52
- result[chartId] = generateFBSUAgePyramid({ options: chartOption, storeData });
100
+ case ChartType.StatusAgePyramid:
101
+ result[chartId] = generateStatusAgePyramid({ options: chartOption, storeData });
53
102
  break;
54
103
  case ChartType.TrSeverities:
55
104
  result[chartId] = generateTrSeveritiesChart({ options: chartOption, storeData });
@@ -75,10 +124,10 @@ const generateChartData = async (props) => {
75
124
  const hasOnlyDefaultEnvironment = (environments) => {
76
125
  return environments.length === 1 && environments[0] === DEFAULT_ENVIRONMENT;
77
126
  };
78
- export const generateCharts = async (chartsOptions, store, reportName, generateUuid) => {
127
+ export const generateCharts = async (chartsOptions, store, reportName, generateUuid, filter) => {
79
128
  const environments = await store.allEnvironments();
80
129
  const chartsData = {
81
- general: await generateChartData({ chartsOptions, store, reportName, generateUuid }),
130
+ general: await generateChartData({ chartsOptions, store, reportName, generateUuid, filter }),
82
131
  byEnv: {},
83
132
  };
84
133
  if (hasOnlyDefaultEnvironment(environments)) {
@@ -91,6 +140,7 @@ export const generateCharts = async (chartsOptions, store, reportName, generateU
91
140
  reportName,
92
141
  env: environment,
93
142
  generateUuid,
143
+ filter,
94
144
  });
95
145
  }
96
146
  return chartsData;
@@ -1,4 +1,4 @@
1
- import type { ChartId, ChartType, DurationDynamicsChartData, DurationsChartData, FBSUAgePyramidChartData, HeatMapSerie, StabilityDistributionChartData, StatusTransitionsChartData, TestBaseGrowthDynamicsChartData, TestingPyramidChartData, TrSeveritiesChartData, TreeMapNode } from "@allurereport/charts-api";
1
+ import type { ChartId, ChartType, DurationDynamicsChartData, DurationsChartData, HeatMapSerie, StabilityDistributionChartData, StatusAgePyramidChartData, StatusTransitionsChartData, TestBaseGrowthDynamicsChartData, TestingPyramidChartData, TrSeveritiesChartData, TreeMapNode } from "@allurereport/charts-api";
2
2
  import type { Statistic, TestStatus } from "@allurereport/core-api";
3
3
  export type TreeMapTooltipAccessor = <T>(node: T) => string[];
4
4
  export interface Point {
@@ -58,8 +58,8 @@ export interface UITreeMapChartData extends ResponseTreeMapChartData {
58
58
  export interface UIHeatMapChartData extends ResponseHeatMapChartData {
59
59
  colors: (value: number, domain?: number[]) => string;
60
60
  }
61
- export type ChartData = CurrentStatusChartData | StatusDynamicsChartData | StatusTransitionsChartData | DurationsChartData | StabilityDistributionChartData | ResponseTreeMapChartData | ResponseHeatMapChartData | TestBaseGrowthDynamicsChartData | FBSUAgePyramidChartData | TrSeveritiesChartData | DurationDynamicsChartData | TestingPyramidChartData;
62
- export type UIChartData = UICurrentStatusChartData | UIStatusDynamicsChartData | UITreeMapChartData | UIHeatMapChartData | UIStatusTransitionsChartData | UIDurationsChartData | TestBaseGrowthDynamicsChartData | FBSUAgePyramidChartData | StabilityDistributionChartData | TrSeveritiesChartData | DurationDynamicsChartData | TestingPyramidChartData;
61
+ export type ChartData = CurrentStatusChartData | StatusDynamicsChartData | StatusTransitionsChartData | DurationsChartData | StabilityDistributionChartData | ResponseTreeMapChartData | ResponseHeatMapChartData | TestBaseGrowthDynamicsChartData | StatusAgePyramidChartData | TrSeveritiesChartData | DurationDynamicsChartData | TestingPyramidChartData;
62
+ export type UIChartData = UICurrentStatusChartData | UIStatusDynamicsChartData | UITreeMapChartData | UIHeatMapChartData | UIStatusTransitionsChartData | UIDurationsChartData | TestBaseGrowthDynamicsChartData | StatusAgePyramidChartData | StabilityDistributionChartData | TrSeveritiesChartData | DurationDynamicsChartData | TestingPyramidChartData;
63
63
  export type ChartsData = Record<ChartId, ChartData>;
64
64
  export type ChartsDataWithEnvs = {
65
65
  general: Record<ChartId, ChartData>;
@@ -107,7 +107,7 @@ export const createCharts = (res) => {
107
107
  else if (chart.type === ChartType.TestBaseGrowthDynamics) {
108
108
  acc[chartId] = res[chartId];
109
109
  }
110
- else if (chart.type === ChartType.FBSUAgePyramid) {
110
+ else if (chart.type === ChartType.StatusAgePyramid) {
111
111
  acc[chartId] = res[chartId];
112
112
  }
113
113
  else if (chart.type === ChartType.TrSeverities) {
package/dist/data.d.ts CHANGED
@@ -4,8 +4,12 @@ export declare const loadReportData: (name: string) => Promise<string>;
4
4
  export declare const reportDataUrl: (path: string, contentType?: string, params?: {
5
5
  bustCache: boolean;
6
6
  }) => Promise<string>;
7
+ export declare class ReportFetchError extends Error {
8
+ readonly response: Response;
9
+ constructor(message: string, response: Response);
10
+ }
7
11
  export declare const fetchReportJsonData: <T>(path: string, params?: {
8
12
  bustCache: boolean;
9
13
  }) => Promise<T>;
10
14
  export declare const fetchReportAttachment: (path: string, contentType?: string) => Promise<Response>;
11
- export declare const getReportOptions: <T>() => T;
15
+ export declare const getReportOptions: <T>() => Readonly<T>;
package/dist/data.js CHANGED
@@ -28,7 +28,7 @@ export const reportDataUrl = async (path, contentType = "application/octet-strea
28
28
  const baseEl = globalThis.document.head.querySelector("base")?.href ?? "https://localhost";
29
29
  const url = new URL(path, baseEl);
30
30
  const liveReloadHash = globalThis.localStorage.getItem(ALLURE_LIVE_RELOAD_HASH_STORAGE_KEY);
31
- const cacheKey = globalThis.allureReportOptions?.cacheKey;
31
+ const cacheKey = getReportOptions()?.cacheKey;
32
32
  if (liveReloadHash) {
33
33
  url.searchParams.set("live_reload_hash", liveReloadHash);
34
34
  }
@@ -37,11 +37,17 @@ export const reportDataUrl = async (path, contentType = "application/octet-strea
37
37
  }
38
38
  return url.toString();
39
39
  };
40
+ export class ReportFetchError extends Error {
41
+ constructor(message, response) {
42
+ super(message);
43
+ this.response = response;
44
+ }
45
+ }
40
46
  export const fetchReportJsonData = async (path, params) => {
41
47
  const url = await reportDataUrl(path, undefined, params);
42
48
  const res = await globalThis.fetch(url);
43
49
  if (!res.ok) {
44
- throw new Error(`Failed to fetch ${url}, response status: ${res.status}`);
50
+ throw new ReportFetchError(`Failed to fetch ${url}, response status: ${res.status}`, res);
45
51
  }
46
52
  const data = res.json();
47
53
  return data;
@@ -0,0 +1,4 @@
1
+ import type { AqlExpression } from "@allurereport/aql";
2
+ import { type Filter } from "./model.js";
3
+ export declare const buildFieldFilters: <T extends string = string>(filters: Filter<T>[]) => AqlExpression;
4
+ export declare const buildFilterPredicate: <Keys extends string = string>(filters: Filter<Keys>[]) => (item: Record<Keys, any>) => boolean;
@@ -0,0 +1,160 @@
1
+ import { aqlArrayConditionExpression, aqlBinaryExpression, aqlConditionExpression, aqlParenExpression, createAqlPredicate, } from "@allurereport/aql";
2
+ import { MAX_ARRAY_FIELD_VALUES, } from "./model.js";
3
+ const buildAqlFromFieldFilter = (field) => {
4
+ const { value: fieldValue } = field;
5
+ const { key, type, strict, value } = fieldValue;
6
+ if (type === "array") {
7
+ if (value.length === 0) {
8
+ throw new Error("ArrayField value cannot be empty");
9
+ }
10
+ if (strict === false) {
11
+ return buildArrayIntersectionFilter(key, value, MAX_ARRAY_FIELD_VALUES);
12
+ }
13
+ return aqlArrayConditionExpression({
14
+ type: "arrayCondition",
15
+ left: {
16
+ identifier: key,
17
+ },
18
+ operator: "IN",
19
+ right: value.map((item) => ({
20
+ value: item,
21
+ type: "STRING",
22
+ })),
23
+ });
24
+ }
25
+ let expressionValue;
26
+ let valueType;
27
+ let operator = "EQ";
28
+ switch (type) {
29
+ case "number": {
30
+ expressionValue = String(value);
31
+ operator = (strict ?? true) ? "EQ" : "CONTAINS";
32
+ valueType = "NUMBER";
33
+ break;
34
+ }
35
+ case "boolean": {
36
+ expressionValue = value ? "true" : "false";
37
+ valueType = "BOOLEAN";
38
+ operator = "EQ";
39
+ break;
40
+ }
41
+ case "string": {
42
+ expressionValue = String(value);
43
+ operator = (strict ?? true) ? "EQ" : "CONTAINS";
44
+ valueType = "STRING";
45
+ break;
46
+ }
47
+ default: {
48
+ const exhaustiveCheck = type;
49
+ throw new Error(`Unsupported field type: ${String(exhaustiveCheck)}`);
50
+ }
51
+ }
52
+ return aqlConditionExpression({
53
+ type: "condition",
54
+ left: {
55
+ identifier: key,
56
+ },
57
+ operator,
58
+ right: {
59
+ value: expressionValue,
60
+ type: valueType,
61
+ },
62
+ });
63
+ };
64
+ export const buildFieldFilters = (filters) => {
65
+ if (filters.length === 0) {
66
+ throw new Error("chainFieldFilters: filters array cannot be empty");
67
+ }
68
+ const buildAqlFromFilterGroup = (group) => {
69
+ const { value } = group;
70
+ if (value.length === 0) {
71
+ throw new Error("buildFieldFilters: value array cannot be empty");
72
+ }
73
+ if (value.length === 1) {
74
+ return buildFieldFilters([value[0]]);
75
+ }
76
+ return aqlParenExpression({
77
+ type: "paren",
78
+ expression: buildFieldFilters(value),
79
+ });
80
+ };
81
+ if (filters.length === 1) {
82
+ const [filter] = filters;
83
+ if (filter.type === "field") {
84
+ return buildAqlFromFieldFilter(filter);
85
+ }
86
+ return buildAqlFromFilterGroup(filter);
87
+ }
88
+ const [first, second, ...rest] = filters;
89
+ if (rest.length === 0) {
90
+ return aqlBinaryExpression({
91
+ type: "binary",
92
+ operator: first.logicalOperator ?? "AND",
93
+ left: buildFieldFilters([first]),
94
+ right: buildFieldFilters([second]),
95
+ });
96
+ }
97
+ return rest.reduce((acc, filter) => {
98
+ return aqlBinaryExpression({
99
+ type: "binary",
100
+ operator: filter.logicalOperator ?? "AND",
101
+ left: acc,
102
+ right: buildFieldFilters([filter]),
103
+ });
104
+ }, buildFieldFilters([first, second]));
105
+ };
106
+ const buildArrayIntersectionFilter = (key, values, maxIndex = 20) => {
107
+ if (values.length === 0) {
108
+ throw new Error("buildArrayIntersectionFilter: values array cannot be empty");
109
+ }
110
+ const conditionsPerValue = values.map((value) => {
111
+ const indexConditions = Array.from({ length: maxIndex + 1 }, (_, index) => {
112
+ return aqlConditionExpression({
113
+ type: "condition",
114
+ left: {
115
+ identifier: key,
116
+ param: {
117
+ value: index,
118
+ type: "number",
119
+ },
120
+ },
121
+ operator: "EQ",
122
+ right: {
123
+ value,
124
+ type: "STRING",
125
+ },
126
+ });
127
+ });
128
+ if (indexConditions.length === 1) {
129
+ return indexConditions[0];
130
+ }
131
+ return indexConditions.reduce((acc, condition, index) => {
132
+ if (index === 0) {
133
+ return condition;
134
+ }
135
+ return aqlBinaryExpression({
136
+ type: "binary",
137
+ operator: "OR",
138
+ left: acc,
139
+ right: condition,
140
+ });
141
+ }, indexConditions[0]);
142
+ });
143
+ if (conditionsPerValue.length === 1) {
144
+ return conditionsPerValue[0];
145
+ }
146
+ return conditionsPerValue.reduce((acc, condition, index) => {
147
+ if (index === 0) {
148
+ return condition;
149
+ }
150
+ return aqlBinaryExpression({
151
+ type: "binary",
152
+ operator: "OR",
153
+ left: acc,
154
+ right: condition,
155
+ });
156
+ }, conditionsPerValue[0]);
157
+ };
158
+ export const buildFilterPredicate = (filters) => {
159
+ return createAqlPredicate(buildFieldFilters(filters));
160
+ };
@@ -0,0 +1,3 @@
1
+ export * from "./builders.js";
2
+ export type { ArrayField, BooleanField, Field, FieldFilter, FieldFilterGroup, Filter, LogicalOperator, NumberField, StringField, } from "./model.js";
3
+ export { MAX_ARRAY_FIELD_VALUES } from "./model.js";
@@ -0,0 +1,2 @@
1
+ export * from "./builders.js";
2
+ export { MAX_ARRAY_FIELD_VALUES } from "./model.js";