@allurereport/web-awesome 3.8.2 → 3.10.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/README.md +112 -0
- package/allurerc-dev.mjs +10 -0
- package/dist/multi/121.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/173.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/174.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/252.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/282.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/29.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/310.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/416.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/507.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/527.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/600.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/605.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/638.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/672.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/686.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/725.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/741.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/749.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/755.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/779.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/894.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/943.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/980.app-b18cce138691927e8759.js +1 -0
- package/dist/multi/app-b18cce138691927e8759.js +2 -0
- package/dist/multi/manifest.json +26 -23
- package/dist/multi/styles-212da6c68fa0beb4c6c5.css +1 -0
- package/dist/multi/styles-5c882b14b6f3112e40c4.css +1 -0
- package/dist/multi/styles-a4f65de86208f79dd2be.css +58 -0
- package/dist/single/app-733f473da7b51f98876d.js +2 -0
- package/dist/single/manifest.json +1 -1
- package/package.json +19 -14
- package/src/assets/scss/_common.scss +2 -2
- package/src/assets/scss/index.scss +8 -6
- package/src/components/BaseLayout/index.tsx +14 -2
- package/src/components/BaseLayout/styles.scss +5 -5
- package/src/components/Categories/CategoryHeaderItem/styles.scss +2 -2
- package/src/components/Categories/CategoryTreeItem/styles.scss +2 -2
- package/src/components/Categories/GroupTreeItem/styles.scss +4 -5
- package/src/components/Categories/HistoryTreeItem/styles.scss +2 -2
- package/src/components/Categories/LabelTreeItem/styles.scss +2 -2
- package/src/components/Categories/MessageTreeItem/index.tsx +1 -1
- package/src/components/Categories/MessageTreeItem/styles.scss +18 -18
- package/src/components/Categories/sticky.ts +1 -1
- package/src/components/Footer/FooterVersion.tsx +5 -10
- package/src/components/Footer/index.tsx +7 -1
- package/src/components/Footer/styles.scss +8 -2
- package/src/components/Header/CiInfo/index.tsx +17 -13
- package/src/components/Header/CiInfo/styles.scss +1 -1
- package/src/components/Header/styles.scss +2 -2
- package/src/components/HeaderControls/index.tsx +1 -3
- package/src/components/HotkeysProvider/index.tsx +556 -0
- package/src/components/KeyboardShortcuts/index.tsx +73 -0
- package/src/components/KeyboardShortcuts/shortcutsConfig.ts +91 -0
- package/src/components/KeyboardShortcuts/styles.scss +69 -0
- package/src/components/MainReport/index.tsx +89 -72
- package/src/components/MainReport/styles.scss +20 -5
- package/src/components/Metadata/index.tsx +27 -6
- package/src/components/Metadata/styles.scss +21 -9
- package/src/components/MetadataButton/index.tsx +2 -0
- package/src/components/MetadataButton/styles.scss +1 -1
- package/src/components/NavTabs/styles.scss +8 -8
- package/src/components/ReportBody/styles.scss +3 -4
- package/src/components/ReportCategories/styles.scss +1 -1
- package/src/components/ReportFilters/styles.scss +1 -1
- package/src/components/ReportGlobalAttachments/styles.scss +1 -1
- package/src/components/ReportGlobalErrors/styles.scss +1 -1
- package/src/components/ReportHeader/index.tsx +25 -13
- package/src/components/ReportHeader/styles.scss +2 -2
- package/src/components/ReportMetadata/index.tsx +44 -15
- package/src/components/ReportMetadata/styles.scss +6 -6
- package/src/components/ReportQualityGateResults/styles.scss +2 -2
- package/src/components/ReportSearch/index.tsx +1 -5
- package/src/components/ReportTabs/styles.scss +9 -9
- package/src/components/SectionSwitcher/index.tsx +87 -10
- package/src/components/SideBySide/index.tsx +20 -2
- package/src/components/SideBySide/styles.scss +9 -1
- package/src/components/SplitLayout/index.tsx +11 -2
- package/src/components/SplitLayout/styles.scss +23 -4
- package/src/components/TestResult/TestStepsEmpty/styles.scss +1 -1
- package/src/components/TestResult/TrDescription/styles.scss +1 -1
- package/src/components/TestResult/TrDropdown/index.tsx +2 -2
- package/src/components/TestResult/TrDropdown/styles.scss +1 -1
- package/src/components/TestResult/TrEmpty/styles.scss +1 -1
- package/src/components/TestResult/TrEnvironmentItem/styles.scss +4 -4
- package/src/components/TestResult/TrError/index.tsx +32 -7
- package/src/components/TestResult/TrError/styles.scss +23 -23
- package/src/components/TestResult/TrHeader/styles.scss +2 -2
- package/src/components/TestResult/TrHistory/styles.scss +6 -6
- package/src/components/TestResult/TrInfo/styles.scss +8 -8
- package/src/components/TestResult/TrLinks/index.tsx +2 -2
- package/src/components/TestResult/TrLinks/styles.scss +2 -2
- package/src/components/TestResult/TrMetadata/index.tsx +1 -1
- package/src/components/TestResult/TrMetadata/styles.scss +1 -1
- package/src/components/TestResult/TrNavigation/index.tsx +1 -1
- package/src/components/TestResult/TrNavigation/styles.scss +2 -2
- package/src/components/TestResult/TrOverview.tsx +2 -0
- package/src/components/TestResult/TrParameters/index.tsx +1 -1
- package/src/components/TestResult/TrParameters/styles.scss +1 -1
- package/src/components/TestResult/TrPrevStatuses/styles.scss +8 -8
- package/src/components/TestResult/TrPwTraces/styles.scss +1 -1
- package/src/components/TestResult/TrRetriesView/TrRetriesItem.tsx +27 -1
- package/src/components/TestResult/TrRetriesView/styles.scss +20 -10
- package/src/components/TestResult/TrSetup/index.tsx +10 -4
- package/src/components/TestResult/TrSeverity/styles.scss +7 -7
- package/src/components/TestResult/TrStatus/styles.scss +2 -35
- package/src/components/TestResult/TrSteps/TrAttachment.tsx +79 -43
- package/src/components/TestResult/TrSteps/TrAttachmentInfo.tsx +44 -17
- package/src/components/TestResult/TrSteps/TrBodyItems.tsx +5 -2
- package/src/components/TestResult/TrSteps/TrErrorStep.tsx +3 -0
- package/src/components/TestResult/TrSteps/TrStep.tsx +15 -6
- package/src/components/TestResult/TrSteps/TrStepHeader.tsx +8 -5
- package/src/components/TestResult/TrSteps/index.tsx +7 -5
- package/src/components/TestResult/TrSteps/stepTreeExpansion.ts +27 -9
- package/src/components/TestResult/TrSteps/styles.scss +80 -20
- package/src/components/TestResult/TrTeardown/index.tsx +10 -4
- package/src/components/TestResult/bodyItems.ts +1 -1
- package/src/components/TestResult/index.tsx +8 -2
- package/src/components/TestResult/styles.scss +10 -1
- package/src/components/TestResult/trOverviewFocus.scss +4 -0
- package/src/components/Timeline/styles.scss +6 -6
- package/src/components/Tree/index.tsx +79 -5
- package/src/components/Tree/styles.scss +55 -35
- package/src/hooks/useTestResultOverviewFocusScroll.ts +23 -0
- package/src/index.html +30 -33
- package/src/index.tsx +12 -6
- package/src/locales/ar.json +62 -1
- package/src/locales/az.json +62 -1
- package/src/locales/de.json +62 -1
- package/src/locales/en.json +62 -1
- package/src/locales/es.json +62 -1
- package/src/locales/fr.json +62 -1
- package/src/locales/he.json +62 -1
- package/src/locales/hy.json +62 -1
- package/src/locales/it.json +62 -1
- package/src/locales/ja.json +62 -1
- package/src/locales/ka.json +62 -1
- package/src/locales/kr.json +62 -1
- package/src/locales/nl.json +62 -1
- package/src/locales/pl.json +62 -1
- package/src/locales/pt.json +62 -1
- package/src/locales/ru.json +62 -1
- package/src/locales/sv.json +62 -1
- package/src/locales/tr.json +62 -1
- package/src/locales/uk.json +62 -1
- package/src/locales/zh-TW.json +62 -1
- package/src/locales/zh.json +62 -1
- package/src/stores/keyboard.ts +371 -0
- package/src/stores/keyboardActions.ts +769 -0
- package/src/stores/locale.ts +5 -2
- package/src/stores/reportEnvSections.ts +6 -0
- package/src/stores/reportRootTabs.ts +95 -0
- package/src/stores/search.ts +147 -0
- package/src/stores/testResultOverviewNav.ts +119 -0
- package/src/stores/testResultTabs.ts +62 -0
- package/src/stores/timeline.ts +1 -1
- package/src/stores/tree.ts +42 -4
- package/src/stores/treeFilters/store.ts +3 -36
- package/src/stores/treeSort.ts +7 -1
- package/src/styles/_pane-active.scss +8 -0
- package/src/styles.scss +1 -1
- package/src/utils/atSeparator.ts +4 -0
- package/src/utils/flattenTestResultOverview.ts +182 -0
- package/src/utils/time.ts +2 -1
- package/src/utils/trOverviewFocus.ts +18 -0
- package/src/utils/treeFilters.ts +15 -4
- package/test/components/EnvironmentPicker.test.tsx +21 -3
- package/test/components/Footer.test.tsx +26 -0
- package/test/components/Header/CiInfo.test.tsx +56 -0
- package/test/components/Header.test.tsx +8 -0
- package/test/components/HeaderControls.test.tsx +28 -0
- package/test/components/ReportGlobals.test.tsx +9 -1
- package/test/components/ReportHeader.test.tsx +77 -0
- package/test/components/ReportMetadata.test.tsx +131 -0
- package/test/components/TestResult/PwTraceButton.test.tsx +8 -0
- package/test/components/TestResult/TrErrorStep.test.tsx +8 -0
- package/test/components/TestResult/TrOverview.test.tsx +30 -10
- package/test/components/TestResult/TrRetriesItem.test.tsx +163 -0
- package/test/components/TestResult/TrSteps.test.tsx +108 -0
- package/test/components/TestResult/bodyItems.test.ts +9 -1
- package/test/components/TestResult/openPwTraceInNewTab.test.ts +8 -0
- package/test/components/TestResult/stepTreeExpansion.test.ts +10 -2
- package/test/components/Timeline.test.tsx +15 -7
- package/test/stores/keyboard/keyboardActions.test.ts +615 -0
- package/test/stores/search.test.ts +143 -0
- package/test/stores/treeFilters/actions.test.ts +8 -0
- package/test/stores/treeSort.test.ts +58 -0
- package/test/utils/flattenTestResultOverview.test.ts +57 -0
- package/test/utils/ownerAddress.test.ts +9 -1
- package/test/utils/time.test.ts +52 -0
- package/test/utils/treeFilters.test.ts +113 -1
- package/types.d.ts +39 -0
- package/webpack.config.js +12 -7
- package/CONTRIBUTING.md +0 -34
- package/dist/multi/173.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/174.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/252.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/282.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/29.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/310.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/416.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/507.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/527.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/600.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/605.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/638.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/672.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/686.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/725.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/741.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/749.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/755.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/894.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/943.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/980.app-f008fb8342025f2b1ace.js +0 -1
- package/dist/multi/app-f008fb8342025f2b1ace.js +0 -2
- package/dist/multi/styles-9f7a23a0c8b79fa76981.css +0 -58
- package/dist/single/app-07332238da9897064301.js +0 -2
- package/src/assets/scss/day.scss +0 -53
- package/src/assets/scss/fonts.scss +0 -3
- package/src/assets/scss/night.scss +0 -63
- package/src/assets/scss/palette.scss +0 -393
- package/src/assets/scss/theme.scss +0 -330
- package/src/assets/scss/vars.scss +0 -11
- /package/dist/multi/{app-f008fb8342025f2b1ace.js.LICENSE.txt → app-b18cce138691927e8759.js.LICENSE.txt} +0 -0
- /package/dist/single/{app-07332238da9897064301.js.LICENSE.txt → app-733f473da7b51f98876d.js.LICENSE.txt} +0 -0
package/src/stores/locale.ts
CHANGED
|
@@ -10,6 +10,8 @@ import { computed, signal } from "@preact/signals";
|
|
|
10
10
|
import i18next, { type TOptions } from "i18next";
|
|
11
11
|
import type { AwesomeReportOptions } from "types";
|
|
12
12
|
|
|
13
|
+
import { ensureAtSeparator } from "@/utils/atSeparator";
|
|
14
|
+
|
|
13
15
|
const namespaces = [
|
|
14
16
|
"empty",
|
|
15
17
|
"execution",
|
|
@@ -25,6 +27,7 @@ const namespaces = [
|
|
|
25
27
|
"ui",
|
|
26
28
|
"welcome",
|
|
27
29
|
"controls",
|
|
30
|
+
"shortcuts",
|
|
28
31
|
"errors",
|
|
29
32
|
"split",
|
|
30
33
|
"modal",
|
|
@@ -111,7 +114,7 @@ export const waitForI18next = i18next
|
|
|
111
114
|
if (override?.includeAtSeparator === false || override?.stripComma) {
|
|
112
115
|
return formatted.replace(",", "");
|
|
113
116
|
}
|
|
114
|
-
return formatted
|
|
117
|
+
return ensureAtSeparator(formatted, i18next.t("ui:at"));
|
|
115
118
|
},
|
|
116
119
|
);
|
|
117
120
|
i18next.services.formatter.add(
|
|
@@ -132,7 +135,7 @@ export const waitForI18next = i18next
|
|
|
132
135
|
if (override?.includeAtSeparator === false || override?.stripComma) {
|
|
133
136
|
return formatted.replace(",", "");
|
|
134
137
|
}
|
|
135
|
-
return formatted
|
|
138
|
+
return ensureAtSeparator(formatted, i18next.t("ui:at"));
|
|
136
139
|
},
|
|
137
140
|
);
|
|
138
141
|
i18next.services.formatter.add("format_duration", (value: number) => {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { categoriesStore } from "@/stores/categories";
|
|
2
|
+
import { currentEnvironment } from "@/stores/env";
|
|
3
|
+
import { globalsStore } from "@/stores/globals";
|
|
4
|
+
import {
|
|
5
|
+
navigateToPlainTestResult,
|
|
6
|
+
navigateToRoot,
|
|
7
|
+
navigateToRootTabRoot,
|
|
8
|
+
navigateToRootTabTestResult,
|
|
9
|
+
rootTabRoute,
|
|
10
|
+
} from "@/stores/router";
|
|
11
|
+
import { currentTrId, trCurrentTab } from "@/stores/testResult";
|
|
12
|
+
|
|
13
|
+
export const REPORT_ROOT_TAB = {
|
|
14
|
+
Results: "results",
|
|
15
|
+
Categories: "categories",
|
|
16
|
+
QualityGate: "qualityGate",
|
|
17
|
+
GlobalAttachments: "globalAttachments",
|
|
18
|
+
GlobalErrors: "globalErrors",
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
export type ReportRootTabId = (typeof REPORT_ROOT_TAB)[keyof typeof REPORT_ROOT_TAB];
|
|
22
|
+
|
|
23
|
+
const REPORT_ROOT_TAB_ORDER: ReportRootTabId[] = [
|
|
24
|
+
REPORT_ROOT_TAB.Results,
|
|
25
|
+
REPORT_ROOT_TAB.Categories,
|
|
26
|
+
REPORT_ROOT_TAB.QualityGate,
|
|
27
|
+
REPORT_ROOT_TAB.GlobalAttachments,
|
|
28
|
+
REPORT_ROOT_TAB.GlobalErrors,
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export const getAvailableReportRootTabs = (): ReportRootTabId[] => {
|
|
32
|
+
const tabs: ReportRootTabId[] = [REPORT_ROOT_TAB.Results];
|
|
33
|
+
const categories = categoriesStore.value.data;
|
|
34
|
+
|
|
35
|
+
if (categories?.roots?.length) {
|
|
36
|
+
tabs.push(REPORT_ROOT_TAB.Categories);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
tabs.push(REPORT_ROOT_TAB.QualityGate, REPORT_ROOT_TAB.GlobalAttachments, REPORT_ROOT_TAB.GlobalErrors);
|
|
40
|
+
|
|
41
|
+
return tabs;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const getCurrentReportRootTab = (): ReportRootTabId => {
|
|
45
|
+
if (rootTabRoute.value.matches) {
|
|
46
|
+
const rootTab = rootTabRoute.value.params.rootTab as ReportRootTabId;
|
|
47
|
+
|
|
48
|
+
if (REPORT_ROOT_TAB_ORDER.includes(rootTab)) {
|
|
49
|
+
return rootTab;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return REPORT_ROOT_TAB.Results;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const navigateToReportRootTab = (tab: ReportRootTabId) => {
|
|
57
|
+
if (!getAvailableReportRootTabs().includes(tab)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const testResultId = currentTrId.value;
|
|
62
|
+
const trTab = trCurrentTab.value;
|
|
63
|
+
|
|
64
|
+
if (tab === REPORT_ROOT_TAB.Results) {
|
|
65
|
+
if (testResultId) {
|
|
66
|
+
navigateToPlainTestResult({ testResultId, tab: trTab });
|
|
67
|
+
} else {
|
|
68
|
+
navigateToRoot();
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (testResultId) {
|
|
74
|
+
navigateToRootTabTestResult({ rootTab: tab, testResultId, tab: trTab });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
navigateToRootTabRoot({ rootTab: tab });
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const cycleReportRootTab = (direction: "next" | "prev") => {
|
|
82
|
+
const available = getAvailableReportRootTabs();
|
|
83
|
+
const current = getCurrentReportRootTab();
|
|
84
|
+
const index = available.indexOf(current);
|
|
85
|
+
|
|
86
|
+
if (index < 0) {
|
|
87
|
+
navigateToReportRootTab(available[0] ?? REPORT_ROOT_TAB.Results);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const nextIndex =
|
|
92
|
+
direction === "next" ? (index + 1) % available.length : (index - 1 + available.length) % available.length;
|
|
93
|
+
|
|
94
|
+
navigateToReportRootTab(available[nextIndex]!);
|
|
95
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { ReportFetchError, errorMessageFromUnknown, fetchReportJsonData } from "@allurereport/web-commons";
|
|
2
|
+
import { signal } from "@preact/signals";
|
|
3
|
+
import MiniSearch from "minisearch";
|
|
4
|
+
import type { AwesomeSearchDocument } from "types";
|
|
5
|
+
|
|
6
|
+
import type { StoreSignalState } from "@/stores/types";
|
|
7
|
+
|
|
8
|
+
const SEARCH_FIELDS: (keyof AwesomeSearchDocument)[] = [
|
|
9
|
+
"id",
|
|
10
|
+
"name",
|
|
11
|
+
"fullName",
|
|
12
|
+
"owner",
|
|
13
|
+
"tags",
|
|
14
|
+
"labels",
|
|
15
|
+
"links",
|
|
16
|
+
"categories",
|
|
17
|
+
"parameters",
|
|
18
|
+
"statusMessage",
|
|
19
|
+
"historyId",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const STORE_FIELDS: (keyof AwesomeSearchDocument)[] = ["nodeId", "name"];
|
|
23
|
+
|
|
24
|
+
export const createSearchIndex = (documents: AwesomeSearchDocument[]) => {
|
|
25
|
+
const searchIndex = new MiniSearch<AwesomeSearchDocument>({
|
|
26
|
+
fields: SEARCH_FIELDS,
|
|
27
|
+
storeFields: STORE_FIELDS,
|
|
28
|
+
searchOptions: {
|
|
29
|
+
combineWith: "AND",
|
|
30
|
+
prefix: true,
|
|
31
|
+
fuzzy: (term) => (term.length > 3 ? 0.2 : false),
|
|
32
|
+
maxFuzzy: 2,
|
|
33
|
+
boost: {
|
|
34
|
+
name: 4,
|
|
35
|
+
fullName: 3,
|
|
36
|
+
owner: 3,
|
|
37
|
+
tags: 2,
|
|
38
|
+
labels: 2,
|
|
39
|
+
links: 1.5,
|
|
40
|
+
categories: 1,
|
|
41
|
+
parameters: 1,
|
|
42
|
+
statusMessage: 0.75,
|
|
43
|
+
historyId: 0.5,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
searchIndex.addAll(documents);
|
|
49
|
+
|
|
50
|
+
return searchIndex;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const searchNodeIds = (searchIndex: MiniSearch<AwesomeSearchDocument>, query: string) => {
|
|
54
|
+
const normalizedQuery = query.trim();
|
|
55
|
+
|
|
56
|
+
if (!normalizedQuery) {
|
|
57
|
+
return new Set<string>();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return new Set(searchIndex.search(normalizedQuery).map(({ nodeId }) => nodeId));
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const searchIndexesStore = signal<StoreSignalState<Record<string, MiniSearch<AwesomeSearchDocument>>>>({
|
|
64
|
+
loading: false,
|
|
65
|
+
error: undefined,
|
|
66
|
+
data: {},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const loadingSearchEnvIds = new Set<string>();
|
|
70
|
+
const failedSearchEnvIds = new Set<string>();
|
|
71
|
+
|
|
72
|
+
const searchIndexPath = (env: string) => `widgets/${env}/search-index.json`;
|
|
73
|
+
const isMissingSearchIndexError = (error: unknown) =>
|
|
74
|
+
error instanceof ReportFetchError && error.response.status === 404;
|
|
75
|
+
|
|
76
|
+
export const resetSearchIndexes = () => {
|
|
77
|
+
loadingSearchEnvIds.clear();
|
|
78
|
+
failedSearchEnvIds.clear();
|
|
79
|
+
searchIndexesStore.value = {
|
|
80
|
+
loading: false,
|
|
81
|
+
error: undefined,
|
|
82
|
+
data: {},
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const fetchEnvSearchIndexes = async (envs: string[]) => {
|
|
87
|
+
const currentData = searchIndexesStore.peek().data ?? {};
|
|
88
|
+
const envsToFetch = envs.filter(
|
|
89
|
+
(env) => !currentData[env] && !loadingSearchEnvIds.has(env) && !failedSearchEnvIds.has(env),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (envsToFetch.length === 0) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
envsToFetch.forEach((env) => loadingSearchEnvIds.add(env));
|
|
97
|
+
|
|
98
|
+
searchIndexesStore.value = {
|
|
99
|
+
...searchIndexesStore.peek(),
|
|
100
|
+
loading: true,
|
|
101
|
+
error: undefined,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const documentsByEnv = await Promise.allSettled(
|
|
106
|
+
envsToFetch.map(async (env) => ({
|
|
107
|
+
env,
|
|
108
|
+
documents: await fetchReportJsonData<AwesomeSearchDocument[]>(searchIndexPath(env), { bustCache: true }),
|
|
109
|
+
})),
|
|
110
|
+
);
|
|
111
|
+
const loadedSearchIndexes: Record<string, MiniSearch<AwesomeSearchDocument>> = {};
|
|
112
|
+
let error: string | undefined;
|
|
113
|
+
|
|
114
|
+
for (const [index, result] of documentsByEnv.entries()) {
|
|
115
|
+
if (result.status === "fulfilled") {
|
|
116
|
+
loadedSearchIndexes[result.value.env] = createSearchIndex(result.value.documents);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!error) {
|
|
121
|
+
error = errorMessageFromUnknown(result.reason);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (isMissingSearchIndexError(result.reason)) {
|
|
125
|
+
failedSearchEnvIds.add(envsToFetch[index]);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
searchIndexesStore.value = {
|
|
130
|
+
data: {
|
|
131
|
+
...(searchIndexesStore.peek().data ?? {}),
|
|
132
|
+
...loadedSearchIndexes,
|
|
133
|
+
},
|
|
134
|
+
loading: false,
|
|
135
|
+
error,
|
|
136
|
+
};
|
|
137
|
+
} catch (e) {
|
|
138
|
+
// Retry malformed/transient responses; only missing search indexes are permanently cached.
|
|
139
|
+
searchIndexesStore.value = {
|
|
140
|
+
...searchIndexesStore.peek(),
|
|
141
|
+
error: errorMessageFromUnknown(e),
|
|
142
|
+
loading: false,
|
|
143
|
+
};
|
|
144
|
+
} finally {
|
|
145
|
+
envsToFetch.forEach((env) => loadingSearchEnvIds.delete(env));
|
|
146
|
+
}
|
|
147
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { moveFocus, type FlatTreeNode, type MoveDirection, type MoveFocusResult } from "@allurereport/web-commons";
|
|
2
|
+
import { computed, effect, signal } from "@preact/signals";
|
|
3
|
+
|
|
4
|
+
import { fixtureResultToTrStepItem, getBodyItems } from "@/components/TestResult/bodyItems";
|
|
5
|
+
import { isTestResultHotkeysContext } from "@/stores/keyboard";
|
|
6
|
+
import { currentTrId, trCurrentTab } from "@/stores/testResult";
|
|
7
|
+
import { testResultStore } from "@/stores/testResults";
|
|
8
|
+
import { collapsedTrees, isTreeOpened, setTreeOpened, toggleTree } from "@/stores/tree";
|
|
9
|
+
import { flattenTestResultOverview } from "@/utils/flattenTestResultOverview";
|
|
10
|
+
export const testResultFocusId = signal<string | undefined>(undefined);
|
|
11
|
+
|
|
12
|
+
export const isTestResultOverviewNavigationContext = (): boolean =>
|
|
13
|
+
isTestResultHotkeysContext() && trCurrentTab.value === "overview";
|
|
14
|
+
|
|
15
|
+
const buildFlatOverview = (): FlatTreeNode[] => {
|
|
16
|
+
const testResultId = currentTrId.value;
|
|
17
|
+
|
|
18
|
+
if (!testResultId) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const testResult = testResultStore.value.data?.[testResultId];
|
|
23
|
+
|
|
24
|
+
if (!testResult) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
collapsedTrees.value;
|
|
29
|
+
|
|
30
|
+
const bodyItems = getBodyItems(testResult, "");
|
|
31
|
+
const setupBodyItems = (testResult.setup ?? []).map((fixture) => fixtureResultToTrStepItem(fixture));
|
|
32
|
+
const teardownBodyItems = (testResult.teardown ?? []).map((fixture) => fixtureResultToTrStepItem(fixture));
|
|
33
|
+
|
|
34
|
+
return flattenTestResultOverview({
|
|
35
|
+
testResultId,
|
|
36
|
+
hasSetup: setupBodyItems.length > 0,
|
|
37
|
+
setupBodyItems,
|
|
38
|
+
bodyItems,
|
|
39
|
+
hasTeardown: teardownBodyItems.length > 0,
|
|
40
|
+
teardownBodyItems,
|
|
41
|
+
isGroupOpened: (id, openedByDefault) => isTreeOpened(id, openedByDefault),
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const flatTestResultOverview = computed(() => buildFlatOverview());
|
|
46
|
+
|
|
47
|
+
export const getFlatTestResultNode = (id: string | undefined) =>
|
|
48
|
+
flatTestResultOverview.value.find((node) => node.id === id);
|
|
49
|
+
|
|
50
|
+
export const moveTestResultFocus = (direction: MoveDirection): MoveFocusResult =>
|
|
51
|
+
moveFocus(flatTestResultOverview.value, testResultFocusId.value, direction);
|
|
52
|
+
|
|
53
|
+
export const setTestResultFocusId = (id: string | undefined) => {
|
|
54
|
+
testResultFocusId.value = id;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const ensureTestResultFocusId = () => {
|
|
58
|
+
const flat = flatTestResultOverview.value;
|
|
59
|
+
|
|
60
|
+
if (flat.length === 0) {
|
|
61
|
+
testResultFocusId.value = undefined;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const currentId = testResultFocusId.value;
|
|
66
|
+
const currentExists = currentId ? flat.some((node) => node.id === currentId) : false;
|
|
67
|
+
|
|
68
|
+
if (currentExists) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
testResultFocusId.value = flat[0]?.id;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
effect(() => {
|
|
76
|
+
if (!isTestResultOverviewNavigationContext()) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
flatTestResultOverview.value;
|
|
81
|
+
ensureTestResultFocusId();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
effect(() => {
|
|
85
|
+
currentTrId.value;
|
|
86
|
+
trCurrentTab.value;
|
|
87
|
+
|
|
88
|
+
if (trCurrentTab.value !== "overview") {
|
|
89
|
+
testResultFocusId.value = undefined;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const resolveOpenedByDefault = (node: FlatTreeNode) => node.openedByDefault ?? true;
|
|
94
|
+
|
|
95
|
+
export const applyTestResultFocusMove = (result: MoveFocusResult) => {
|
|
96
|
+
const node = result.nextId ? getFlatTestResultNode(result.nextId) : undefined;
|
|
97
|
+
|
|
98
|
+
if (result.collapse && node?.nodeId) {
|
|
99
|
+
setTreeOpened(node.nodeId, false, resolveOpenedByDefault(node));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (result.expand && node?.nodeId) {
|
|
103
|
+
setTreeOpened(node.nodeId, true, resolveOpenedByDefault(node));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (result.nextId) {
|
|
107
|
+
setTestResultFocusId(result.nextId);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const toggleTestResultFocusNode = () => {
|
|
112
|
+
const node = getFlatTestResultNode(testResultFocusId.value);
|
|
113
|
+
|
|
114
|
+
if (!node?.nodeId) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
toggleTree(node.nodeId, resolveOpenedByDefault(node));
|
|
119
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { navigateToTestResultTab } from "@/stores/router";
|
|
2
|
+
import { currentTrId, trCurrentTab } from "@/stores/testResult";
|
|
3
|
+
|
|
4
|
+
export const TEST_RESULT_TAB = {
|
|
5
|
+
Overview: "overview",
|
|
6
|
+
History: "history",
|
|
7
|
+
Retries: "retries",
|
|
8
|
+
Attachments: "attachments",
|
|
9
|
+
Environments: "environments",
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
export type TestResultTabId = (typeof TEST_RESULT_TAB)[keyof typeof TEST_RESULT_TAB];
|
|
13
|
+
|
|
14
|
+
const TEST_RESULT_TAB_ORDER: TestResultTabId[] = [
|
|
15
|
+
TEST_RESULT_TAB.Overview,
|
|
16
|
+
TEST_RESULT_TAB.History,
|
|
17
|
+
TEST_RESULT_TAB.Retries,
|
|
18
|
+
TEST_RESULT_TAB.Attachments,
|
|
19
|
+
TEST_RESULT_TAB.Environments,
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export const getCurrentTestResultTab = (): TestResultTabId => {
|
|
23
|
+
const tab = trCurrentTab.value as TestResultTabId;
|
|
24
|
+
|
|
25
|
+
if (TEST_RESULT_TAB_ORDER.includes(tab)) {
|
|
26
|
+
return tab;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return TEST_RESULT_TAB.Overview;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const navigateToTestResultTabById = (tab: TestResultTabId) => {
|
|
33
|
+
const testResultId = currentTrId.value;
|
|
34
|
+
|
|
35
|
+
if (!testResultId) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (tab === TEST_RESULT_TAB.Overview) {
|
|
40
|
+
navigateToTestResultTab({ testResultId, tab: "" });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
navigateToTestResultTab({ testResultId, tab });
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const cycleTestResultTab = (direction: "next" | "prev") => {
|
|
48
|
+
const testResultId = currentTrId.value;
|
|
49
|
+
|
|
50
|
+
if (!testResultId) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const current = getCurrentTestResultTab();
|
|
55
|
+
const index = TEST_RESULT_TAB_ORDER.indexOf(current);
|
|
56
|
+
const nextIndex =
|
|
57
|
+
direction === "next"
|
|
58
|
+
? (index + 1) % TEST_RESULT_TAB_ORDER.length
|
|
59
|
+
: (index - 1 + TEST_RESULT_TAB_ORDER.length) % TEST_RESULT_TAB_ORDER.length;
|
|
60
|
+
|
|
61
|
+
navigateToTestResultTabById(TEST_RESULT_TAB_ORDER[nextIndex]!);
|
|
62
|
+
};
|
package/src/stores/timeline.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { StoreSignalState } from "@/stores/types";
|
|
|
6
6
|
|
|
7
7
|
export type TimlineTr = Pick<
|
|
8
8
|
TestResult,
|
|
9
|
-
"id" | "name" | "status" | "flaky" | "
|
|
9
|
+
"id" | "name" | "status" | "flaky" | "isRetry" | "environment" | "start" | "duration"
|
|
10
10
|
> & {
|
|
11
11
|
environmentName?: string;
|
|
12
12
|
host: string;
|
package/src/stores/tree.ts
CHANGED
|
@@ -7,7 +7,9 @@ import type { StoreSignalState } from "@/stores/types";
|
|
|
7
7
|
import { loadFromLocalStorage } from "@/utils/loadFromLocalStorage";
|
|
8
8
|
import { createRecursiveTree, isRecursiveTreeEmpty } from "@/utils/treeFilters";
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { currentEnvironment } from "./env";
|
|
11
|
+
import { fetchEnvSearchIndexes, searchIndexesStore, searchNodeIds } from "./search";
|
|
12
|
+
import { treeNonQueryFilters, treeQueryFilterValue } from "./treeFilters/store";
|
|
11
13
|
import { sortBy } from "./treeSort";
|
|
12
14
|
|
|
13
15
|
export const treeStore = signal<StoreSignalState<Record<string, AwesomeTree>>>({
|
|
@@ -117,13 +119,48 @@ const treeEntries = computed(() => (treeStore.value.data ? Object.entries(treeSt
|
|
|
117
119
|
const alwaysTruePredicate = () => true;
|
|
118
120
|
|
|
119
121
|
const filterPredicate = computed(() => {
|
|
120
|
-
if (
|
|
122
|
+
if (treeNonQueryFilters.value.length === 0) {
|
|
121
123
|
return alwaysTruePredicate;
|
|
122
124
|
}
|
|
123
125
|
|
|
124
|
-
return buildFilterPredicate(
|
|
126
|
+
return buildFilterPredicate(treeNonQueryFilters.value);
|
|
125
127
|
});
|
|
126
128
|
|
|
129
|
+
effect(() => {
|
|
130
|
+
if (!treeQueryFilterValue.value?.trim()) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const treeEnvIds = Object.keys(treeStore.value.data ?? {});
|
|
135
|
+
const envsToFetch = currentEnvironment.value ? [currentEnvironment.value] : treeEnvIds;
|
|
136
|
+
|
|
137
|
+
if (envsToFetch.length === 0) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
fetchEnvSearchIndexes(envsToFetch);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const searchFilterPredicate = (env: string) => {
|
|
145
|
+
const query = treeQueryFilterValue.value?.trim();
|
|
146
|
+
|
|
147
|
+
if (!query) {
|
|
148
|
+
return alwaysTruePredicate;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const searchIndex = searchIndexesStore.value.data?.[env];
|
|
152
|
+
|
|
153
|
+
if (!searchIndex) {
|
|
154
|
+
fetchEnvSearchIndexes([env]);
|
|
155
|
+
|
|
156
|
+
return alwaysTruePredicate;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const matchingNodeIds = searchNodeIds(searchIndex, query);
|
|
160
|
+
|
|
161
|
+
return (leaf: { nodeId: string }) => matchingNodeIds.has(leaf.nodeId);
|
|
162
|
+
};
|
|
163
|
+
|
|
127
164
|
export const filteredTree = computed(() => {
|
|
128
165
|
return treeEntries.value.reduce(
|
|
129
166
|
(acc, [key, value]) => {
|
|
@@ -132,12 +169,13 @@ export const filteredTree = computed(() => {
|
|
|
132
169
|
}
|
|
133
170
|
|
|
134
171
|
const { root, leavesById, groupsById } = value;
|
|
172
|
+
const envSearchFilterPredicate = searchFilterPredicate(key);
|
|
135
173
|
|
|
136
174
|
const tree = createRecursiveTree({
|
|
137
175
|
group: root as AwesomeTreeGroup,
|
|
138
176
|
leavesById,
|
|
139
177
|
groupsById,
|
|
140
|
-
filterPredicate: filterPredicate.value,
|
|
178
|
+
filterPredicate: (leaf) => filterPredicate.value(leaf) && envSearchFilterPredicate(leaf),
|
|
141
179
|
sortBy: sortBy.value,
|
|
142
180
|
});
|
|
143
181
|
|
|
@@ -114,38 +114,9 @@ const treeStatusFilter = computed<AwesomeStringFieldFilter>(() => ({
|
|
|
114
114
|
},
|
|
115
115
|
}));
|
|
116
116
|
|
|
117
|
-
export const
|
|
118
|
-
return {
|
|
119
|
-
type: "group",
|
|
120
|
-
logicalOperator: "AND",
|
|
121
|
-
value: [
|
|
122
|
-
{
|
|
123
|
-
type: "field",
|
|
124
|
-
logicalOperator: "OR",
|
|
125
|
-
value: {
|
|
126
|
-
key: "name",
|
|
127
|
-
value: urlQueryFilter.value,
|
|
128
|
-
type: "string",
|
|
129
|
-
strict: false,
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
type: "field",
|
|
134
|
-
logicalOperator: "OR",
|
|
135
|
-
value: {
|
|
136
|
-
key: "id",
|
|
137
|
-
value: urlQueryFilter.value,
|
|
138
|
-
type: "string",
|
|
139
|
-
strict: false,
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
};
|
|
144
|
-
});
|
|
117
|
+
export const treeQueryFilterValue = computed(() => urlQueryFilter.value);
|
|
145
118
|
|
|
146
|
-
export const
|
|
147
|
-
|
|
148
|
-
export const setTreeQueryFilter = (query: string) => {
|
|
119
|
+
export const setTreeQueryFilter = (query?: string) => {
|
|
149
120
|
setQueryFilter(query);
|
|
150
121
|
};
|
|
151
122
|
|
|
@@ -217,13 +188,9 @@ export const treeQuickFilters = computed<AwesomeFilter[]>(() => [
|
|
|
217
188
|
treeCategoriesFilter.value,
|
|
218
189
|
]);
|
|
219
190
|
|
|
220
|
-
export const
|
|
191
|
+
export const treeNonQueryFilters = computed(() => {
|
|
221
192
|
const filters: AwesomeFilter[] = [];
|
|
222
193
|
|
|
223
|
-
if (treeQueryFilterValue.value) {
|
|
224
|
-
filters.push(treeQueryFilter.value);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
194
|
const hasBothRetryAndFlaky = urlRetryFilter.value && urlFlakyFilter.value;
|
|
228
195
|
|
|
229
196
|
if (hasBothRetryAndFlaky) {
|
package/src/stores/treeSort.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { getParamValue, hasParam, setParams } from "@allurereport/web-commons";
|
|
1
|
+
import { getParamValue, getReportOptions, hasParam, setParams } from "@allurereport/web-commons";
|
|
2
2
|
import { computed, effect, signal } from "@preact/signals";
|
|
3
3
|
|
|
4
|
+
import type { AwesomeReportOptions } from "../../types.js";
|
|
5
|
+
|
|
4
6
|
export type SortByDirection = "asc" | "desc";
|
|
5
7
|
export type SortByField = "order" | "duration" | "status" | "name";
|
|
6
8
|
export type SortBy = `${SortByField},${SortByDirection}`;
|
|
@@ -34,6 +36,10 @@ const getInitialSortBy = (): SortBy => {
|
|
|
34
36
|
if (stored && validateSortBy(stored.toLowerCase())) {
|
|
35
37
|
return stored.toLowerCase() as SortBy;
|
|
36
38
|
}
|
|
39
|
+
const { defaultSortBy } = getReportOptions<AwesomeReportOptions>() ?? {};
|
|
40
|
+
if (defaultSortBy && validateSortBy(defaultSortBy.toLowerCase())) {
|
|
41
|
+
return defaultSortBy.toLowerCase() as SortBy;
|
|
42
|
+
}
|
|
37
43
|
return DEFAULT_SORT_BY;
|
|
38
44
|
};
|
|
39
45
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// border-top on the white pane card (visible above header/content; inset shadow is covered by children).
|
|
2
|
+
@mixin split-pane-indicator {
|
|
3
|
+
border-top: 3px solid transparent;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
@mixin split-pane-indicator-active {
|
|
7
|
+
border-top-color: var(--bg-support-aldebaran);
|
|
8
|
+
}
|
package/src/styles.scss
CHANGED