@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
@@ -0,0 +1,345 @@
1
+ import {
2
+ type ArrayField,
3
+ type BooleanField,
4
+ type FieldFilter,
5
+ type FieldFilterGroup,
6
+ type LogicalOperator,
7
+ MAX_ARRAY_FIELD_VALUES,
8
+ } from "@allurereport/web-commons";
9
+ import {
10
+ Button,
11
+ Counter,
12
+ DropdownButton,
13
+ IconButton,
14
+ Menu,
15
+ Text,
16
+ Tooltip,
17
+ allureIcons,
18
+ useTooltip,
19
+ } from "@allurereport/web-components";
20
+ import { createPortal } from "preact/compat";
21
+ import { useCallback, useEffect, useRef, useState } from "preact/hooks";
22
+ import { useI18n } from "@/stores/locale";
23
+ import * as styles from "./styles.scss";
24
+
25
+ const FilterBtn = (props: {
26
+ icon: string;
27
+ text: string;
28
+ isActive: boolean;
29
+ counter?: number;
30
+ onClick: () => void;
31
+ onClear?: () => void;
32
+ isDropdown?: boolean;
33
+ isExpanded?: boolean;
34
+ error?: string;
35
+ testId?: string;
36
+ description?: string;
37
+ disabled?: boolean;
38
+ }) => {
39
+ const {
40
+ icon,
41
+ text,
42
+ isActive,
43
+ counter,
44
+ onClick,
45
+ onClear,
46
+ isDropdown,
47
+ isExpanded,
48
+ error,
49
+ testId,
50
+ description,
51
+ disabled,
52
+ } = props;
53
+ const [isTooltipVisible, setIsTooltipVisible] = useState(false);
54
+ const tooltipRef = useRef<HTMLDivElement>(null);
55
+ const triggerRef = useRef<HTMLDivElement>(null);
56
+
57
+ useTooltip({ isVisible: isTooltipVisible, triggerRef, tooltipRef, placement: "top" });
58
+
59
+ const hasError = error?.length > 0;
60
+ const hasDescription = description?.length > 0;
61
+
62
+ useEffect(() => {
63
+ if (!hasError && !hasDescription) {
64
+ setIsTooltipVisible(false);
65
+ }
66
+ }, [hasError, hasDescription]);
67
+
68
+ const handleMouseOver = useCallback(() => {
69
+ if (hasError || hasDescription) {
70
+ setIsTooltipVisible(true);
71
+ }
72
+ }, [hasError, hasDescription]);
73
+
74
+ const handleMouseLeave = useCallback(() => {
75
+ setIsTooltipVisible(false);
76
+ }, []);
77
+
78
+ const commonProps = {
79
+ icon,
80
+ text,
81
+ size: "m",
82
+ style: isActive ? "raised" : "ghost",
83
+ action: hasError ? "danger" : "default",
84
+ onClick,
85
+ trailingSlot: counter !== undefined ? <Counter count={counter} size="s" truncateCount /> : null,
86
+ isDisabled: disabled,
87
+ } as const;
88
+
89
+ const clearButton =
90
+ isActive && onClear ? (
91
+ <div className={styles.clear}>
92
+ <IconButton size="xs" style="ghost" icon={allureIcons.solidXCircle} onClick={onClear} rounded />
93
+ </div>
94
+ ) : null;
95
+
96
+ let content = <Button {...commonProps} />;
97
+
98
+ if (isDropdown) {
99
+ content = <DropdownButton {...commonProps} isExpanded={isExpanded} />;
100
+ }
101
+
102
+ const hasTooltipContent = !isExpanded && (hasError || hasDescription);
103
+
104
+ return (
105
+ <div className={styles.btnWrapper} data-testid={testId}>
106
+ <div ref={triggerRef} onPointerOver={handleMouseOver} onPointerLeave={handleMouseLeave}>
107
+ {content}
108
+ </div>
109
+ {clearButton}
110
+ {createPortal(
111
+ <div
112
+ ref={tooltipRef}
113
+ className={styles.tooltip}
114
+ data-testid="filter-tooltip"
115
+ data-visible={(isTooltipVisible && hasTooltipContent) || undefined}
116
+ >
117
+ {error && <Tooltip ref={tooltipRef}>{error}</Tooltip>}
118
+ {!error && description && <Tooltip ref={tooltipRef}>{description}</Tooltip>}
119
+ </div>,
120
+ document.body,
121
+ )}
122
+ </div>
123
+ );
124
+ };
125
+
126
+ export const BooleanFieldFilter = <T extends BooleanField = BooleanField>(props: {
127
+ field: T;
128
+ onChange: (filter: T) => void;
129
+ icon?: string;
130
+ label?: string;
131
+ testId?: string;
132
+ description?: string;
133
+ }) => {
134
+ const { field, onChange, icon, label, testId, description } = props;
135
+ const { value, key } = field;
136
+
137
+ const handleChange = useCallback(() => {
138
+ onChange({ ...field, value: !value });
139
+ }, [field, onChange, value]);
140
+
141
+ return (
142
+ <FilterBtn
143
+ icon={icon}
144
+ text={label ?? key}
145
+ isActive={value}
146
+ onClick={handleChange}
147
+ testId={testId}
148
+ description={description}
149
+ />
150
+ );
151
+ };
152
+
153
+ export const MultipleChoiceFieldFilter = (props: {
154
+ group: FieldFilterGroup;
155
+ counter?: boolean;
156
+ onChange: (group: FieldFilterGroup) => void;
157
+ onClear?: () => void;
158
+ options: { key: string; label?: string; description?: string; icon?: string }[];
159
+ fieldKey: string;
160
+ logicalOperator?: LogicalOperator;
161
+ strict?: boolean;
162
+ icon?: string;
163
+ label?: string;
164
+ testId?: string;
165
+ disabled?: boolean;
166
+ }) => {
167
+ const {
168
+ group,
169
+ onChange,
170
+ icon,
171
+ label,
172
+ fieldKey,
173
+ options,
174
+ logicalOperator = "AND",
175
+ strict = true,
176
+ counter = true,
177
+ onClear,
178
+ testId,
179
+ disabled,
180
+ } = props;
181
+ const { value } = group;
182
+
183
+ const handleOptionClick = useCallback(
184
+ (optionKey: string, optionValue: boolean) => {
185
+ const newValues = value.filter(
186
+ (v) =>
187
+ v.type === "field" && v.value.type === "string" && v.value.key === fieldKey && v.value.value !== optionKey,
188
+ );
189
+
190
+ if (optionValue) {
191
+ newValues.push({
192
+ type: "field",
193
+ value: {
194
+ type: "string",
195
+ key: fieldKey,
196
+ value: optionKey,
197
+ strict,
198
+ },
199
+ logicalOperator,
200
+ });
201
+ }
202
+
203
+ onChange({ ...group, value: newValues });
204
+ },
205
+ [group, onChange, value, fieldKey, strict, logicalOperator],
206
+ );
207
+
208
+ const isOptionChecked = (optionKey: string) => {
209
+ return value.some(
210
+ (v) => v.type === "field" && v.value.type === "string" && v.value.key === fieldKey && v.value.value === optionKey,
211
+ );
212
+ };
213
+
214
+ const checkedValuesCount = options.map(({ key }) => key).filter(isOptionChecked).length;
215
+ const hasCheckedValues = checkedValuesCount > 0;
216
+
217
+ if (disabled) {
218
+ return (
219
+ <FilterBtn
220
+ icon={icon}
221
+ text={label ?? fieldKey}
222
+ isActive={hasCheckedValues}
223
+ onClick={() => {}}
224
+ disabled
225
+ testId={testId}
226
+ />
227
+ );
228
+ }
229
+
230
+ return (
231
+ <Menu
232
+ size="l"
233
+ placement="bottom-start"
234
+ menuTrigger={({ onClick, isOpened }) => (
235
+ <FilterBtn
236
+ icon={icon}
237
+ text={label ?? fieldKey}
238
+ isActive={hasCheckedValues}
239
+ isExpanded={isOpened}
240
+ counter={counter && hasCheckedValues ? checkedValuesCount : undefined}
241
+ onClick={onClick}
242
+ onClear={onClear}
243
+ isDropdown
244
+ testId={testId}
245
+ />
246
+ )}
247
+ >
248
+ <Menu.Section>
249
+ {options.map((option) => (
250
+ <Menu.ItemWithCheckmark
251
+ key={option.key}
252
+ onClick={() => handleOptionClick(option.key, !isOptionChecked(option.key))}
253
+ isChecked={isOptionChecked(option.key)}
254
+ leadingIcon={option.icon}
255
+ closeMenuOnClick={false}
256
+ dataTestId={`${option.key}-filter`}
257
+ >
258
+ <div className={styles.itemContent}>
259
+ <Text tag="div">{option.label ?? option.key}</Text>
260
+ {option.description && (
261
+ <Text tag="div" size="s" type="paragraph" className={styles.description}>
262
+ {option.description}
263
+ </Text>
264
+ )}
265
+ </div>
266
+ </Menu.ItemWithCheckmark>
267
+ ))}
268
+ </Menu.Section>
269
+ </Menu>
270
+ );
271
+ };
272
+
273
+ export const ArrayFieldFilter = <T extends ArrayField = ArrayField>(props: {
274
+ filter: FieldFilter & { value: T };
275
+ counter?: boolean;
276
+ onChange: (filter: FieldFilter & { value: T }) => void;
277
+ onClear?: () => void;
278
+ options: { key: string; label?: string; icon?: string }[];
279
+ icon?: string;
280
+ label?: string;
281
+ description?: string;
282
+ disabled?: boolean;
283
+ }) => {
284
+ const { filter, onChange, icon, label, options, counter = true, onClear, description, disabled } = props;
285
+ const { value, key } = filter.value;
286
+ const { t } = useI18n("filters");
287
+
288
+ const handleOptionClick = useCallback(
289
+ (optionKey: string, optionValue: boolean) => {
290
+ const newValues = value.filter((v) => v !== optionKey);
291
+
292
+ if (optionValue) {
293
+ newValues.push(optionKey);
294
+ }
295
+
296
+ onChange({ ...filter, value: { ...filter.value, value: newValues } });
297
+ },
298
+ [filter, onChange, value],
299
+ );
300
+
301
+ const isOptionChecked = (optionKey: string) => {
302
+ return value.some((v) => v === optionKey);
303
+ };
304
+
305
+ const checkedValuesCount = options.map(({ key: optionKey }) => optionKey).filter(isOptionChecked).length;
306
+ const hasCheckedValues = checkedValuesCount > 0;
307
+
308
+ const errorText = t("errors.max_values", { count: MAX_ARRAY_FIELD_VALUES });
309
+ const hasError = checkedValuesCount > MAX_ARRAY_FIELD_VALUES;
310
+
311
+ return (
312
+ <Menu
313
+ placement="bottom-start"
314
+ menuTrigger={({ onClick, isOpened }) => (
315
+ <FilterBtn
316
+ icon={icon}
317
+ text={label ?? key}
318
+ isActive={hasCheckedValues}
319
+ isExpanded={isOpened}
320
+ counter={counter && hasCheckedValues ? checkedValuesCount : undefined}
321
+ onClick={onClick}
322
+ onClear={onClear}
323
+ isDropdown
324
+ error={hasError ? errorText : undefined}
325
+ description={description}
326
+ disabled={disabled}
327
+ />
328
+ )}
329
+ >
330
+ <Menu.Section>
331
+ {options.map((option) => (
332
+ <Menu.ItemWithCheckmark
333
+ closeMenuOnClick={false}
334
+ key={option.key}
335
+ onClick={() => handleOptionClick(option.key, !isOptionChecked(option.key))}
336
+ isChecked={isOptionChecked(option.key)}
337
+ leadingIcon={option.icon}
338
+ >
339
+ {option.label ?? option.key}
340
+ </Menu.ItemWithCheckmark>
341
+ ))}
342
+ </Menu.Section>
343
+ </Menu>
344
+ );
345
+ };
@@ -0,0 +1,29 @@
1
+ import { allureIcons } from "@allurereport/web-components";
2
+ import { useI18n } from "@/stores";
3
+ import type { AwesomeBooleanField, AwesomeFieldFilter } from "@/stores/treeFilters/model";
4
+ import { BooleanFieldFilter } from "./BaseFilters";
5
+
6
+ type RetryOrFlakyFilter = AwesomeFieldFilter & {
7
+ value: AwesomeBooleanField;
8
+ };
9
+
10
+ export const RetryFlakyFilter = (props: {
11
+ filter: RetryOrFlakyFilter;
12
+ onChange: (filter: RetryOrFlakyFilter) => void;
13
+ }) => {
14
+ const { filter, onChange } = props;
15
+ const { value: field } = filter;
16
+ const { key } = field;
17
+ const { t, t: tDescription } = useI18n("filters");
18
+
19
+ return (
20
+ <BooleanFieldFilter
21
+ field={field}
22
+ onChange={(value) => onChange({ ...filter, value })}
23
+ icon={key === "retry" ? allureIcons.lineArrowsRefreshCcw1 : allureIcons.lineIconBomb2}
24
+ label={t(key)}
25
+ testId={`${key}-filter`}
26
+ description={tDescription(`description.${key}`)}
27
+ />
28
+ );
29
+ };
@@ -0,0 +1,41 @@
1
+ import { computed } from "@preact/signals";
2
+ import { useI18n } from "@/stores";
3
+ import { treeTags } from "@/stores/treeFilters/store";
4
+ import type { AwesomeArrayFieldFilter } from "../../stores/treeFilters/model";
5
+ import { ArrayFieldFilter } from "./BaseFilters";
6
+
7
+ const options = computed(() => {
8
+ return treeTags.value.map((tag) => ({ key: tag, label: tag }));
9
+ });
10
+
11
+ export const TagsFilter = (props: {
12
+ filter: AwesomeArrayFieldFilter;
13
+ onChange: (filter: AwesomeArrayFieldFilter) => void;
14
+ }) => {
15
+ const { filter, onChange } = props;
16
+ const { t } = useI18n("filters");
17
+
18
+ if (options.value.length === 0) {
19
+ return null;
20
+ }
21
+
22
+ return (
23
+ <ArrayFieldFilter
24
+ filter={filter}
25
+ onChange={onChange}
26
+ options={options.value}
27
+ label={t("tags")}
28
+ description={t("description.tags")}
29
+ counter
30
+ onClear={() =>
31
+ onChange({
32
+ ...filter,
33
+ value: {
34
+ ...filter.value,
35
+ value: [],
36
+ },
37
+ })
38
+ }
39
+ />
40
+ );
41
+ };
@@ -0,0 +1,49 @@
1
+ import { allureIcons } from "@allurereport/web-components";
2
+ import { useMemo } from "preact/hooks";
3
+ import { useI18n } from "@/stores";
4
+ import type { AwesomeFilterGroupSimple } from "../../stores/treeFilters/model";
5
+ import { MultipleChoiceFieldFilter } from "./BaseFilters";
6
+
7
+ const transitionOptions = [
8
+ { key: "new", icon: allureIcons.lineAlertsNew },
9
+ { key: "fixed", icon: allureIcons.lineAlertsFixed },
10
+ { key: "regressed", icon: allureIcons.lineAlertsRegressed },
11
+ { key: "malfunctioned", icon: allureIcons.lineAlertsMalfunctioned },
12
+ ];
13
+
14
+ export const TransitionFilter = (props: {
15
+ group: AwesomeFilterGroupSimple;
16
+ onChange: (group: AwesomeFilterGroupSimple) => void;
17
+ }) => {
18
+ const { group, onChange } = props;
19
+ const { t } = useI18n("filters");
20
+ const options = useMemo(
21
+ () =>
22
+ transitionOptions.map((option) => ({
23
+ ...option,
24
+ label: t(option.key),
25
+ description: t(`description.${option.key}`),
26
+ })),
27
+ [t],
28
+ );
29
+
30
+ return (
31
+ <MultipleChoiceFieldFilter
32
+ group={group}
33
+ onChange={onChange}
34
+ options={options}
35
+ label={t("transition")}
36
+ fieldKey="transition"
37
+ logicalOperator="OR"
38
+ strict
39
+ counter
40
+ testId="transition-filter"
41
+ onClear={() =>
42
+ onChange({
43
+ ...group,
44
+ value: [],
45
+ })
46
+ }
47
+ />
48
+ );
49
+ };
@@ -0,0 +1,44 @@
1
+ import { For } from "@preact/signals/utils";
2
+ import type { AwesomeFilter } from "@/stores/treeFilters/model";
3
+ import { setTreeFilter, treeQuickFilters } from "@/stores/treeFilters/store";
4
+ import { isFlakyFilter, isRetryFilter, isTagFilter, isTransitionFilter } from "@/stores/treeFilters/utils";
5
+ import { BooleanFieldFilter } from "./BaseFilters";
6
+ import { RetryFlakyFilter } from "./RetryFlaky";
7
+ import { TagsFilter } from "./TagsFilter";
8
+ import { TransitionFilter } from "./TransitionFilter";
9
+ import * as styles from "./styles.scss";
10
+
11
+ const Filter = (props: { filter: AwesomeFilter; onChange: (filter: AwesomeFilter) => void }) => {
12
+ const { filter, onChange } = props;
13
+ const { value: field, type } = filter;
14
+
15
+ if (isRetryFilter(filter) || isFlakyFilter(filter)) {
16
+ return <RetryFlakyFilter filter={filter} onChange={onChange} />;
17
+ }
18
+
19
+ if (isTransitionFilter(filter)) {
20
+ return <TransitionFilter group={filter} onChange={onChange} />;
21
+ }
22
+
23
+ if (type === "field" && field.type === "boolean") {
24
+ return <BooleanFieldFilter field={field} onChange={(value) => onChange({ ...filter, value })} />;
25
+ }
26
+
27
+ if (isTagFilter(filter)) {
28
+ return <TagsFilter filter={filter} onChange={onChange} />;
29
+ }
30
+
31
+ return null;
32
+ };
33
+
34
+ const QuickFilters = () => {
35
+ return <For each={treeQuickFilters}>{(filter) => <Filter filter={filter} onChange={setTreeFilter} />}</For>;
36
+ };
37
+
38
+ export const ReportFilters = () => {
39
+ return (
40
+ <div className={styles.wrapper}>
41
+ <QuickFilters />
42
+ </div>
43
+ );
44
+ };
@@ -0,0 +1,55 @@
1
+ .wrapper {
2
+ display: flex;
3
+ flex-direction: row;
4
+ column-gap: 8px;
5
+ row-gap: 12px;
6
+ flex-wrap: wrap;
7
+ align-items: center;
8
+ flex-grow: 1;
9
+ width: 100%;
10
+ }
11
+
12
+ .btnWrapper {
13
+ position: relative;
14
+
15
+ &:hover,
16
+ &:focus-within {
17
+ & .clear {
18
+ opacity: 1;
19
+ }
20
+ }
21
+ }
22
+
23
+ .clear {
24
+ transition: opacity var(--color-change-transition-duration) ease-out;
25
+ opacity: 0;
26
+ position: absolute;
27
+ top: -9.5px;
28
+ right: -9.5px;
29
+ }
30
+
31
+ .tooltip {
32
+ opacity: 0;
33
+ pointer-events: none;
34
+ user-select: none;
35
+ transition: opacity 0.2s ease-in-out;
36
+ & > div {
37
+ max-width: 300px;
38
+ min-width: 0;
39
+ text-align: center;
40
+ }
41
+
42
+ &[data-visible="true"] {
43
+ opacity: 1;
44
+ }
45
+ }
46
+
47
+ .description {
48
+ color: var(--on-text-secondary);
49
+ }
50
+
51
+ .itemContent {
52
+ display: flex;
53
+ flex-direction: column;
54
+ gap: 4px;
55
+ }
@@ -0,0 +1,29 @@
1
+ import { SearchBox } from "@allurereport/web-components";
2
+ import { useI18n } from "@/stores/locale";
3
+ import { setTreeQueryFilter, treeQueryFilterValue } from "@/stores/treeFilters/store";
4
+
5
+ const handleQuerySearch = (value: string) => {
6
+ if (!value) {
7
+ setTreeQueryFilter(undefined);
8
+ return;
9
+ }
10
+
11
+ setTreeQueryFilter(value);
12
+ };
13
+
14
+ const QuerySearch = () => {
15
+ const { t } = useI18n("search");
16
+
17
+ return (
18
+ <SearchBox
19
+ placeholder={t("search-placeholder")}
20
+ value={treeQueryFilterValue.value}
21
+ onChange={handleQuerySearch}
22
+ changeDebounce={150}
23
+ />
24
+ );
25
+ };
26
+
27
+ export const ReportSearch = () => {
28
+ return <QuerySearch />;
29
+ };
@@ -2,7 +2,7 @@ import { Text } from "@allurereport/web-components";
2
2
  import { useComputed } from "@preact/signals";
