@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
|
@@ -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
|
+
});
|
package/src/stores/variables.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
35
|
+
...variables.peek(),
|
|
36
36
|
error: e.message,
|
|
37
37
|
loading: false,
|
|
38
38
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class AllurePersistError extends Error {
|
|
2
|
+
constructor(
|
|
3
|
+
message: string,
|
|
4
|
+
public readonly originalError?: Error,
|
|
5
|
+
) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "AllurePersistError";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get message(): string {
|
|
11
|
+
return `${this.name}: ${this.message}${this.originalError ? `\n${this.originalError.message}` : ""}`;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const persist = <T>(pair: [string, T], storage: Storage = localStorage) => {
|
|
16
|
+
const [key, value] = pair;
|
|
17
|
+
try {
|
|
18
|
+
storage.setItem(key, JSON.stringify(value));
|
|
19
|
+
} catch (e) {
|
|
20
|
+
// eslint-disable-next-line no-console
|
|
21
|
+
console.error(new AllurePersistError(`Failed to persist ${key} to ${storage.name}`, e as Error));
|
|
22
|
+
}
|
|
23
|
+
};
|
package/src/utils/tree.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RecursiveTree } from "@allurereport/web-components/global";
|
|
2
2
|
import type { AwesomeTreeLeaf } from "types";
|
|
3
3
|
|
|
4
|
-
type Localizer = (data: string) => string;
|
|
4
|
+
type Localizer = (data: string, params?: Record<string, unknown>) => string;
|
|
5
5
|
|
|
6
6
|
type Localizers = {
|
|
7
7
|
tooltip: Localizer;
|
|
@@ -9,10 +9,17 @@ type Localizers = {
|
|
|
9
9
|
|
|
10
10
|
export const createLeafLocalizer =
|
|
11
11
|
(t: Localizers) =>
|
|
12
|
-
(leaf: AwesomeTreeLeaf): AwesomeTreeLeaf =>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
(leaf: AwesomeTreeLeaf): AwesomeTreeLeaf => {
|
|
13
|
+
const tooltips = {
|
|
14
|
+
transition: t.tooltip(leaf.transition),
|
|
15
|
+
flaky: leaf.flaky && t.tooltip("flaky"),
|
|
16
|
+
retries: leaf.retriesCount && t.tooltip("retries", { count: leaf.retriesCount }),
|
|
17
|
+
};
|
|
18
|
+
return {
|
|
19
|
+
...leaf,
|
|
20
|
+
tooltips,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
16
23
|
|
|
17
24
|
export const createTreeLocalizer =
|
|
18
25
|
(t: Localizers) =>
|
package/src/utils/treeFilters.ts
CHANGED
|
@@ -11,91 +11,79 @@ import {
|
|
|
11
11
|
ordinal,
|
|
12
12
|
reverse,
|
|
13
13
|
} from "@allurereport/core-api";
|
|
14
|
-
import type {
|
|
14
|
+
import type { SortBy } from "@/stores/treeSort";
|
|
15
15
|
import type { AwesomeRecursiveTree, AwesomeTree, AwesomeTreeGroup, AwesomeTreeLeaf } from "../../types";
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
const queryMatched = !filterOptions?.query || leaf.name.toLowerCase().includes(filterOptions.query.toLowerCase());
|
|
19
|
-
const statusMatched =
|
|
20
|
-
!filterOptions?.status || filterOptions?.status === "total" || leaf.status === filterOptions.status;
|
|
21
|
-
const flakyMatched = !filterOptions?.filter?.flaky || leaf.flaky;
|
|
22
|
-
const retryMatched = !filterOptions?.filter?.retry || leaf.retry;
|
|
23
|
-
const newMatched = !filterOptions?.filter?.new || leaf.transition === "new";
|
|
24
|
-
const fixedMatched = !filterOptions?.filter?.fixed || leaf.transition === "fixed";
|
|
25
|
-
const regressedMatched = !filterOptions?.filter?.regressed || leaf.transition === "regressed";
|
|
26
|
-
const malfuctionedMatched = !filterOptions?.filter?.malfunctioned || leaf.transition === "malfunctioned";
|
|
27
|
-
|
|
28
|
-
return [
|
|
29
|
-
queryMatched,
|
|
30
|
-
statusMatched,
|
|
31
|
-
flakyMatched,
|
|
32
|
-
retryMatched,
|
|
33
|
-
newMatched,
|
|
34
|
-
fixedMatched,
|
|
35
|
-
regressedMatched,
|
|
36
|
-
malfuctionedMatched,
|
|
37
|
-
].every(Boolean);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const leafComparatorByTreeSortBy = (sortBy: TreeSortBy = "status"): Comparator<TreeLeaf<AwesomeTreeLeaf>> => {
|
|
17
|
+
const leafComparatorByTreeSortBy = (sortBy: SortBy = "status,asc"): Comparator<TreeLeaf<AwesomeTreeLeaf>> => {
|
|
41
18
|
const typedCompareBy = compareBy<TreeLeaf<AwesomeTreeLeaf>>;
|
|
42
19
|
switch (sortBy) {
|
|
43
|
-
case "order":
|
|
20
|
+
case "order,asc":
|
|
21
|
+
case "order,desc":
|
|
44
22
|
return typedCompareBy("groupOrder", ordinal());
|
|
45
|
-
case "duration":
|
|
23
|
+
case "duration,asc":
|
|
24
|
+
case "duration,desc":
|
|
46
25
|
return typedCompareBy("duration", ordinal());
|
|
47
|
-
case "
|
|
26
|
+
case "name,asc":
|
|
27
|
+
case "name,desc":
|
|
48
28
|
return typedCompareBy("name", alphabetically());
|
|
49
|
-
case "status":
|
|
29
|
+
case "status,asc":
|
|
30
|
+
case "status,desc":
|
|
50
31
|
return typedCompareBy("status", byStatus());
|
|
51
32
|
default:
|
|
52
33
|
// eslint-disable-next-line no-console
|
|
53
|
-
console.
|
|
34
|
+
console.warn(`unsupported comparator ${sortBy as string}`);
|
|
54
35
|
return () => 0;
|
|
55
36
|
}
|
|
56
37
|
};
|
|
57
38
|
|
|
58
|
-
const groupComparatorByTreeSortBy = (sortBy:
|
|
39
|
+
const groupComparatorByTreeSortBy = (sortBy: SortBy = "status,asc"): Comparator<DefaultTreeGroup> => {
|
|
59
40
|
const typedCompareBy = compareBy<DefaultTreeGroup>;
|
|
60
41
|
switch (sortBy) {
|
|
61
|
-
case "
|
|
42
|
+
case "name,desc":
|
|
43
|
+
case "name,asc":
|
|
62
44
|
return typedCompareBy("name", alphabetically());
|
|
63
|
-
case "order":
|
|
64
|
-
case "
|
|
65
|
-
case "
|
|
45
|
+
case "order,desc":
|
|
46
|
+
case "order,asc":
|
|
47
|
+
case "duration,desc":
|
|
48
|
+
case "duration,asc":
|
|
49
|
+
case "status,desc":
|
|
50
|
+
case "status,asc":
|
|
66
51
|
return typedCompareBy("statistic", byStatistic());
|
|
67
52
|
default:
|
|
68
53
|
// eslint-disable-next-line no-console
|
|
69
|
-
console.
|
|
54
|
+
console.warn(`unsupported comparator ${sortBy as string}`);
|
|
70
55
|
return () => 0;
|
|
71
56
|
}
|
|
72
57
|
};
|
|
73
58
|
|
|
74
|
-
export const leafComparator = (
|
|
75
|
-
const cmp = leafComparatorByTreeSortBy(
|
|
76
|
-
const directional =
|
|
59
|
+
export const leafComparator = (sortBy: SortBy = "status,asc"): Comparator<TreeLeaf<AwesomeTreeLeaf>> => {
|
|
60
|
+
const cmp = leafComparatorByTreeSortBy(sortBy);
|
|
61
|
+
const directional = sortBy.split(",")[1] === "asc" ? cmp : reverse(cmp);
|
|
77
62
|
// apply fallback sorting by name
|
|
78
63
|
return andThen([directional, compareBy("name", alphabetically())]);
|
|
79
64
|
};
|
|
80
65
|
|
|
81
|
-
export const groupComparator = (
|
|
82
|
-
const cmp = groupComparatorByTreeSortBy(
|
|
83
|
-
const directional =
|
|
66
|
+
export const groupComparator = (sortBy: SortBy = "status,asc"): Comparator<DefaultTreeGroup> => {
|
|
67
|
+
const cmp = groupComparatorByTreeSortBy(sortBy);
|
|
68
|
+
const directional = sortBy.split(",")[1] === "asc" ? cmp : reverse(cmp);
|
|
84
69
|
// apply fallback sorting by name
|
|
85
70
|
return andThen([directional, compareBy("name", alphabetically())]);
|
|
86
71
|
};
|
|
87
72
|
|
|
88
73
|
export const filterLeaves = (
|
|
89
|
-
|
|
74
|
+
leafIds: string[] = [],
|
|
90
75
|
leavesById: AwesomeTree["leavesById"],
|
|
91
|
-
|
|
76
|
+
filterPredicate: (item: AwesomeTreeLeaf) => boolean,
|
|
77
|
+
sortBy: SortBy = "status,asc",
|
|
92
78
|
) => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
79
|
+
let leaves = [...leafIds].map((leafId) => leavesById[leafId]);
|
|
80
|
+
|
|
81
|
+
if (filterPredicate) {
|
|
82
|
+
leaves = leaves.filter(filterPredicate);
|
|
83
|
+
}
|
|
96
84
|
|
|
97
|
-
const comparator = leafComparator(
|
|
98
|
-
return
|
|
85
|
+
const comparator = leafComparator(sortBy);
|
|
86
|
+
return leaves.sort(comparator);
|
|
99
87
|
};
|
|
100
88
|
|
|
101
89
|
/**
|
|
@@ -107,12 +95,14 @@ export const createRecursiveTree = (payload: {
|
|
|
107
95
|
group: AwesomeTreeGroup;
|
|
108
96
|
groupsById: AwesomeTree["groupsById"];
|
|
109
97
|
leavesById: AwesomeTree["leavesById"];
|
|
110
|
-
|
|
98
|
+
filterPredicate: (item: AwesomeTreeLeaf) => boolean;
|
|
99
|
+
sortBy: SortBy;
|
|
111
100
|
}): AwesomeRecursiveTree => {
|
|
112
|
-
const { group, groupsById, leavesById,
|
|
101
|
+
const { group, groupsById, leavesById, filterPredicate, sortBy } = payload;
|
|
113
102
|
const groupLeaves: string[] = group.leaves ?? [];
|
|
114
103
|
|
|
115
|
-
const leaves = filterLeaves(groupLeaves, leavesById,
|
|
104
|
+
const leaves = filterLeaves(groupLeaves, leavesById, filterPredicate, sortBy);
|
|
105
|
+
|
|
116
106
|
const trees =
|
|
117
107
|
group.groups
|
|
118
108
|
?.map((groupId) =>
|
|
@@ -120,20 +110,24 @@ export const createRecursiveTree = (payload: {
|
|
|
120
110
|
group: groupsById[groupId],
|
|
121
111
|
groupsById,
|
|
122
112
|
leavesById,
|
|
123
|
-
|
|
113
|
+
filterPredicate,
|
|
114
|
+
sortBy,
|
|
124
115
|
}),
|
|
125
116
|
)
|
|
126
117
|
?.filter((rt) => !isRecursiveTreeEmpty(rt)) ?? [];
|
|
127
118
|
|
|
128
119
|
const statistic: Statistic = emptyStatistic();
|
|
120
|
+
|
|
129
121
|
trees.forEach((rt: AwesomeRecursiveTree) => {
|
|
130
122
|
if (rt.statistic) {
|
|
131
123
|
const additional: Statistic = rt.statistic;
|
|
124
|
+
|
|
132
125
|
mergeStatistic(statistic, additional);
|
|
133
126
|
}
|
|
134
127
|
});
|
|
135
128
|
leaves.forEach((leaf) => {
|
|
136
129
|
const status: TestStatus = leaf.status;
|
|
130
|
+
|
|
137
131
|
incrementStatistic(statistic, status);
|
|
138
132
|
});
|
|
139
133
|
|
|
@@ -141,7 +135,7 @@ export const createRecursiveTree = (payload: {
|
|
|
141
135
|
...group,
|
|
142
136
|
statistic,
|
|
143
137
|
leaves,
|
|
144
|
-
trees: trees.sort(groupComparator(
|
|
138
|
+
trees: trees.sort(groupComparator(sortBy)),
|
|
145
139
|
};
|
|
146
140
|
};
|
|
147
141
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as webCommons from "@allurereport/web-commons";
|
|
2
|
+
import { signal } from "@preact/signals";
|
|
2
3
|
import { cleanup, render, screen } from "@testing-library/preact";
|
|
3
|
-
import {
|
|
4
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
5
|
import { Header } from "@/components/Header";
|
|
5
6
|
import { CiInfo } from "@/components/Header/CiInfo";
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
7
|
+
import type * as routerModule from "@/stores/router";
|
|
8
|
+
import { testResultStore } from "@/stores/testResults";
|
|
8
9
|
|
|
9
10
|
const fixtures = {
|
|
10
11
|
ci: {
|
|
@@ -12,64 +13,60 @@ const fixtures = {
|
|
|
12
13
|
},
|
|
13
14
|
};
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}));
|
|
18
|
-
vi.mock("@/stores/router", async () => {
|
|
19
|
-
const { signal } = await import("@preact/signals");
|
|
16
|
+
// Create a controllable route signal
|
|
17
|
+
const mockRouteParams = signal<{ testResultId?: string; tab?: string }>({});
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
route: signal({}),
|
|
23
|
-
};
|
|
24
|
-
});
|
|
25
|
-
vi.mock("@/stores/sections", async () => {
|
|
26
|
-
const { signal } = await import("@preact/signals");
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
availableSections: signal([]),
|
|
30
|
-
};
|
|
31
|
-
});
|
|
19
|
+
// Mock UI components to simplify tests
|
|
32
20
|
vi.mock("@/components/HeaderControls", () => ({
|
|
33
|
-
HeaderControls: () => <div data-testid="header-controls"
|
|
21
|
+
HeaderControls: () => <div data-testid="header-controls" />,
|
|
34
22
|
}));
|
|
23
|
+
|
|
35
24
|
vi.mock("@/components/SectionPicker", () => ({
|
|
36
|
-
SectionPicker: () => <div data-testid="section-picker"
|
|
25
|
+
SectionPicker: () => <div data-testid="section-picker" />,
|
|
37
26
|
}));
|
|
27
|
+
|
|
38
28
|
vi.mock("@/components/TestResult/TrHeader/TrBreadcrumbs", () => ({
|
|
39
|
-
TrBreadcrumbs: () => <div data-testid="breadcrumbs"
|
|
29
|
+
TrBreadcrumbs: () => <div data-testid="breadcrumbs" />,
|
|
40
30
|
}));
|
|
31
|
+
|
|
41
32
|
vi.mock("@/components/Header/CiInfo", () => ({
|
|
42
|
-
|
|
43
|
-
CiInfo: vi.fn().mockImplementation(() => <div data-testid="ci-info"></div>),
|
|
33
|
+
CiInfo: vi.fn().mockImplementation(() => <div data-testid="ci-info" />),
|
|
44
34
|
}));
|
|
45
35
|
|
|
36
|
+
// Mock router module with controllable testResultRoute
|
|
37
|
+
vi.mock("@/stores/router", async (importOriginal) => {
|
|
38
|
+
const actual = await importOriginal<typeof routerModule>();
|
|
39
|
+
const { computed: computedFn } = await import("@preact/signals");
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
...actual,
|
|
43
|
+
testResultRoute: computedFn(() => {
|
|
44
|
+
const params = mockRouteParams.value;
|
|
45
|
+
const hasTestResultId = params.testResultId !== undefined;
|
|
46
|
+
return {
|
|
47
|
+
matches: hasTestResultId,
|
|
48
|
+
params: params || {},
|
|
49
|
+
};
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
|
|
46
54
|
beforeEach(() => {
|
|
47
55
|
vi.clearAllMocks();
|
|
48
56
|
cleanup();
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
mockRouteParams.value = {};
|
|
58
|
+
testResultStore.value = {
|
|
59
|
+
loading: false,
|
|
60
|
+
error: undefined,
|
|
61
|
+
data: undefined,
|
|
62
|
+
};
|
|
63
|
+
vi.spyOn(webCommons, "getReportOptions").mockReturnValue({});
|
|
51
64
|
});
|
|
52
65
|
|
|
53
66
|
describe("components > Header", () => {
|
|
54
|
-
it("should render sections picker when there are sections available", () => {
|
|
55
|
-
availableSections.value = ["section1", "section2"];
|
|
56
|
-
|
|
57
|
-
render(<Header />);
|
|
58
|
-
|
|
59
|
-
expect(screen.getByTestId("section-picker")).toBeInTheDocument();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("shouldn't render sections picker when there are no sections available", () => {
|
|
63
|
-
render(<Header />);
|
|
64
|
-
|
|
65
|
-
expect(screen.queryByTestId("section-picker")).not.toBeInTheDocument();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
67
|
it("should render ci info only when testResultId route parameter doesn't exists and ci report option is available", () => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
};
|
|
72
|
-
(getReportOptions as Mock).mockReturnValue({
|
|
68
|
+
mockRouteParams.value = {};
|
|
69
|
+
vi.spyOn(webCommons, "getReportOptions").mockReturnValue({
|
|
73
70
|
ci: fixtures.ci,
|
|
74
71
|
});
|
|
75
72
|
|
|
@@ -80,12 +77,10 @@ describe("components > Header", () => {
|
|
|
80
77
|
});
|
|
81
78
|
|
|
82
79
|
it("shouldn't render ci info when testResultId route parameter exists", () => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
testResultId: "1",
|
|
86
|
-
},
|
|
80
|
+
mockRouteParams.value = {
|
|
81
|
+
testResultId: "1",
|
|
87
82
|
};
|
|
88
|
-
(getReportOptions
|
|
83
|
+
vi.spyOn(webCommons, "getReportOptions").mockReturnValue({
|
|
89
84
|
ci: fixtures.ci,
|
|
90
85
|
});
|
|
91
86
|
|
|
@@ -96,12 +91,10 @@ describe("components > Header", () => {
|
|
|
96
91
|
});
|
|
97
92
|
|
|
98
93
|
it("should render breadcrumbs when testResultId route parameter exists", () => {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
testResultId: "1",
|
|
102
|
-
},
|
|
94
|
+
mockRouteParams.value = {
|
|
95
|
+
testResultId: "1",
|
|
103
96
|
};
|
|
104
|
-
(getReportOptions
|
|
97
|
+
vi.spyOn(webCommons, "getReportOptions").mockReturnValue({
|
|
105
98
|
ci: fixtures.ci,
|
|
106
99
|
});
|
|
107
100
|
|
|
@@ -111,9 +104,7 @@ describe("components > Header", () => {
|
|
|
111
104
|
});
|
|
112
105
|
|
|
113
106
|
it("shouldn't render breadcrumbs when testResultId route parameter doesn't exists", () => {
|
|
114
|
-
|
|
115
|
-
params: {},
|
|
116
|
-
};
|
|
107
|
+
mockRouteParams.value = {};
|
|
117
108
|
|
|
118
109
|
render(<Header />);
|
|
119
110
|
|