@allurereport/web-awesome 3.0.0 → 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.
- package/dist/multi/173.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/174.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/252.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/282.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/29.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/416.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/527.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/600.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/605.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/638.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/672.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/686.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/725.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/741.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/755.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/894.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/91.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/943.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/980.app-79c65c7bff941abcbc51.js +1 -0
- package/dist/multi/app-79c65c7bff941abcbc51.js +2 -0
- package/dist/multi/{app-9931797d1602fc52db5b.js.LICENSE.txt → app-79c65c7bff941abcbc51.js.LICENSE.txt} +7 -0
- package/dist/multi/manifest.json +21 -21
- package/dist/multi/styles-9e390bad7ce54a807a8e.css +49 -0
- package/dist/single/app-3ca67f29d0f1166c08ca.js +2 -0
- package/dist/single/{app-6199dc1c2fd3bddc2526.js.LICENSE.txt → app-3ca67f29d0f1166c08ca.js.LICENSE.txt} +7 -0
- package/dist/single/manifest.json +1 -1
- package/package.json +8 -8
- package/src/assets/scss/palette.scss +102 -102
- package/src/assets/scss/vars.scss +3 -0
- package/src/components/BaseLayout/index.tsx +25 -21
- package/src/components/BaseLayout/styles.scss +1 -0
- package/src/components/Charts/index.tsx +5 -2
- package/src/components/Footer/FooterVersion.tsx +14 -8
- package/src/components/Header/index.tsx +9 -7
- package/src/components/HeaderControls/index.tsx +5 -2
- package/src/components/MainReport/styles.scss +1 -0
- package/src/components/Metadata/index.tsx +24 -7
- package/src/components/ReportBody/HeaderActions.tsx +4 -13
- package/src/components/ReportBody/SortBy.tsx +28 -17
- package/src/components/ReportBody/index.tsx +12 -17
- package/src/components/ReportBody/styles.scss +4 -1
- package/src/components/ReportFilters/BaseFilters.tsx +345 -0
- package/src/components/ReportFilters/RetryFlaky.tsx +29 -0
- package/src/components/ReportFilters/TagsFilter.tsx +41 -0
- package/src/components/ReportFilters/TransitionFilter.tsx +49 -0
- package/src/components/ReportFilters/index.tsx +44 -0
- package/src/components/ReportFilters/styles.scss +55 -0
- package/src/components/ReportSearch/index.tsx +29 -0
- package/src/components/ReportTabs/index.tsx +37 -0
- package/src/components/SectionPicker/index.tsx +1 -1
- package/src/components/SplitLayout/index.tsx +7 -5
- package/src/components/TestResult/TestStepsEmpty/index.tsx +1 -7
- package/src/components/TestResult/TrEmpty/index.tsx +1 -7
- package/src/components/TestResult/TrEnvironmentItem/index.tsx +2 -2
- package/src/components/TestResult/TrError/index.tsx +9 -2
- package/src/components/TestResult/TrHeader/TrBreadcrumbs.tsx +2 -2
- package/src/components/TestResult/TrHistory/TrHistoryItem.tsx +38 -7
- package/src/components/TestResult/TrHistory/index.tsx +18 -8
- package/src/components/TestResult/TrHistory/styles.scss +4 -7
- package/src/components/TestResult/TrInfo/styles.scss +1 -0
- package/src/components/TestResult/TrNavigation/index.tsx +109 -68
- package/src/components/TestResult/TrNavigation/styles.scss +15 -25
- package/src/components/TestResult/TrPwTraces/PwTraceButton.tsx +1 -8
- package/src/components/TestResult/TrRetriesView/TrRetriesItem.tsx +2 -3
- package/src/components/TestResult/TrRetriesView/index.tsx +4 -3
- package/src/components/TestResult/TrSteps/TrAttachment.tsx +5 -3
- package/src/components/TestResult/TrSteps/TrAttachmentInfo.tsx +10 -3
- package/src/components/TestResult/TrSteps/TrStep.tsx +3 -3
- package/src/components/TestResult/TrTabs/index.tsx +7 -23
- package/src/components/TestResult/index.tsx +9 -4
- package/src/components/TestResult/styles.scss +1 -0
- package/src/components/Tree/index.tsx +22 -25
- package/src/index.html +19 -18
- package/src/index.tsx +20 -28
- package/src/locales/az.json +42 -12
- package/src/locales/de.json +42 -12
- package/src/locales/en.json +42 -12
- package/src/locales/es.json +42 -12
- package/src/locales/fr.json +42 -12
- package/src/locales/he.json +42 -12
- package/src/locales/hy.json +42 -12
- package/src/locales/it.json +42 -12
- package/src/locales/ja.json +42 -12
- package/src/locales/ka.json +42 -12
- package/src/locales/kr.json +42 -12
- package/src/locales/nl.json +42 -12
- package/src/locales/pl.json +42 -12
- package/src/locales/pt.json +42 -12
- package/src/locales/ru.json +42 -12
- package/src/locales/sv.json +42 -12
- package/src/locales/tr.json +42 -12
- package/src/locales/ua.json +42 -12
- package/src/locales/zh.json +42 -12
- package/src/stores/chart.ts +2 -2
- package/src/stores/env.ts +6 -6
- package/src/stores/envInfo.ts +2 -2
- package/src/stores/globals.ts +1 -1
- package/src/stores/index.ts +0 -1
- package/src/stores/layout.ts +20 -11
- package/src/stores/locale.ts +2 -1
- package/src/stores/qualityGate.ts +2 -2
- package/src/stores/router.ts +25 -91
- package/src/stores/sections.ts +32 -45
- package/src/stores/stats.ts +4 -4
- package/src/stores/testResult.ts +5 -0
- package/src/stores/testResults.ts +7 -5
- package/src/stores/tree.ts +49 -126
- package/src/stores/treeFilters/actions.ts +63 -0
- package/src/stores/treeFilters/constants.ts +13 -0
- package/src/stores/treeFilters/model.ts +51 -0
- package/src/stores/treeFilters/store.ts +273 -0
- package/src/stores/treeFilters/utils.ts +132 -0
- package/src/stores/treeSort.ts +71 -0
- package/src/stores/variables.ts +3 -3
- package/src/utils/persist.ts +23 -0
- package/src/utils/tree.ts +12 -5
- package/src/utils/treeFilters.ts +48 -54
- package/test/components/Header.test.tsx +49 -58
- package/test/utils/treeFilters.test.ts +18 -176
- package/types.d.ts +4 -1
- package/dist/multi/173.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/174.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/252.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/282.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/29.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/416.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/527.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/600.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/605.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/638.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/672.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/686.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/725.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/741.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/755.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/894.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/91.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/943.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/980.app-9931797d1602fc52db5b.js +0 -1
- package/dist/multi/app-9931797d1602fc52db5b.js +0 -2
- package/dist/multi/styles-8fe37354d1c2270c691e.css +0 -48
- package/dist/single/app-6199dc1c2fd3bddc2526.js +0 -2
- package/src/components/ReportBody/Filters.tsx +0 -71
- package/src/components/Tabs/index.tsx +0 -62
- package/src/stores/theme.ts +0 -30
- /package/src/components/{Tabs → ReportTabs}/styles.scss +0 -0
package/src/stores/stats.ts
CHANGED
|
@@ -20,7 +20,7 @@ export const statsByEnvStore = signal<StoreSignalState<Record<string, Statistic>
|
|
|
20
20
|
|
|
21
21
|
export const fetchReportStats = async () => {
|
|
22
22
|
reportStatsStore.value = {
|
|
23
|
-
...reportStatsStore.
|
|
23
|
+
...reportStatsStore.peek(),
|
|
24
24
|
loading: true,
|
|
25
25
|
error: undefined,
|
|
26
26
|
};
|
|
@@ -43,7 +43,7 @@ export const fetchReportStats = async () => {
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
export const fetchEnvStats = async (envs: string[]) => {
|
|
46
|
-
const envsToFetch = envs.filter((env) => !statsByEnvStore.
|
|
46
|
+
const envsToFetch = envs.filter((env) => !statsByEnvStore.peek().data?.[env]);
|
|
47
47
|
|
|
48
48
|
// all envs have already been fetched
|
|
49
49
|
if (envsToFetch.length === 0) {
|
|
@@ -51,7 +51,7 @@ export const fetchEnvStats = async (envs: string[]) => {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
statsByEnvStore.value = {
|
|
54
|
-
...statsByEnvStore.
|
|
54
|
+
...statsByEnvStore.peek(),
|
|
55
55
|
loading: true,
|
|
56
56
|
error: undefined,
|
|
57
57
|
};
|
|
@@ -73,7 +73,7 @@ export const fetchEnvStats = async (envs: string[]) => {
|
|
|
73
73
|
};
|
|
74
74
|
} catch (err) {
|
|
75
75
|
statsByEnvStore.value = {
|
|
76
|
-
...statsByEnvStore.
|
|
76
|
+
...statsByEnvStore.peek(),
|
|
77
77
|
error: err.message,
|
|
78
78
|
loading: false,
|
|
79
79
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { computed } from "@preact/signals";
|
|
2
|
+
import { testResultRoute } from "./router";
|
|
3
|
+
|
|
4
|
+
export const trCurrentTab = computed(() => testResultRoute.value.params.tab ?? "overview");
|
|
5
|
+
export const currentTrId = computed(() => testResultRoute.value.params.testResultId);
|
|
@@ -32,7 +32,7 @@ export const fetchTestResultNav = async (env?: string) => {
|
|
|
32
32
|
};
|
|
33
33
|
} catch (err) {
|
|
34
34
|
testResultNavStore.value = {
|
|
35
|
-
...testResultNavStore.
|
|
35
|
+
...testResultNavStore.peek(),
|
|
36
36
|
error: err.message,
|
|
37
37
|
loading: false,
|
|
38
38
|
};
|
|
@@ -40,12 +40,14 @@ export const fetchTestResultNav = async (env?: string) => {
|
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
export const fetchTestResult = async (testResultId: string) => {
|
|
43
|
-
|
|
43
|
+
const trData = testResultStore.peek().data;
|
|
44
|
+
|
|
45
|
+
if (!testResultId || (trData && testResultId in trData)) {
|
|
44
46
|
return;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
testResultStore.value = {
|
|
48
|
-
...testResultStore.
|
|
50
|
+
...testResultStore.peek(),
|
|
49
51
|
loading: true,
|
|
50
52
|
error: undefined,
|
|
51
53
|
};
|
|
@@ -56,13 +58,13 @@ export const fetchTestResult = async (testResultId: string) => {
|
|
|
56
58
|
});
|
|
57
59
|
|
|
58
60
|
testResultStore.value = {
|
|
59
|
-
data: { ...testResultStore.
|
|
61
|
+
data: { ...testResultStore.peek().data, [testResultId]: data },
|
|
60
62
|
error: undefined,
|
|
61
63
|
loading: false,
|
|
62
64
|
};
|
|
63
65
|
} catch (err) {
|
|
64
66
|
testResultStore.value = {
|
|
65
|
-
...testResultStore.
|
|
67
|
+
...testResultStore.peek(),
|
|
66
68
|
error: err.message,
|
|
67
69
|
loading: false,
|
|
68
70
|
};
|
package/src/stores/tree.ts
CHANGED
|
@@ -1,21 +1,12 @@
|
|
|
1
|
-
import { fetchReportJsonData } from "@allurereport/web-commons";
|
|
1
|
+
import { buildFilterPredicate, fetchReportJsonData } from "@allurereport/web-commons";
|
|
2
2
|
import type { RecursiveTree } from "@allurereport/web-components/global";
|
|
3
3
|
import { computed, effect, signal } from "@preact/signals";
|
|
4
|
-
import type {
|
|
4
|
+
import type { AwesomeTree, AwesomeTreeGroup } from "types";
|
|
5
5
|
import type { StoreSignalState } from "@/stores/types";
|
|
6
6
|
import { loadFromLocalStorage } from "@/utils/loadFromLocalStorage";
|
|
7
7
|
import { createRecursiveTree, isRecursiveTreeEmpty } from "@/utils/treeFilters";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export type TreeDirection = "asc" | "desc";
|
|
11
|
-
export type TreeFilters = "flaky" | "retry" | "new" | "fixed" | "regressed" | "malfunctioned";
|
|
12
|
-
export type TreeFiltersState = {
|
|
13
|
-
query: string;
|
|
14
|
-
status: AwesomeStatus;
|
|
15
|
-
filter: Record<TreeFilters, boolean>;
|
|
16
|
-
sortBy: TreeSortBy;
|
|
17
|
-
direction: TreeDirection;
|
|
18
|
-
};
|
|
8
|
+
import { treeFilters } from "./treeFilters/store";
|
|
9
|
+
import { sortBy } from "./treeSort";
|
|
19
10
|
|
|
20
11
|
export const treeStore = signal<StoreSignalState<Record<string, AwesomeTree>>>({
|
|
21
12
|
loading: true,
|
|
@@ -45,117 +36,8 @@ export const toggleTree = (id: string) => {
|
|
|
45
36
|
collapsedTrees.value = newSet;
|
|
46
37
|
};
|
|
47
38
|
|
|
48
|
-
export const selectedFilters = signal(new Set(loadFromLocalStorage("selectedFilters", []) as []));
|
|
49
|
-
|
|
50
|
-
effect(() => {
|
|
51
|
-
localStorage.setItem("selectedFilters", JSON.stringify([...selectedFilters.value]));
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
export const treeFiltersStore = signal<TreeFiltersState>(
|
|
55
|
-
loadFromLocalStorage<TreeFiltersState>("treeFilters", {
|
|
56
|
-
query: "",
|
|
57
|
-
status: "total",
|
|
58
|
-
filter: {
|
|
59
|
-
flaky: false,
|
|
60
|
-
retry: false,
|
|
61
|
-
new: false,
|
|
62
|
-
fixed: false,
|
|
63
|
-
regressed: false,
|
|
64
|
-
malfunctioned: false,
|
|
65
|
-
},
|
|
66
|
-
sortBy: "order",
|
|
67
|
-
direction: "asc",
|
|
68
|
-
}) as TreeFiltersState,
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
effect(() => {
|
|
72
|
-
localStorage.setItem("treeFilters", JSON.stringify(treeFiltersStore.value));
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
export const filteredTree = computed(() => {
|
|
76
|
-
return Object.entries(treeStore.value.data).reduce(
|
|
77
|
-
(acc, [key, value]) => {
|
|
78
|
-
if (!value) {
|
|
79
|
-
return acc;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const { root, leavesById, groupsById } = value;
|
|
83
|
-
const tree = createRecursiveTree({
|
|
84
|
-
group: root as AwesomeTreeGroup,
|
|
85
|
-
leavesById,
|
|
86
|
-
groupsById,
|
|
87
|
-
filterOptions: treeFiltersStore.value,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
return Object.assign(acc, {
|
|
91
|
-
[key]: tree,
|
|
92
|
-
});
|
|
93
|
-
},
|
|
94
|
-
{} as Record<string, RecursiveTree>,
|
|
95
|
-
);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
export const noTestsFound = computed(() => {
|
|
99
|
-
return Object.values(filteredTree.value).every(isRecursiveTreeEmpty);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
export const clearTreeFilters = () => {
|
|
103
|
-
treeFiltersStore.value = {
|
|
104
|
-
query: "",
|
|
105
|
-
status: "total",
|
|
106
|
-
filter: {
|
|
107
|
-
flaky: false,
|
|
108
|
-
retry: false,
|
|
109
|
-
new: false,
|
|
110
|
-
fixed: false,
|
|
111
|
-
regressed: false,
|
|
112
|
-
malfunctioned: false,
|
|
113
|
-
},
|
|
114
|
-
sortBy: "order",
|
|
115
|
-
direction: "asc",
|
|
116
|
-
};
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
export const setTreeQuery = (query: string) => {
|
|
120
|
-
treeFiltersStore.value = {
|
|
121
|
-
...treeFiltersStore.value,
|
|
122
|
-
query,
|
|
123
|
-
};
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
export const setTreeStatus = (status: AwesomeStatus) => {
|
|
127
|
-
treeFiltersStore.value = {
|
|
128
|
-
...treeFiltersStore.value,
|
|
129
|
-
status,
|
|
130
|
-
};
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
export const setTreeSortBy = (sortBy: TreeSortBy) => {
|
|
134
|
-
treeFiltersStore.value = {
|
|
135
|
-
...treeFiltersStore.value,
|
|
136
|
-
sortBy,
|
|
137
|
-
};
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
export const setTreeDirection = (direction: TreeDirection) => {
|
|
141
|
-
treeFiltersStore.value = {
|
|
142
|
-
...treeFiltersStore.value,
|
|
143
|
-
direction,
|
|
144
|
-
};
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
export const setTreeFilter = (filterKey: TreeFilters, value: boolean) => {
|
|
148
|
-
treeFiltersStore.value = {
|
|
149
|
-
...treeFiltersStore.value,
|
|
150
|
-
filter: {
|
|
151
|
-
...treeFiltersStore.value.filter,
|
|
152
|
-
[filterKey]: value,
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
};
|
|
156
|
-
|
|
157
39
|
export const fetchEnvTreesData = async (envs: string[]) => {
|
|
158
|
-
const envsToFetch = envs.filter((env) => !treeStore.
|
|
40
|
+
const envsToFetch = envs.filter((env) => !treeStore.peek().data?.[env]);
|
|
159
41
|
|
|
160
42
|
// all envs have already been fetched
|
|
161
43
|
if (envsToFetch.length === 0) {
|
|
@@ -163,7 +45,7 @@ export const fetchEnvTreesData = async (envs: string[]) => {
|
|
|
163
45
|
}
|
|
164
46
|
|
|
165
47
|
treeStore.value = {
|
|
166
|
-
...treeStore.
|
|
48
|
+
...treeStore.peek(),
|
|
167
49
|
loading: true,
|
|
168
50
|
error: undefined,
|
|
169
51
|
};
|
|
@@ -173,7 +55,7 @@ export const fetchEnvTreesData = async (envs: string[]) => {
|
|
|
173
55
|
envsToFetch.map((env) => fetchReportJsonData<AwesomeTree>(`widgets/${env}/tree.json`, { bustCache: true })),
|
|
174
56
|
);
|
|
175
57
|
|
|
176
|
-
const previous = treeStore.
|
|
58
|
+
const previous = treeStore.peek().data;
|
|
177
59
|
treeStore.value = {
|
|
178
60
|
data: envsToFetch.reduce(
|
|
179
61
|
(acc, env, index) => {
|
|
@@ -189,9 +71,50 @@ export const fetchEnvTreesData = async (envs: string[]) => {
|
|
|
189
71
|
};
|
|
190
72
|
} catch (e) {
|
|
191
73
|
treeStore.value = {
|
|
192
|
-
...treeStore.
|
|
74
|
+
...treeStore.peek(),
|
|
193
75
|
error: e.message,
|
|
194
76
|
loading: false,
|
|
195
77
|
};
|
|
196
78
|
}
|
|
197
79
|
};
|
|
80
|
+
|
|
81
|
+
const treeEntries = computed(() => (treeStore.value.data ? Object.entries(treeStore.value.data) : []));
|
|
82
|
+
|
|
83
|
+
const alwaysTruePredicate = () => true;
|
|
84
|
+
|
|
85
|
+
const filterPredicate = computed(() => {
|
|
86
|
+
if (treeFilters.value.length === 0) {
|
|
87
|
+
return alwaysTruePredicate;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return buildFilterPredicate(treeFilters.value);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
export const filteredTree = computed(() => {
|
|
94
|
+
return treeEntries.value.reduce(
|
|
95
|
+
(acc, [key, value]) => {
|
|
96
|
+
if (!value) {
|
|
97
|
+
return acc;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const { root, leavesById, groupsById } = value;
|
|
101
|
+
|
|
102
|
+
const tree = createRecursiveTree({
|
|
103
|
+
group: root as AwesomeTreeGroup,
|
|
104
|
+
leavesById,
|
|
105
|
+
groupsById,
|
|
106
|
+
filterPredicate: filterPredicate.value,
|
|
107
|
+
sortBy: sortBy.value,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return Object.assign(acc, {
|
|
111
|
+
[key]: tree,
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
{} as Record<string, RecursiveTree>,
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
export const noTestsFound = computed(
|
|
119
|
+
() => !Object.values(filteredTree.value).some((tree) => !isRecursiveTreeEmpty(tree)),
|
|
120
|
+
);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { TestStatus, TestStatusTransition } from "@allurereport/core-api";
|
|
2
|
+
import { ReportFetchError, fetchReportJsonData, setParams } from "@allurereport/web-commons";
|
|
3
|
+
import { PARAMS } from "./constants";
|
|
4
|
+
import type { TreeFiltersData } from "./model";
|
|
5
|
+
import { treeTags } from "./store";
|
|
6
|
+
|
|
7
|
+
export const setQueryFilter = (query?: string) => {
|
|
8
|
+
setParams({
|
|
9
|
+
key: PARAMS.QUERY,
|
|
10
|
+
value: query?.trim() === "" ? undefined : query,
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const setStatusFilter = (status?: TestStatus) => {
|
|
15
|
+
setParams({
|
|
16
|
+
key: PARAMS.STATUS,
|
|
17
|
+
value: status,
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const setFlakyFilter = (flaky?: boolean) => {
|
|
22
|
+
setParams({
|
|
23
|
+
key: PARAMS.FLAKY,
|
|
24
|
+
value: flaky ? "true" : undefined,
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const setRetryFilter = (retry?: boolean) => {
|
|
29
|
+
setParams({
|
|
30
|
+
key: PARAMS.RETRY,
|
|
31
|
+
value: retry ? "true" : undefined,
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const setTransitionFilter = (transitions: TestStatusTransition[]) => {
|
|
36
|
+
setParams({
|
|
37
|
+
key: PARAMS.TRANSITION,
|
|
38
|
+
value: transitions,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const setTagsFilter = (tags: string[]) => {
|
|
43
|
+
setParams({
|
|
44
|
+
key: PARAMS.TAGS,
|
|
45
|
+
value: tags,
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const fetchTreeFiltersData = async () => {
|
|
50
|
+
try {
|
|
51
|
+
const response = await fetchReportJsonData<TreeFiltersData>("widgets/tree-filters.json", { bustCache: true });
|
|
52
|
+
|
|
53
|
+
treeTags.value = response.tags;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error instanceof ReportFetchError && error.response.status === 404) {
|
|
56
|
+
treeTags.value = [];
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// eslint-disable-next-line no-console
|
|
61
|
+
console.error("Failed to fetch tree filters data:\n\n", error);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TestStatus, TestStatusTransition } from "@allurereport/core-api";
|
|
2
|
+
|
|
3
|
+
export const TRANSITIONS: TestStatusTransition[] = ["new", "fixed", "regressed", "malfunctioned"];
|
|
4
|
+
export const STATUSES: TestStatus[] = ["passed", "failed", "skipped", "broken", "unknown"];
|
|
5
|
+
|
|
6
|
+
export const PARAMS = {
|
|
7
|
+
QUERY: "query",
|
|
8
|
+
STATUS: "status",
|
|
9
|
+
FLAKY: "flaky",
|
|
10
|
+
RETRY: "retry",
|
|
11
|
+
TRANSITION: "transition",
|
|
12
|
+
TAGS: "tags",
|
|
13
|
+
} as const;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { TestStatus, TestStatusTransition } from "@allurereport/core-api";
|
|
2
|
+
import type {
|
|
3
|
+
ArrayField,
|
|
4
|
+
BooleanField,
|
|
5
|
+
Field,
|
|
6
|
+
FieldFilter,
|
|
7
|
+
FieldFilterGroup,
|
|
8
|
+
StringField,
|
|
9
|
+
} from "@allurereport/web-commons";
|
|
10
|
+
import type { AwesomeTreeLeaf } from "types";
|
|
11
|
+
|
|
12
|
+
export type Filters = {
|
|
13
|
+
query?: string;
|
|
14
|
+
status?: TestStatus;
|
|
15
|
+
flaky?: boolean;
|
|
16
|
+
retry?: boolean;
|
|
17
|
+
transition?: TestStatusTransition[];
|
|
18
|
+
tags?: string[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type AwesomeFieldFilter = FieldFilter<keyof AwesomeTreeLeaf>;
|
|
22
|
+
|
|
23
|
+
export type AwesomeFieldFilterGroup = FieldFilterGroup<keyof AwesomeTreeLeaf> & {
|
|
24
|
+
fieldKey?: keyof AwesomeTreeLeaf;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type AwesomeFilterGroupSimple = AwesomeFieldFilterGroup & {
|
|
28
|
+
value: AwesomeFieldFilter[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type AwesomeFilter = AwesomeFieldFilter | AwesomeFilterGroupSimple;
|
|
32
|
+
|
|
33
|
+
export type AwesomeField = Field<keyof AwesomeTreeLeaf>;
|
|
34
|
+
|
|
35
|
+
export type AwesomeBooleanField = BooleanField<keyof AwesomeTreeLeaf>;
|
|
36
|
+
|
|
37
|
+
export type AwesomeStringFieldFilter = AwesomeFieldFilter & {
|
|
38
|
+
value: StringField<keyof AwesomeTreeLeaf>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type AwesomeArrayFieldFilter = AwesomeFieldFilter & {
|
|
42
|
+
value: ArrayField<keyof AwesomeTreeLeaf>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type AwesomeBooleanFieldFilter = AwesomeFieldFilter & {
|
|
46
|
+
value: AwesomeBooleanField;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type TreeFiltersData = {
|
|
50
|
+
tags: string[];
|
|
51
|
+
};
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import type { TestStatus, TestStatusTransition } from "@allurereport/core-api";
|
|
2
|
+
import { getParamValue, getParamValues } from "@allurereport/web-commons";
|
|
3
|
+
import { computed, signal } from "@preact/signals";
|
|
4
|
+
import type { AwesomeStatus } from "types";
|
|
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;
|
|
42
|
+
});
|
|
43
|
+
|
|
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);
|
|
129
|
+
};
|
|
130
|
+
|
|
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
|
+
}));
|
|
168
|
+
|
|
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
|
+
},
|
|
178
|
+
}));
|
|
179
|
+
|
|
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);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return filters;
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
export const setTreeFilter = (filter: AwesomeFilter) => {
|
|
231
|
+
if (isTransitionFilter(filter)) {
|
|
232
|
+
const transitions: TestStatusTransition[] = [];
|
|
233
|
+
|
|
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
|
+
};
|