3
3
  import { type ComponentChildren } from "preact";
4
4
  import { useCallback } from "preact/hooks";
5
- import { setTreeStatus, treeStatus } from "@/stores/treeFilters";
5
+ import { setTreeStatus, treeStatus } from "@/stores/treeFilters/store";
6
6
  import type { AwesomeStatus } from "../../../types.js";
7
7
  import * as styles from "./styles.scss";
8
8
 
@@ -37,7 +37,7 @@ export const SectionPicker = () => {
37
37
  )}
38
38
  >
39
39
  <Menu.Section>
40
- {["default", ...availableSections.value].map((value) => (
40
+ {["default", ...availableSections].map((value) => (
41
41
  <Menu.ItemWithCheckmark
42
42
  onClick={() => setSection(value)}
43
43
  key={value}
@@ -1,11 +1,13 @@
1
1
  import { Loadable, PageLoader, Text } from "@allurereport/web-components";
2
+ import { computed } from "@preact/signals";
2
3
  import type { JSX } from "preact";
3
4
  import { useEffect, useRef, useState } from "preact/hooks";
4
5
  import MainReport from "@/components/MainReport";
5
6
  import SideBySide from "@/components/SideBySide";
6
7
  import TestResult from "@/components/TestResult";
7
8
  import { useI18n } from "@/stores";
8
- import { route } from "@/stores/router";
9
+ import { testResultRoute } from "@/stores/router";
10
+ import { currentTrId } from "@/stores/testResult";
9
11
  import { testResultStore } from "@/stores/testResults";
10
12
  import { treeStore } from "@/stores/tree";
11
13
  import * as styles from "./styles.scss";
@@ -28,8 +30,10 @@ const Loader = () => {
28
30
  );
29
31
  };
30
32
 
33
+ const isTestResultRoute = computed(() => testResultRoute.value.matches);
34
+
31
35
  export const SplitLayout = () => {
32
- const testResultId = route.value.params?.testResultId ?? null;
36
+ const testResultId = currentTrId.value;
33
37
  const [cachedMain, setCachedMain] = useState<JSX.Element | null>(null);
34
38
  const { t } = useI18n("controls");
35
39
  const leftSide = (
@@ -37,7 +41,7 @@ export const SplitLayout = () => {
37
41
  );
38
42
 
39
43
  const TrView = () => {
40
- return testResultId ? (
44
+ return isTestResultRoute.value ? (
41
45
  <Loadable
42
46
  source={testResultStore}
43
47
  renderLoader={() => <Loader />}
@@ -5,7 +5,7 @@ import { type FunctionalComponent } from "preact";
5
5
  import { useState } from "preact/hooks";
6
6
  import { TrError } from "@/components/TestResult/TrError";
7
7
  import { useI18n } from "@/stores";
8
- import { navigateTo, openInNewTab } from "@/stores/router";
8
+ import { navigateToTestResult, openInNewTab } from "@/stores/router";
9
9
  import { timestampToDate } from "@/utils/time";
10
10
  import type { AwesomeTestResult } from "../../../../types";
11
11
  import * as styles from "./styles.scss";
@@ -42,7 +42,7 @@ export const TrEnvironmentItem: FunctionalComponent<{
42
42
  }
43
43
 
44
44
  e.stopPropagation();
45
- navigateTo(navigateUrl);
45
+ navigateToTestResult({ testResultId: id });
46
46
  }}
47
47
  >
48
48
  <TreeItemIcon status={status} className={styles["test-result-environment-item-status"]} />
@@ -2,7 +2,7 @@ import { IconButton, SvgIcon, Text, allureIcons } from "@allurereport/web-compon
2
2
  import clsx from "clsx";
3
3
  import type { AwesomeTestResult } from "types";
4
4
  import * as styles from "@/components/TestResult/TrHeader/styles.scss";
5
- import { navigateTo } from "@/stores/router";
5
+ import { navigateToRoot } from "@/stores/router";
6
6
 
7
7
  interface TrBreadcrumbs {
8
8
  testResult?: AwesomeTestResult;
@@ -19,7 +19,7 @@ export const TrBreadcrumbs = ({ testResult }: TrBreadcrumbs) => {
19
19
  size={"s"}
20
20
  style={"ghost"}
21
21
  className={styles["test-result-breadcrumb-link"]}
22
- onClick={() => navigateTo("")}
22
+ onClick={() => navigateToRoot()}
23
23
  />
24
24
  </div>
25
25
  {Boolean(breadcrumbs?.length) &&