@allurereport/web-awesome 3.0.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/dist/multi/173.app-79c65c7bff941abcbc51.js +1 -0
  2. package/dist/multi/174.app-79c65c7bff941abcbc51.js +1 -0
  3. package/dist/multi/252.app-79c65c7bff941abcbc51.js +1 -0
  4. package/dist/multi/282.app-79c65c7bff941abcbc51.js +1 -0
  5. package/dist/multi/29.app-79c65c7bff941abcbc51.js +1 -0
  6. package/dist/multi/416.app-79c65c7bff941abcbc51.js +1 -0
  7. package/dist/multi/527.app-79c65c7bff941abcbc51.js +1 -0
  8. package/dist/multi/600.app-79c65c7bff941abcbc51.js +1 -0
  9. package/dist/multi/605.app-79c65c7bff941abcbc51.js +1 -0
  10. package/dist/multi/638.app-79c65c7bff941abcbc51.js +1 -0
  11. package/dist/multi/672.app-79c65c7bff941abcbc51.js +1 -0
  12. package/dist/multi/686.app-79c65c7bff941abcbc51.js +1 -0
  13. package/dist/multi/725.app-79c65c7bff941abcbc51.js +1 -0
  14. package/dist/multi/741.app-79c65c7bff941abcbc51.js +1 -0
  15. package/dist/multi/755.app-79c65c7bff941abcbc51.js +1 -0
  16. package/dist/multi/894.app-79c65c7bff941abcbc51.js +1 -0
  17. package/dist/multi/91.app-79c65c7bff941abcbc51.js +1 -0
  18. package/dist/multi/943.app-79c65c7bff941abcbc51.js +1 -0
  19. package/dist/multi/980.app-79c65c7bff941abcbc51.js +1 -0
  20. package/dist/multi/app-79c65c7bff941abcbc51.js +2 -0
  21. package/dist/multi/{app-bae2a0fe5738d77cd976.js.LICENSE.txt → app-79c65c7bff941abcbc51.js.LICENSE.txt} +7 -0
  22. package/dist/multi/manifest.json +21 -21
  23. package/dist/multi/styles-9e390bad7ce54a807a8e.css +49 -0
  24. package/dist/single/app-3ca67f29d0f1166c08ca.js +2 -0
  25. package/dist/single/{app-996d3b5869f8fc942b66.js.LICENSE.txt → app-3ca67f29d0f1166c08ca.js.LICENSE.txt} +7 -0
  26. package/dist/single/manifest.json +1 -1
  27. package/package.json +7 -7
  28. package/src/assets/scss/vars.scss +3 -0
  29. package/src/components/BaseLayout/index.tsx +25 -21
  30. package/src/components/BaseLayout/styles.scss +1 -0
  31. package/src/components/Charts/index.tsx +5 -2
  32. package/src/components/Footer/FooterVersion.tsx +14 -8
  33. package/src/components/Header/index.tsx +9 -7
  34. package/src/components/HeaderControls/index.tsx +5 -2
  35. package/src/components/MainReport/styles.scss +1 -0
  36. package/src/components/Metadata/index.tsx +24 -7
  37. package/src/components/ReportBody/HeaderActions.tsx +4 -13
  38. package/src/components/ReportBody/SortBy.tsx +27 -13
  39. package/src/components/ReportBody/index.tsx +1 -1
  40. package/src/components/ReportBody/styles.scss +4 -1
  41. package/src/components/ReportFilters/BaseFilters.tsx +345 -0
  42. package/src/components/ReportFilters/RetryFlaky.tsx +29 -0
  43. package/src/components/ReportFilters/TagsFilter.tsx +41 -0
  44. package/src/components/ReportFilters/TransitionFilter.tsx +49 -0
  45. package/src/components/ReportFilters/index.tsx +44 -0
  46. package/src/components/ReportFilters/styles.scss +55 -0
  47. package/src/components/ReportSearch/index.tsx +29 -0
  48. package/src/components/ReportTabs/index.tsx +1 -1
  49. package/src/components/SectionPicker/index.tsx +1 -1
  50. package/src/components/SplitLayout/index.tsx +7 -3
  51. package/src/components/TestResult/TrEnvironmentItem/index.tsx +2 -2
  52. package/src/components/TestResult/TrHeader/TrBreadcrumbs.tsx +2 -2
  53. package/src/components/TestResult/TrHistory/TrHistoryItem.tsx +38 -7
  54. package/src/components/TestResult/TrHistory/index.tsx +18 -8
  55. package/src/components/TestResult/TrHistory/styles.scss +4 -7
  56. package/src/components/TestResult/TrInfo/styles.scss +1 -0
  57. package/src/components/TestResult/TrNavigation/index.tsx +109 -68
  58. package/src/components/TestResult/TrNavigation/styles.scss +15 -25
  59. package/src/components/TestResult/TrPwTraces/PwTraceButton.tsx +1 -8
  60. package/src/components/TestResult/TrRetriesView/TrRetriesItem.tsx +2 -3
  61. package/src/components/TestResult/TrRetriesView/index.tsx +4 -3
  62. package/src/components/TestResult/TrSteps/TrAttachment.tsx +5 -3
  63. package/src/components/TestResult/TrSteps/TrAttachmentInfo.tsx +10 -3
  64. package/src/components/TestResult/TrTabs/index.tsx +7 -23
  65. package/src/components/TestResult/index.tsx +9 -4
  66. package/src/components/TestResult/styles.scss +1 -0
  67. package/src/components/Tree/index.tsx +14 -9
  68. package/src/index.html +19 -18
  69. package/src/index.tsx +20 -27
  70. package/src/locales/az.json +39 -11
  71. package/src/locales/de.json +39 -11
  72. package/src/locales/en.json +39 -11
  73. package/src/locales/es.json +39 -11
  74. package/src/locales/fr.json +39 -11
  75. package/src/locales/he.json +39 -11
  76. package/src/locales/hy.json +39 -11
  77. package/src/locales/it.json +39 -11
  78. package/src/locales/ja.json +39 -11
  79. package/src/locales/ka.json +39 -11
  80. package/src/locales/kr.json +39 -11
  81. package/src/locales/nl.json +39 -11
  82. package/src/locales/pl.json +39 -11
  83. package/src/locales/pt.json +39 -11
  84. package/src/locales/ru.json +39 -11
  85. package/src/locales/sv.json +39 -11
  86. package/src/locales/tr.json +39 -11
  87. package/src/locales/ua.json +39 -11
  88. package/src/locales/zh.json +39 -11
  89. package/src/stores/chart.ts +2 -2
  90. package/src/stores/env.ts +6 -6
  91. package/src/stores/envInfo.ts +2 -2
  92. package/src/stores/globals.ts +1 -1
  93. package/src/stores/index.ts +0 -1
  94. package/src/stores/layout.ts +20 -11
  95. package/src/stores/locale.ts +2 -1
  96. package/src/stores/qualityGate.ts +2 -2
  97. package/src/stores/router.ts +25 -91
  98. package/src/stores/sections.ts +32 -45
  99. package/src/stores/stats.ts +4 -4
  100. package/src/stores/testResult.ts +5 -0
  101. package/src/stores/testResults.ts +7 -5
  102. package/src/stores/tree.ts +20 -13
  103. package/src/stores/treeFilters/actions.ts +48 -52
  104. package/src/stores/treeFilters/constants.ts +11 -5
  105. package/src/stores/treeFilters/model.ts +51 -0
  106. package/src/stores/treeFilters/store.ts +260 -60
  107. package/src/stores/treeFilters/utils.ts +132 -0
  108. package/src/stores/treeSort.ts +71 -0
  109. package/src/stores/variables.ts +3 -3
  110. package/src/utils/treeFilters.ts +48 -66
  111. package/test/components/Header.test.tsx +49 -58
  112. package/test/utils/treeFilters.test.ts +18 -321
  113. package/types.d.ts +2 -1
  114. package/dist/multi/173.app-bae2a0fe5738d77cd976.js +0 -1
  115. package/dist/multi/174.app-bae2a0fe5738d77cd976.js +0 -1
  116. package/dist/multi/252.app-bae2a0fe5738d77cd976.js +0 -1
  117. package/dist/multi/282.app-bae2a0fe5738d77cd976.js +0 -1
  118. package/dist/multi/29.app-bae2a0fe5738d77cd976.js +0 -1
  119. package/dist/multi/416.app-bae2a0fe5738d77cd976.js +0 -1
  120. package/dist/multi/527.app-bae2a0fe5738d77cd976.js +0 -1
  121. package/dist/multi/600.app-bae2a0fe5738d77cd976.js +0 -1
  122. package/dist/multi/605.app-bae2a0fe5738d77cd976.js +0 -1
  123. package/dist/multi/638.app-bae2a0fe5738d77cd976.js +0 -1
  124. package/dist/multi/672.app-bae2a0fe5738d77cd976.js +0 -1
  125. package/dist/multi/686.app-bae2a0fe5738d77cd976.js +0 -1
  126. package/dist/multi/725.app-bae2a0fe5738d77cd976.js +0 -1
  127. package/dist/multi/741.app-bae2a0fe5738d77cd976.js +0 -1
  128. package/dist/multi/755.app-bae2a0fe5738d77cd976.js +0 -1
  129. package/dist/multi/894.app-bae2a0fe5738d77cd976.js +0 -1
  130. package/dist/multi/91.app-bae2a0fe5738d77cd976.js +0 -1
  131. package/dist/multi/943.app-bae2a0fe5738d77cd976.js +0 -1
  132. package/dist/multi/980.app-bae2a0fe5738d77cd976.js +0 -1
  133. package/dist/multi/app-bae2a0fe5738d77cd976.js +0 -2
  134. package/dist/multi/styles-bbf68b2ba63c38b53c38.css +0 -48
  135. package/dist/single/app-996d3b5869f8fc942b66.js +0 -2
  136. package/src/components/ReportBody/Filters.tsx +0 -122
  137. package/src/stores/theme.ts +0 -30
  138. package/src/stores/treeFilters/index.ts +0 -3
  139. package/src/stores/treeFilters/types.ts +0 -12
  140. package/test/stores/treeFilters.test.ts +0 -302
@@ -1,73 +1,273 @@
1
- import { batch, computed, effect, signal } from "@preact/signals";
1
+ import type { TestStatus, TestStatusTransition } from "@allurereport/core-api";
2
+ import { getParamValue, getParamValues } from "@allurereport/web-commons";
3
+ import { computed, signal } from "@preact/signals";
2
4
  import type { AwesomeStatus } from "types";
3
- import { loadFromLocalStorage } from "@/utils/loadFromLocalStorage";
4
- import { persist } from "@/utils/persist";
5
- import { transitionFiltersList } from "./constants";
6
- import type { TreeDirection, TreeFilters, TreeFiltersState, TreeSortBy } from "./types";
7
-
8
- export const treeQuery = signal<string>("");
9
- export const treeStatus = signal<AwesomeStatus>("total");
10
- export const treeSortBy = signal<TreeSortBy>("order");
11
- export const treeDirection = signal<TreeDirection>("asc");
12
- export const treeFilter = signal<Record<TreeFilters, boolean>>({
13
- flaky: false,
14
- retry: false,
15
- new: false,
16
- fixed: false,
17
- regressed: false,
18
- malfunctioned: false,
5
+ import {
6
+ setFlakyFilter,
7
+ setQueryFilter,
8
+ setRetryFilter,
9
+ setStatusFilter,
10
+ setTagsFilter,
11
+ setTransitionFilter,
12
+ } from "./actions";
13
+ import { PARAMS } from "./constants";
14
+ import type {
15
+ AwesomeArrayFieldFilter,
16
+ AwesomeBooleanFieldFilter,
17
+ AwesomeFilter,
18
+ AwesomeFilterGroupSimple,
19
+ AwesomeStringFieldFilter,
20
+ } from "./model";
21
+ import {
22
+ isFlakyFilter,
23
+ isRetryFilter,
24
+ isTagFilter,
25
+ isTransitionFilter,
26
+ validateStatus,
27
+ validateTransition,
28
+ } from "./utils";
29
+
30
+ export const treeTags = signal<string[]>([]);
31
+
32
+ const hasTreeTags = computed(() => treeTags.value.length > 0);
33
+
34
+ const urlQueryFilter = computed<string | undefined>(() => {
35
+ const queryValue = getParamValue(PARAMS.QUERY) ?? "";
36
+
37
+ if (queryValue.trim() === "") {
38
+ return undefined;
39
+ }
40
+
41
+ return queryValue;
19
42
  });
20
43
 
21
- const initialized = signal(false);
22
-
23
- const init = () => {
24
- const initialState = loadFromLocalStorage<TreeFiltersState>("treeFilters", {
25
- query: "",
26
- status: "total",
27
- filter: {
28
- flaky: false,
29
- retry: false,
30
- new: false,
31
- fixed: false,
32
- regressed: false,
33
- malfunctioned: false,
34
- },
35
- sortBy: "order",
36
- direction: "asc",
37
- });
38
-
39
- batch(() => {
40
- treeQuery.value = initialState.query;
41
- treeStatus.value = initialState.status;
42
- treeSortBy.value = initialState.sortBy;
43
- treeDirection.value = initialState.direction;
44
- treeFilter.value = initialState.filter;
45
- initialized.value = true;
46
- });
44
+ const urlStatusFilter = computed<TestStatus | undefined>(() => {
45
+ const status = getParamValue(PARAMS.STATUS) ?? undefined;
46
+
47
+ if (status && validateStatus(status)) {
48
+ return status;
49
+ }
50
+
51
+ return undefined;
52
+ });
53
+
54
+ const urlFlakyFilter = computed(() => getParamValue(PARAMS.FLAKY) === "true");
55
+ const urlRetryFilter = computed(() => getParamValue(PARAMS.RETRY) === "true");
56
+
57
+ const EMPTY_TRANSITIONS: TestStatusTransition[] = [];
58
+
59
+ const urlTransitionFilter = computed(() => {
60
+ const transitions = getParamValues(PARAMS.TRANSITION) ?? EMPTY_TRANSITIONS;
61
+
62
+ if (transitions.length === 0) {
63
+ return EMPTY_TRANSITIONS;
64
+ }
65
+
66
+ return transitions.filter((transition) => validateTransition(transition));
67
+ });
68
+
69
+ const EMPTY_TAGS: string[] = [];
70
+
71
+ const urlTagsFilter = computed<string[]>(() => {
72
+ const tags = getParamValues(PARAMS.TAGS) ?? EMPTY_TAGS;
73
+
74
+ if (tags.length === 0) {
75
+ return EMPTY_TAGS;
76
+ }
77
+
78
+ if (treeTags.value.length === 0) {
79
+ return tags;
80
+ }
81
+
82
+ return tags.filter((tag) => treeTags.value.includes(tag));
83
+ });
84
+
85
+ const treeStatusFilter = computed<AwesomeStringFieldFilter>(() => ({
86
+ type: "field",
87
+ logicalOperator: "AND",
88
+ value: {
89
+ key: "status",
90
+ value: urlStatusFilter.value,
91
+ type: "string",
92
+ strict: false,
93
+ },
94
+ }));
95
+
96
+ export const treeQueryFilter = computed<AwesomeFilterGroupSimple>(() => {
97
+ return {
98
+ type: "group",
99
+ logicalOperator: "AND",
100
+ value: [
101
+ {
102
+ type: "field",
103
+ logicalOperator: "OR",
104
+ value: {
105
+ key: "name",
106
+ value: urlQueryFilter.value,
107
+ type: "string",
108
+ strict: false,
109
+ },
110
+ },
111
+ {
112
+ type: "field",
113
+ logicalOperator: "OR",
114
+ value: {
115
+ key: "id",
116
+ value: urlQueryFilter.value,
117
+ type: "string",
118
+ strict: false,
119
+ },
120
+ },
121
+ ],
122
+ };
123
+ });
124
+
125
+ export const treeQueryFilterValue = computed(() => treeQueryFilter.value.value[0].value.value as string);
126
+
127
+ export const setTreeQueryFilter = (query: string) => {
128
+ setQueryFilter(query);
47
129
  };
48
130
 
49
- init();
131
+ const treeRetryFilter = computed<AwesomeBooleanFieldFilter>(() => {
132
+ return {
133
+ type: "field",
134
+ logicalOperator: "OR",
135
+ value: {
136
+ key: "retry",
137
+ value: !!urlRetryFilter.value,
138
+ type: "boolean",
139
+ },
140
+ };
141
+ });
142
+
143
+ const treeFlakyFilter = computed<AwesomeBooleanFieldFilter>(() => ({
144
+ type: "field",
145
+ logicalOperator: "OR",
146
+ value: {
147
+ key: "flaky",
148
+ value: !!urlFlakyFilter.value,
149
+ type: "boolean",
150
+ },
151
+ }));
152
+
153
+ const treeTransitionFilter = computed<AwesomeFilterGroupSimple>(() => ({
154
+ type: "group",
155
+ logicalOperator: "AND",
156
+ fieldKey: "transition",
157
+ value: urlTransitionFilter.value.map((transition) => ({
158
+ type: "field",
159
+ value: {
160
+ key: "transition",
161
+ value: transition,
162
+ type: "string",
163
+ logicalOperator: "OR",
164
+ strict: true,
165
+ },
166
+ })),
167
+ }));
50
168
 
51
- const treeFiltersState = computed(() => ({
52
- query: treeQuery.value,
53
- status: treeStatus.value,
54
- sortBy: treeSortBy.value,
55
- direction: treeDirection.value,
56
- filter: treeFilter.value,
169
+ const treeTagsFilter = computed<AwesomeArrayFieldFilter>(() => ({
170
+ type: "field",
171
+ logicalOperator: "AND",
172
+ value: {
173
+ key: "tags",
174
+ value: urlTagsFilter.value,
175
+ type: "array",
176
+ strict: false,
177
+ },
57
178
  }));
58
179
 
59
- effect(() => {
60
- if (!initialized.value) {
61
- return;
180
+ export const treeQuickFilters = computed<AwesomeFilter[]>(() => [
181
+ treeRetryFilter.value,
182
+ treeFlakyFilter.value,
183
+ treeTransitionFilter.value,
184
+ treeTagsFilter.value,
185
+ ]);
186
+
187
+ export const treeFilters = computed(() => {
188
+ const filters: AwesomeFilter[] = [];
189
+
190
+ if (treeQueryFilterValue.value) {
191
+ filters.push(treeQueryFilter.value);
192
+ }
193
+
194
+ const hasBothRetryAndFlaky = urlRetryFilter.value && urlFlakyFilter.value;
195
+
196
+ if (hasBothRetryAndFlaky) {
197
+ filters.push({
198
+ type: "group",
199
+ logicalOperator: "AND",
200
+ value: [
201
+ { ...treeRetryFilter.value, logicalOperator: "OR" },
202
+ { ...treeFlakyFilter.value, logicalOperator: "OR" },
203
+ ],
204
+ });
205
+ }
206
+
207
+ if (!hasBothRetryAndFlaky && urlRetryFilter.value) {
208
+ filters.push({ ...treeRetryFilter.value, logicalOperator: "AND" });
209
+ }
210
+
211
+ if (!hasBothRetryAndFlaky && urlFlakyFilter.value) {
212
+ filters.push({ ...treeFlakyFilter.value, logicalOperator: "AND" });
213
+ }
214
+
215
+ if (urlTransitionFilter.value.length > 0) {
216
+ filters.push(treeTransitionFilter.value);
217
+ }
218
+
219
+ if (urlTagsFilter.value.length > 0) {
220
+ filters.push(treeTagsFilter.value);
221
+ }
222
+
223
+ if (urlStatusFilter.value) {
224
+ filters.push(treeStatusFilter.value);
62
225
  }
63
226
 
64
- persist(["treeFilters", treeFiltersState.value]);
227
+ return filters;
65
228
  });
66
229
 
67
- export const transitionFilters = computed(() =>
68
- transitionFiltersList.map((transition) => [transition, treeFilter.value[transition]] as const),
69
- );
230
+ export const setTreeFilter = (filter: AwesomeFilter) => {
231
+ if (isTransitionFilter(filter)) {
232
+ const transitions: TestStatusTransition[] = [];
70
233
 
71
- export const testTypeFilters = computed(() =>
72
- (["flaky", "retry"] as const).map((testType) => [testType, treeFilter.value[testType]] as const),
73
- );
234
+ for (const v of filter.value) {
235
+ if (v.type === "field" && v.value.type === "string" && v.value.key === "transition") {
236
+ transitions.push(v.value.value as TestStatusTransition);
237
+ }
238
+ }
239
+
240
+ setTransitionFilter(transitions);
241
+ }
242
+
243
+ if (isRetryFilter(filter)) {
244
+ setRetryFilter(filter.value.value);
245
+ }
246
+
247
+ if (isFlakyFilter(filter)) {
248
+ setFlakyFilter(filter.value.value);
249
+ }
250
+
251
+ if (
252
+ isTagFilter(filter) &&
253
+ // Apply tags filter only if there are tags to filter by
254
+ hasTreeTags.peek()
255
+ ) {
256
+ setTagsFilter(filter.value.value);
257
+ }
258
+ };
259
+
260
+ export const treeStatus = computed<AwesomeStatus>(() => urlStatusFilter.value ?? "total");
261
+
262
+ export const setTreeStatus = (status: AwesomeStatus) => {
263
+ setStatusFilter(status === "total" ? undefined : status);
264
+ };
265
+
266
+ export const clearTreeFilters = () => {
267
+ setQueryFilter("");
268
+ setRetryFilter(false);
269
+ setFlakyFilter(false);
270
+ setTransitionFilter([]);
271
+ setTagsFilter([]);
272
+ setStatusFilter();
273
+ };
@@ -0,0 +1,132 @@
1
+ import type { TestStatus, TestStatusTransition } from "@allurereport/core-api";
2
+ import { MAX_ARRAY_FIELD_VALUES, getCurrentUrl } from "@allurereport/web-commons";
3
+ import { PARAMS, STATUSES, TRANSITIONS } from "./constants";
4
+ import type {
5
+ AwesomeArrayFieldFilter,
6
+ AwesomeBooleanFieldFilter,
7
+ AwesomeFilter,
8
+ AwesomeFilterGroupSimple,
9
+ Filters,
10
+ } from "./model";
11
+
12
+ export const truncateArrayFieldValues = (values: string[]): string[] => {
13
+ return values.slice(0, MAX_ARRAY_FIELD_VALUES);
14
+ };
15
+
16
+ export const getTagsFilterUrl = (tags: string[]): string => {
17
+ const url = new URL(window.location.pathname, window.location.origin);
18
+
19
+ tags.forEach((tag) => {
20
+ url.searchParams.append("tags", tag);
21
+ });
22
+
23
+ return url.toString();
24
+ };
25
+
26
+ export const validateTransition = (transition: string): transition is TestStatusTransition => {
27
+ return TRANSITIONS.includes(transition as TestStatusTransition);
28
+ };
29
+
30
+ export const validateStatus = (status: string): status is TestStatus => {
31
+ return STATUSES.includes(status as TestStatus);
32
+ };
33
+
34
+ export const migrateFilterParam = () => {
35
+ if (typeof window === "undefined") {
36
+ return;
37
+ }
38
+
39
+ const currentUrl = new URL(getCurrentUrl());
40
+
41
+ const hasFilterParam = currentUrl.searchParams.has("filter");
42
+
43
+ if (!hasFilterParam) {
44
+ return;
45
+ }
46
+
47
+ const filtersParam = currentUrl.searchParams.getAll("filter") ?? [];
48
+
49
+ const retryParamFromFilter = filtersParam.includes("retry");
50
+ const flakyParamFromFilter = filtersParam.includes("flaky");
51
+ const retryParamFromUrl = currentUrl.searchParams.get("retry") === "true";
52
+ const flakyParamFromUrl = currentUrl.searchParams.get("flaky") === "true";
53
+ const transitionParamFromUrl = currentUrl.searchParams.get("transition") ?? undefined;
54
+ const transitionParam = filtersParam.find((filter) => validateTransition(filter));
55
+
56
+ if (retryParamFromFilter || retryParamFromUrl) {
57
+ currentUrl.searchParams.set("retry", "true");
58
+ } else {
59
+ currentUrl.searchParams.delete("retry");
60
+ }
61
+
62
+ if (flakyParamFromFilter || flakyParamFromUrl) {
63
+ currentUrl.searchParams.set("flaky", "true");
64
+ } else {
65
+ currentUrl.searchParams.delete("flaky");
66
+ }
67
+
68
+ if (transitionParamFromUrl || transitionParam) {
69
+ currentUrl.searchParams.set("transition", transitionParam ?? "");
70
+ } else {
71
+ currentUrl.searchParams.delete("transition");
72
+ }
73
+
74
+ currentUrl.searchParams.delete("filter");
75
+
76
+ window.history.replaceState(null, "", currentUrl.toString());
77
+ window.dispatchEvent(new Event("replaceState"));
78
+ };
79
+
80
+ export const constructFilterParams = (filters: Filters) => {
81
+ const params = new URLSearchParams();
82
+
83
+ if (filters.query) {
84
+ params.set(PARAMS.QUERY, filters.query);
85
+ }
86
+
87
+ if (filters.status) {
88
+ params.set(PARAMS.STATUS, filters.status);
89
+ }
90
+
91
+ if (filters.flaky) {
92
+ params.set(PARAMS.FLAKY, "true");
93
+ }
94
+
95
+ if (filters.retry) {
96
+ params.set(PARAMS.RETRY, "true");
97
+ }
98
+
99
+ if (filters.transition) {
100
+ filters.transition.forEach((transition) => {
101
+ params.set(PARAMS.TRANSITION, transition);
102
+ });
103
+ }
104
+
105
+ if (filters.tags) {
106
+ filters.tags.forEach((tag) => {
107
+ params.set(PARAMS.TAGS, tag);
108
+ });
109
+ }
110
+
111
+ if (filters.status) {
112
+ params.set(PARAMS.STATUS, filters.status);
113
+ }
114
+
115
+ return params;
116
+ };
117
+
118
+ export const isRetryFilter = (filter: AwesomeFilter): filter is AwesomeBooleanFieldFilter => {
119
+ return filter.type === "field" && filter.value.type === "boolean" && filter.value.key === "retry";
120
+ };
121
+
122
+ export const isFlakyFilter = (filter: AwesomeFilter): filter is AwesomeBooleanFieldFilter => {
123
+ return filter.type === "field" && filter.value.type === "boolean" && filter.value.key === "flaky";
124
+ };
125
+
126
+ export const isTagFilter = (filter: AwesomeFilter): filter is AwesomeArrayFieldFilter => {
127
+ return filter.type === "field" && filter.value.type === "array" && filter.value.key === "tags";
128
+ };
129
+
130
+ export const isTransitionFilter = (filter: AwesomeFilter): filter is AwesomeFilterGroupSimple => {
131
+ return filter.type === "group" && filter.fieldKey === "transition";
132
+ };
@@ -0,0 +1,71 @@
1
+ import { getParamValue, hasParam, setParams } from "@allurereport/web-commons";
2
+ import { computed, effect } from "@preact/signals";
3
+
4
+ export type SortByDirection = "asc" | "desc";
5
+ export type SortByField = "order" | "duration" | "status" | "name";
6
+ export type SortBy = `${SortByField},${SortByDirection}`;
7
+
8
+ const DEFAULT_SORT_BY: SortBy = "order,asc";
9
+
10
+ export const DIRECTIONS: SortByDirection[] = ["asc", "desc"];
11
+
12
+ export const SORT_BY_STORAGE_KEY = "ALLURE_REPORT_SORT_BY";
13
+ export const SORT_BY_FIELDS: SortByField[] = ["order", "duration", "status", "name"];
14
+
15
+ const SORT_BY_PARAM = "sortBy";
16
+
17
+ const hasSortByParam = computed(() => hasParam(SORT_BY_PARAM));
18
+
19
+ export const setSortBy = (sortByValue: SortBy) => {
20
+ if (hasSortByParam.peek()) {
21
+ setParams({
22
+ key: SORT_BY_PARAM,
23
+ value: undefined,
24
+ });
25
+ }
26
+
27
+ if (typeof window === "undefined") {
28
+ return;
29
+ }
30
+
31
+ localStorage.setItem(SORT_BY_STORAGE_KEY, sortByValue);
32
+ };
33
+
34
+ const validateSortBy = (sortByValue: string): sortByValue is SortBy => {
35
+ const parts = sortByValue.split(",");
36
+ if (parts.length !== 2) {
37
+ return false;
38
+ }
39
+ const [field, direction] = parts;
40
+
41
+ return SORT_BY_FIELDS.includes(field as SortByField) && DIRECTIONS.includes(direction as SortByDirection);
42
+ };
43
+
44
+ export const sortBy = computed<SortBy>(() => {
45
+ const urlSortBy = getParamValue(SORT_BY_PARAM) ?? undefined;
46
+
47
+ // SortBy from URL is taking precedence over the storage value
48
+ if (urlSortBy && validateSortBy(urlSortBy.toLowerCase())) {
49
+ return urlSortBy.toLowerCase() as SortBy;
50
+ }
51
+
52
+ if (typeof window === "undefined") {
53
+ return DEFAULT_SORT_BY;
54
+ }
55
+
56
+ const storageSortBy = localStorage.getItem(SORT_BY_STORAGE_KEY);
57
+
58
+ if (storageSortBy && validateSortBy(storageSortBy.toLowerCase())) {
59
+ return storageSortBy.toLowerCase() as SortBy;
60
+ }
61
+
62
+ return DEFAULT_SORT_BY;
63
+ });
64
+
65
+ effect(() => {
66
+ if (typeof window === "undefined") {
67
+ return;
68
+ }
69
+
70
+ localStorage.setItem(SORT_BY_STORAGE_KEY, sortBy.value);
71
+ });
@@ -12,7 +12,7 @@ export const variables = signal<StoreSignalState<Record<string, Variables>>>({
12
12
 
13
13
  export const fetchVariables = async (env: string = "default") => {
14
14
  variables.value = {
15
- ...variables.value,
15
+ ...variables.peek(),
16
16
  loading: true,
17
17
  error: undefined,
18
18
  };
@@ -24,7 +24,7 @@ export const fetchVariables = async (env: string = "default") => {
24
24
 
25
25
  variables.value = {
26
26
  data: {
27
- ...variables.value.data,
27
+ ...variables.peek().data,
28
28
  [env]: res,
29
29
  },
30
30
  error: undefined,
@@ -32,7 +32,7 @@ export const fetchVariables = async (env: string = "default") => {
32
32
  };
33
33
  } catch (e) {
34
34
  variables.value = {
35
- ...variables.value,
35
+ ...variables.peek(),
36
36
  error: e.message,
37
37
  loading: false,
38
38
  };