@allurereport/web-awesome 3.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/.babelrc.js +46 -0
  2. package/.eslintrc.cjs +18 -0
  3. package/CONTRIBUTING.md +34 -0
  4. package/README.md +27 -0
  5. package/dist/multi/141.app-d01d0c66.js +1 -0
  6. package/dist/multi/222.app-d01d0c66.js +1 -0
  7. package/dist/multi/335.app-d01d0c66.js +1 -0
  8. package/dist/multi/34.app-d01d0c66.js +1 -0
  9. package/dist/multi/349.app-d01d0c66.js +1 -0
  10. package/dist/multi/378.app-d01d0c66.js +1 -0
  11. package/dist/multi/406.app-d01d0c66.js +1 -0
  12. package/dist/multi/476.app-d01d0c66.js +1 -0
  13. package/dist/multi/53.app-d01d0c66.js +1 -0
  14. package/dist/multi/584.app-d01d0c66.js +1 -0
  15. package/dist/multi/690.app-d01d0c66.js +1 -0
  16. package/dist/multi/747.app-d01d0c66.js +1 -0
  17. package/dist/multi/767.app-d01d0c66.js +1 -0
  18. package/dist/multi/816.app-d01d0c66.js +1 -0
  19. package/dist/multi/83.app-d01d0c66.js +1 -0
  20. package/dist/multi/873.app-d01d0c66.js +1 -0
  21. package/dist/multi/920.app-d01d0c66.js +1 -0
  22. package/dist/multi/991.app-d01d0c66.js +1 -0
  23. package/dist/multi/JetBrainsMono_vf-9e9649b6..woff2 +0 -0
  24. package/dist/multi/JetBrainsMono_vf-b9a9c326..woff +0 -0
  25. package/dist/multi/app-d01d0c66.js +2 -0
  26. package/dist/multi/app-d01d0c66.js.LICENSE.txt +16 -0
  27. package/dist/multi/manifest.json +26 -0
  28. package/dist/multi/pt-root-ui_vf-22fe60ca..woff +0 -0
  29. package/dist/multi/pt-root-ui_vf-9d251e8b..woff2 +0 -0
  30. package/dist/multi/styles-d01d0c66.css +39 -0
  31. package/dist/single/app-6596cb08.js +2 -0
  32. package/dist/single/app-6596cb08.js.LICENSE.txt +16 -0
  33. package/dist/single/manifest.json +3 -0
  34. package/package.json +107 -0
  35. package/postcss.config.js +5 -0
  36. package/src/assets/scss/_common.scss +143 -0
  37. package/src/assets/scss/day.scss +51 -0
  38. package/src/assets/scss/fonts.scss +3 -0
  39. package/src/assets/scss/index.scss +7 -0
  40. package/src/assets/scss/night.scss +61 -0
  41. package/src/assets/scss/palette.scss +393 -0
  42. package/src/assets/scss/theme.scss +119 -0
  43. package/src/assets/scss/vars.scss +9 -0
  44. package/src/assets/svg/line-alerts-alert-circle.svg +12 -0
  45. package/src/assets/svg/line-general-eye.svg +7 -0
  46. package/src/assets/svg/line-icon-bomb-2.svg +12 -0
  47. package/src/components/ArrowButton/index.tsx +36 -0
  48. package/src/components/ArrowButton/styles.scss +35 -0
  49. package/src/components/BaseLayout/index.tsx +43 -0
  50. package/src/components/BaseLayout/styles.scss +60 -0
  51. package/src/components/Footer/FooterLogo.tsx +16 -0
  52. package/src/components/Footer/FooterVersion.tsx +37 -0
  53. package/src/components/Footer/index.tsx +13 -0
  54. package/src/components/Footer/styles.scss +14 -0
  55. package/src/components/Header/index.tsx +28 -0
  56. package/src/components/Header/styles.scss +33 -0
  57. package/src/components/LanguagePicker/index.tsx +40 -0
  58. package/src/components/MainReport/index.tsx +21 -0
  59. package/src/components/MainReport/styles.scss +11 -0
  60. package/src/components/Metadata/index.tsx +121 -0
  61. package/src/components/Metadata/styles.scss +146 -0
  62. package/src/components/MetadataButton/index.tsx +32 -0
  63. package/src/components/MetadataButton/styles.scss +53 -0
  64. package/src/components/Modal/index.tsx +22 -0
  65. package/src/components/ReportBody/Filters.tsx +90 -0
  66. package/src/components/ReportBody/HeaderActions.tsx +21 -0
  67. package/src/components/ReportBody/SortBy.tsx +132 -0
  68. package/src/components/ReportBody/context.tsx +106 -0
  69. package/src/components/ReportBody/index.tsx +72 -0
  70. package/src/components/ReportBody/styles.scss +69 -0
  71. package/src/components/ReportHeader/ReportHeaderLabelList.tsx +12 -0
  72. package/src/components/ReportHeader/ReportHeaderLogo.tsx +10 -0
  73. package/src/components/ReportHeader/ReportHeaderPie.tsx +14 -0
  74. package/src/components/ReportHeader/index.tsx +33 -0
  75. package/src/components/ReportHeader/styles.scss +51 -0
  76. package/src/components/ReportLogo/index.tsx +16 -0
  77. package/src/components/ReportLogo/styles.scss +20 -0
  78. package/src/components/ReportLogoFull/index.tsx +20 -0
  79. package/src/components/ReportLogoFull/styles.scss +7 -0
  80. package/src/components/ReportMetadata/MetadataItem.tsx +45 -0
  81. package/src/components/ReportMetadata/MetadataSummary.tsx +81 -0
  82. package/src/components/ReportMetadata/MetadataTestType.tsx +16 -0
  83. package/src/components/ReportMetadata/MetadataWithIcon.tsx +21 -0
  84. package/src/components/ReportMetadata/index.tsx +46 -0
  85. package/src/components/ReportMetadata/styles.scss +99 -0
  86. package/src/components/SideBySide/index.tsx +52 -0
  87. package/src/components/SideBySide/styles.scss +65 -0
  88. package/src/components/SplitLayout/index.tsx +77 -0
  89. package/src/components/SplitLayout/styles.scss +85 -0
  90. package/src/components/Tabs/index.tsx +62 -0
  91. package/src/components/Tabs/styles.scss +56 -0
  92. package/src/components/TestResult/TestResultAttachmentsView/index.tsx +27 -0
  93. package/src/components/TestResult/TestResultAttachmentsView/styles.scss +12 -0
  94. package/src/components/TestResult/TestResultDescription/index.tsx +27 -0
  95. package/src/components/TestResult/TestResultDescription/styles.scss +12 -0
  96. package/src/components/TestResult/TestResultDropdown/index.tsx +26 -0
  97. package/src/components/TestResult/TestResultDropdown/styles.scss +34 -0
  98. package/src/components/TestResult/TestResultEmpty/index.tsx +34 -0
  99. package/src/components/TestResult/TestResultEmpty/styles.scss +25 -0
  100. package/src/components/TestResult/TestResultHeader/TestResultBreadcrumbs.tsx +44 -0
  101. package/src/components/TestResult/TestResultHeader/index.tsx +21 -0
  102. package/src/components/TestResult/TestResultHeader/styles.scss +48 -0
  103. package/src/components/TestResult/TestResultHistory/TestResultHistoryItem.tsx +66 -0
  104. package/src/components/TestResult/TestResultHistory/index.tsx +26 -0
  105. package/src/components/TestResult/TestResultHistory/styles.scss +63 -0
  106. package/src/components/TestResult/TestResultInfo/TestResultInfoStatuses.tsx +30 -0
  107. package/src/components/TestResult/TestResultInfo/index.tsx +81 -0
  108. package/src/components/TestResult/TestResultInfo/styles.scss +68 -0
  109. package/src/components/TestResult/TestResultLinks/index.tsx +56 -0
  110. package/src/components/TestResult/TestResultLinks/styles.scss +30 -0
  111. package/src/components/TestResult/TestResultMetadata/index.tsx +27 -0
  112. package/src/components/TestResult/TestResultMetadata/styles.scss +8 -0
  113. package/src/components/TestResult/TestResultNavigation/index.tsx +80 -0
  114. package/src/components/TestResult/TestResultNavigation/styles.scss +48 -0
  115. package/src/components/TestResult/TestResultOverview.tsx +43 -0
  116. package/src/components/TestResult/TestResultParameters/index.tsx +30 -0
  117. package/src/components/TestResult/TestResultParameters/styles.scss +8 -0
  118. package/src/components/TestResult/TestResultPrevStatuses/index.tsx +49 -0
  119. package/src/components/TestResult/TestResultPrevStatuses/styles.scss +57 -0
  120. package/src/components/TestResult/TestResultRetriesView/TestResultRetriesItem.tsx +51 -0
  121. package/src/components/TestResult/TestResultRetriesView/index.tsx +24 -0
  122. package/src/components/TestResult/TestResultRetriesView/styles.scss +69 -0
  123. package/src/components/TestResult/TestResultSetup/index.tsx +58 -0
  124. package/src/components/TestResult/TestResultSeverity/index.tsx +27 -0
  125. package/src/components/TestResult/TestResultSeverity/styles.scss +29 -0
  126. package/src/components/TestResult/TestResultStatus/index.tsx +25 -0
  127. package/src/components/TestResult/TestResultStatus/styles.scss +36 -0
  128. package/src/components/TestResult/TestResultSteps/index.tsx +58 -0
  129. package/src/components/TestResult/TestResultSteps/styles.scss +225 -0
  130. package/src/components/TestResult/TestResultSteps/testResultAttachment.tsx +76 -0
  131. package/src/components/TestResult/TestResultSteps/testResultAttachmentInfo.tsx +83 -0
  132. package/src/components/TestResult/TestResultSteps/testResultStep.tsx +84 -0
  133. package/src/components/TestResult/TestResultSteps/testResultStepInfo.tsx +30 -0
  134. package/src/components/TestResult/TestResultSteps/wrongAttachment.tsx +8 -0
  135. package/src/components/TestResult/TestResultTabs/index.tsx +68 -0
  136. package/src/components/TestResult/TestResultTabs/styles.scss +76 -0
  137. package/src/components/TestResult/TestResultTeardown/index.tsx +59 -0
  138. package/src/components/TestResult/TestStepsEmpty/index.tsx +23 -0
  139. package/src/components/TestResult/TestStepsEmpty/styles.scss +25 -0
  140. package/src/components/TestResult/TrError/TrDiff.tsx +124 -0
  141. package/src/components/TestResult/TrError/index.tsx +78 -0
  142. package/src/components/TestResult/TrError/styles.scss +133 -0
  143. package/src/components/TestResult/index.tsx +62 -0
  144. package/src/components/TestResult/styles.scss +11 -0
  145. package/src/components/ThemeButton/ThemeButton.tsx +20 -0
  146. package/src/components/ToggleLayout/index.tsx +17 -0
  147. package/src/components/Tree/index.tsx +75 -0
  148. package/src/components/Tree/styles.scss +189 -0
  149. package/src/i18n/constants.ts +124 -0
  150. package/src/i18n/locales/am.json +133 -0
  151. package/src/i18n/locales/az.json +133 -0
  152. package/src/i18n/locales/de.json +133 -0
  153. package/src/i18n/locales/en.json +134 -0
  154. package/src/i18n/locales/es.json +133 -0
  155. package/src/i18n/locales/fr.json +133 -0
  156. package/src/i18n/locales/he.json +133 -0
  157. package/src/i18n/locales/it.json +133 -0
  158. package/src/i18n/locales/ja.json +133 -0
  159. package/src/i18n/locales/ka.json +133 -0
  160. package/src/i18n/locales/kr.json +133 -0
  161. package/src/i18n/locales/nl.json +133 -0
  162. package/src/i18n/locales/pl.json +131 -0
  163. package/src/i18n/locales/pt.json +133 -0
  164. package/src/i18n/locales/ru.json +131 -0
  165. package/src/i18n/locales/sv.json +133 -0
  166. package/src/i18n/locales/tr.json +133 -0
  167. package/src/i18n/locales/zh.json +133 -0
  168. package/src/index.html +40 -0
  169. package/src/index.tsx +96 -0
  170. package/src/stores/chart.ts +32 -0
  171. package/src/stores/envInfo.ts +34 -0
  172. package/src/stores/index.ts +4 -0
  173. package/src/stores/layout.ts +36 -0
  174. package/src/stores/locale.ts +75 -0
  175. package/src/stores/modal.ts +22 -0
  176. package/src/stores/router.ts +48 -0
  177. package/src/stores/stats.ts +36 -0
  178. package/src/stores/testResults.ts +66 -0
  179. package/src/stores/theme.ts +32 -0
  180. package/src/stores/tree.ts +157 -0
  181. package/src/stores/types.ts +5 -0
  182. package/src/styles.scss +45 -0
  183. package/src/types/globals.d.ts +13 -0
  184. package/src/types/window.d.ts +8 -0
  185. package/src/utils/capitalize.ts +6 -0
  186. package/src/utils/copyToClipboard.ts +16 -0
  187. package/src/utils/isMac.ts +8 -0
  188. package/src/utils/loadFromLocalStorage.ts +8 -0
  189. package/src/utils/statuses.ts +55 -0
  190. package/src/utils/time.ts +17 -0
  191. package/src/utils/treeFilters.ts +147 -0
  192. package/test/utils/treeFilters.test.ts +448 -0
  193. package/tsconfig.json +27 -0
  194. package/types.d.ts +99 -0
  195. package/vitest.config.ts +18 -0
  196. package/webpack.config.js +112 -0
@@ -0,0 +1,75 @@
1
+ import { getReportOptions } from "@allurereport/web-commons";
2
+ import { computed, signal } from "@preact/signals";
3
+ import i18next, { type TOptions } from "i18next";
4
+ import type { AwesomeReportOptions } from "types";
5
+ import { DEFAULT_LOCALE, LANG_LOCALE, type LangLocale } from "@/i18n/constants";
6
+
7
+ const namespaces = [
8
+ "empty",
9
+ "execution",
10
+ "filters",
11
+ "search",
12
+ "severity",
13
+ "sort-by",
14
+ "sort-by.directions",
15
+ "sort-by.values",
16
+ "statuses",
17
+ "tabs",
18
+ "testSummary",
19
+ "ui",
20
+ "welcome",
21
+ "controls",
22
+ "errors",
23
+ "split",
24
+ "modal",
25
+ ];
26
+
27
+ export const currentLocale = signal<LangLocale>("en" as LangLocale);
28
+ export const currentLocaleIso = computed(() => LANG_LOCALE[currentLocale.value].iso);
29
+ export const currentLocaleIsRTL = computed(() => ["ar", "he", "fa"].includes(currentLocale.value));
30
+
31
+ export const getLocale = async () => {
32
+ const { reportLanguage } = getReportOptions<AwesomeReportOptions>() ?? {};
33
+ const locale = localStorage.getItem("currentLocale") || reportLanguage || DEFAULT_LOCALE;
34
+
35
+ await setLocale(locale as LangLocale);
36
+ };
37
+
38
+ export const waitForI18next = i18next
39
+ .use({
40
+ type: "backend",
41
+ read: async (
42
+ language: LangLocale,
43
+ namespace: string,
44
+ callback: (errorValue: unknown, translations: null) => void,
45
+ ) => {
46
+ await import(`@/i18n/locales/${language}.json`)
47
+ .then((resources: Record<string, null>) => {
48
+ callback(null, resources[namespace]);
49
+ })
50
+ .catch((error) => {
51
+ callback(error, null);
52
+ });
53
+ },
54
+ })
55
+ .init({
56
+ lng: currentLocale.value,
57
+ fallbackLng: "en",
58
+ ns: namespaces,
59
+ interpolation: { escapeValue: false },
60
+ });
61
+
62
+ export const useI18n = (namespace?: string) => {
63
+ const t = computed(() => (key: string, options?: TOptions) => i18next.t(key, { ns: namespace, ...options }));
64
+
65
+ return {
66
+ t: t.value,
67
+ currentLocale: currentLocale.value,
68
+ };
69
+ };
70
+
71
+ export const setLocale = async (locale: LangLocale) => {
72
+ await i18next.changeLanguage(locale as string);
73
+ localStorage.setItem("currentLocale", locale as string);
74
+ currentLocale.value = locale;
75
+ };
@@ -0,0 +1,22 @@
1
+ import type { ModalDataProps } from "@allurereport/web-components";
2
+ import { signal } from "@preact/signals";
3
+
4
+ export const isModalOpen = signal(false);
5
+
6
+ export const modalData = signal<ModalDataProps>({
7
+ data: null,
8
+ preview: false,
9
+ component: null,
10
+ isModalOpen: isModalOpen.value,
11
+ closeModal: null,
12
+ title: "",
13
+ });
14
+
15
+ export const openModal = (props: ModalDataProps) => {
16
+ modalData.value = { ...props };
17
+ isModalOpen.value = true;
18
+ };
19
+
20
+ export const closeModal = () => {
21
+ isModalOpen.value = false;
22
+ };
@@ -0,0 +1,48 @@
1
+ import { computed, signal } from "@preact/signals";
2
+
3
+ type NavigateToString = string;
4
+ type NavigateToObject = {
5
+ id?: string | null;
6
+ params?: {
7
+ tabName?: string | null;
8
+ };
9
+ };
10
+
11
+ const parseHash = () => {
12
+ const hash = globalThis.location.hash.slice(1);
13
+ const [id, params] = hash.split("/");
14
+ return {
15
+ id: id || null,
16
+ params: params ? { tabName: params } : {},
17
+ };
18
+ };
19
+
20
+ export const route = signal<NavigateToObject>(parseHash());
21
+
22
+ export const handleHashChange = () => {
23
+ const newRoute = parseHash();
24
+
25
+ if (newRoute.id !== route.value.id || newRoute.params.tabName !== route.value.params.tabName) {
26
+ route.value = newRoute;
27
+ }
28
+ };
29
+
30
+ export const navigateTo = (path: NavigateToString | NavigateToObject) => {
31
+ let newHash = "";
32
+
33
+ if (typeof path === "string") {
34
+ newHash = path.startsWith("#") ? path.slice(1) : path;
35
+ } else {
36
+ const { id, params = {} } = path;
37
+
38
+ newHash = `${id}/${params.tabName ?? ""}`;
39
+ }
40
+ history.pushState(null, "", `#${newHash}`);
41
+ handleHashChange();
42
+ };
43
+
44
+ export const openInNewTab = (path: string) => {
45
+ window.open(`#${path}`, "_blank");
46
+ };
47
+
48
+ export const activeTab = computed(() => route.value.params?.tabName || "overview");
@@ -0,0 +1,36 @@
1
+ import type { Statistic } from "@allurereport/core-api";
2
+ import { fetchReportJsonData } from "@allurereport/web-commons";
3
+ import { signal } from "@preact/signals";
4
+ import type { StoreSignalState } from "@/stores/types";
5
+
6
+ export const statsStore = signal<StoreSignalState<Statistic>>({
7
+ loading: true,
8
+ error: undefined,
9
+ data: {
10
+ total: 0,
11
+ },
12
+ });
13
+
14
+ export const fetchStats = async () => {
15
+ statsStore.value = {
16
+ ...statsStore.value,
17
+ loading: true,
18
+ error: undefined,
19
+ };
20
+
21
+ try {
22
+ const res = await fetchReportJsonData<Statistic>("widgets/allure_statistic.json");
23
+
24
+ statsStore.value = {
25
+ data: res,
26
+ error: undefined,
27
+ loading: false,
28
+ };
29
+ } catch (err) {
30
+ statsStore.value = {
31
+ data: { total: 0 },
32
+ error: err.message,
33
+ loading: false,
34
+ };
35
+ }
36
+ };
@@ -0,0 +1,66 @@
1
+ import { fetchReportJsonData } from "@allurereport/web-commons";
2
+ import { signal } from "@preact/signals";
3
+ import { type AwesomeTestResult } from "../../types";
4
+ import { type StoreSignalState } from "./types";
5
+
6
+ export type TestResultsStoreState = Record<string, AwesomeTestResult>;
7
+
8
+ export type TestResultNavStoreState = string[];
9
+
10
+ export const testResultStore = signal<StoreSignalState<TestResultsStoreState>>({
11
+ loading: true,
12
+ error: undefined,
13
+ data: undefined,
14
+ });
15
+
16
+ export const testResultNavStore = signal<StoreSignalState<TestResultNavStoreState>>({
17
+ loading: true,
18
+ error: undefined,
19
+ data: undefined,
20
+ });
21
+
22
+ export const fetchTestResultNav = async () => {
23
+ try {
24
+ const data = await fetchReportJsonData<string[]>("widgets/nav.json");
25
+
26
+ testResultNavStore.value = {
27
+ data,
28
+ error: undefined,
29
+ loading: false,
30
+ };
31
+ } catch (err) {
32
+ testResultNavStore.value = {
33
+ ...testResultNavStore.value,
34
+ error: err.message,
35
+ loading: false,
36
+ };
37
+ }
38
+ };
39
+
40
+ export const fetchTestResult = async (testResultId: string) => {
41
+ if (!testResultId || (testResultStore.value.data && testResultId in testResultStore.value.data)) {
42
+ return;
43
+ }
44
+
45
+ testResultStore.value = {
46
+ ...testResultStore.value,
47
+ loading: true,
48
+ error: undefined,
49
+ };
50
+
51
+ try {
52
+ const data = await fetchReportJsonData<AwesomeTestResult>(`data/test-results/${testResultId}.json`);
53
+
54
+ testResultStore.value = {
55
+ data: { ...testResultStore.value.data, [testResultId]: data },
56
+ error: undefined,
57
+ loading: false,
58
+ };
59
+ } catch (err) {
60
+ testResultStore.value = {
61
+ ...testResultStore.value,
62
+ error: err.message,
63
+ loading: false,
64
+ };
65
+ }
66
+ };
@@ -0,0 +1,32 @@
1
+ import { getReportOptions } from "@allurereport/web-commons";
2
+ import { signal } from "@preact/signals";
3
+ import type { AwesomeReportOptions } from "../../types.js";
4
+
5
+ type Theme = "light" | "dark";
6
+
7
+ export const themeStore = signal<Theme>("light");
8
+
9
+ export const setTheme = (newTheme: Theme): void => {
10
+ themeStore.value = newTheme;
11
+ document.documentElement.setAttribute("data-theme", newTheme);
12
+ window.localStorage.setItem("theme", newTheme);
13
+ };
14
+
15
+ export const toggleTheme = () => {
16
+ setTheme(themeStore.value === "light" ? "dark" : "light");
17
+ };
18
+
19
+ export const getTheme = () => {
20
+ const { theme } = getReportOptions<AwesomeReportOptions>() ?? {};
21
+ const themeFromLS = (window.localStorage.getItem("theme") as Theme | null) || (theme as Theme);
22
+
23
+ if (themeFromLS) {
24
+ setTheme(themeFromLS);
25
+ return;
26
+ }
27
+
28
+ const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)").matches;
29
+ const initialTheme = prefersDarkScheme ? "dark" : "light";
30
+
31
+ setTheme(initialTheme);
32
+ };
@@ -0,0 +1,157 @@
1
+ import { fetchReportJsonData } from "@allurereport/web-commons";
2
+ import { computed, effect, signal } from "@preact/signals";
3
+ import type { AwesomeStatus, AwesomeTree, AwesomeTreeGroup } from "types";
4
+ import type { StoreSignalState } from "@/stores/types";
5
+ import { loadFromLocalStorage } from "@/utils/loadFromLocalStorage";
6
+ import { createRecursiveTree, isRecursiveTreeEmpty } from "@/utils/treeFilters";
7
+
8
+ export type TreeSortBy = "order" | "duration" | "status" | "alphabet";
9
+ export type TreeDirection = "asc" | "desc";
10
+ export type TreeFilters = "flaky" | "retry" | "new";
11
+ export type TreeFiltersState = {
12
+ query: string;
13
+ status: AwesomeStatus;
14
+ filter: Record<TreeFilters, boolean>;
15
+ sortBy: TreeSortBy;
16
+ direction: TreeDirection;
17
+ };
18
+
19
+ export const treeStore = signal<StoreSignalState<AwesomeTree>>({
20
+ loading: true,
21
+ error: undefined,
22
+ data: undefined,
23
+ });
24
+
25
+ export const noTests = computed(() => !Object.keys(treeStore?.value?.data?.leavesById).length);
26
+
27
+ const loadedFromLS = loadFromLocalStorage<string[]>("collapsedTrees", []);
28
+ export const collapsedTrees = signal(new Set(loadedFromLS as string[]));
29
+
30
+ effect(() => {
31
+ localStorage.setItem("collapsedTrees", JSON.stringify([...collapsedTrees.value]));
32
+ });
33
+
34
+ export const toggleTree = (id: string) => {
35
+ const newSet = new Set(collapsedTrees.value);
36
+ if (newSet.has(id)) {
37
+ newSet.delete(id);
38
+ } else {
39
+ newSet.add(id);
40
+ }
41
+ collapsedTrees.value = newSet;
42
+ };
43
+
44
+ export const selectedFilters = signal(new Set(loadFromLocalStorage("selectedFilters", []) as []));
45
+
46
+ effect(() => {
47
+ localStorage.setItem("selectedFilters", JSON.stringify([...selectedFilters.value]));
48
+ });
49
+
50
+ export const treeFiltersStore = signal<TreeFiltersState>(
51
+ loadFromLocalStorage<TreeFiltersState>("treeFilters", {
52
+ query: "",
53
+ status: "total",
54
+ filter: {
55
+ flaky: false,
56
+ retry: false,
57
+ new: false,
58
+ },
59
+ sortBy: "order",
60
+ direction: "asc",
61
+ }) as TreeFiltersState,
62
+ );
63
+
64
+ effect(() => {
65
+ localStorage.setItem("treeFilters", JSON.stringify(treeFiltersStore.value));
66
+ });
67
+
68
+ export const filteredTree = computed(() => {
69
+ const { root, leavesById, groupsById } = treeStore.value.data;
70
+
71
+ return createRecursiveTree({
72
+ group: root as AwesomeTreeGroup,
73
+ leavesById,
74
+ groupsById,
75
+ filterOptions: treeFiltersStore.value,
76
+ });
77
+ });
78
+
79
+ export const noTestsFound = computed(() => {
80
+ return isRecursiveTreeEmpty(filteredTree.value);
81
+ });
82
+
83
+ export const clearTreeFilters = () => {
84
+ treeFiltersStore.value = {
85
+ query: "",
86
+ status: "total",
87
+ filter: {
88
+ flaky: false,
89
+ retry: false,
90
+ new: false,
91
+ },
92
+ sortBy: "order",
93
+ direction: "asc",
94
+ };
95
+ };
96
+
97
+ export const setTreeQuery = (query: string) => {
98
+ treeFiltersStore.value = {
99
+ ...treeFiltersStore.value,
100
+ query,
101
+ };
102
+ };
103
+
104
+ export const setTreeStatus = (status: AwesomeStatus) => {
105
+ treeFiltersStore.value = {
106
+ ...treeFiltersStore.value,
107
+ status,
108
+ };
109
+ };
110
+
111
+ export const setTreeSortBy = (sortBy: TreeSortBy) => {
112
+ treeFiltersStore.value = {
113
+ ...treeFiltersStore.value,
114
+ sortBy,
115
+ };
116
+ };
117
+
118
+ export const setTreeDirection = (direction: TreeDirection) => {
119
+ treeFiltersStore.value = {
120
+ ...treeFiltersStore.value,
121
+ direction,
122
+ };
123
+ };
124
+
125
+ export const setTreeFilter = (filterKey: TreeFilters, value: boolean) => {
126
+ treeFiltersStore.value = {
127
+ ...treeFiltersStore.value,
128
+ filter: {
129
+ ...treeFiltersStore.value.filter,
130
+ [filterKey]: value,
131
+ },
132
+ };
133
+ };
134
+
135
+ export const fetchTreeData = async () => {
136
+ treeStore.value = {
137
+ ...treeStore.value,
138
+ loading: true,
139
+ error: undefined,
140
+ };
141
+
142
+ try {
143
+ const res = await fetchReportJsonData<AwesomeTree>("widgets/tree.json");
144
+
145
+ treeStore.value = {
146
+ data: res,
147
+ error: undefined,
148
+ loading: false,
149
+ };
150
+ } catch (e) {
151
+ treeStore.value = {
152
+ ...treeStore.value,
153
+ error: e.message,
154
+ loading: false,
155
+ };
156
+ }
157
+ };
@@ -0,0 +1,5 @@
1
+ export interface StoreSignalState<T> {
2
+ error?: string;
3
+ loading: boolean;
4
+ data?: T;
5
+ }
@@ -0,0 +1,45 @@
1
+ .main {
2
+ position: relative;
3
+ max-width: 1920px;
4
+ margin: 0 auto;
5
+
6
+ &:hover {
7
+ .split {
8
+ opacity: 1;
9
+ }
10
+ }
11
+ }
12
+
13
+ .split {
14
+ opacity: 0;
15
+ position: absolute;
16
+ left: 128px;
17
+ top: 8px;
18
+ }
19
+
20
+ .loader {
21
+ position: fixed;
22
+ width: 100%;
23
+ height: 100%;
24
+ top: 0;
25
+ left: 0;
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ background: var(--bg-base-secondary);
30
+ background-blend-mode: saturation;
31
+ flex-direction: column;
32
+ gap: 16px;
33
+ transition:
34
+ opacity 100ms,
35
+ background-color 200ms;
36
+
37
+ visibility: hidden;
38
+ opacity: 0;
39
+
40
+ &.loading {
41
+ z-index: 10;
42
+ visibility: visible;
43
+ opacity: 1;
44
+ }
45
+ }
@@ -0,0 +1,13 @@
1
+ declare module "*.svg" {
2
+ const content: {
3
+ id: string;
4
+ };
5
+
6
+ export default content;
7
+ }
8
+
9
+ declare module "*.scss" {
10
+ const content: Record<string, string>;
11
+
12
+ export = content;
13
+ }
@@ -0,0 +1,8 @@
1
+ declare global {
2
+ interface Window {
3
+ reportDataReady: boolean;
4
+ reportData: Record<string, any>;
5
+ }
6
+ }
7
+
8
+ export {};
@@ -0,0 +1,6 @@
1
+ export const capitalize = (str: string) => {
2
+ if (!str) {
3
+ return;
4
+ }
5
+ return str.charAt(0).toLocaleUpperCase() + str.slice(1);
6
+ };
@@ -0,0 +1,16 @@
1
+ export const copyToClipboard = async (text: string) => {
2
+ if (navigator.clipboard) {
3
+ await navigator.clipboard.writeText(text);
4
+ return;
5
+ }
6
+
7
+ const textarea = document.createElement("textarea");
8
+ textarea.value = text;
9
+ textarea.style.position = "fixed";
10
+ textarea.style.opacity = "0";
11
+ document.body.appendChild(textarea);
12
+ textarea.focus();
13
+ textarea.select();
14
+ document.execCommand("copy");
15
+ document.body.removeChild(textarea);
16
+ };
@@ -0,0 +1,8 @@
1
+ export const testPlatform = (re: RegExp) => {
2
+ // @ts-ignore
3
+ const platform: string = window?.navigator?.userAgentData?.platform ?? window?.navigator?.platform;
4
+
5
+ return platform ? re.test(platform) : false;
6
+ };
7
+
8
+ export const isMac = testPlatform(/^Mac/i);
@@ -0,0 +1,8 @@
1
+ export const loadFromLocalStorage = <T>(key: string, defaultValue?: T): T => {
2
+ try {
3
+ const stored = localStorage.getItem(key);
4
+ return stored ? (JSON.parse(stored) as T) : defaultValue;
5
+ } catch {
6
+ return defaultValue;
7
+ }
8
+ };
@@ -0,0 +1,55 @@
1
+ import type { DefaultTreeGroup, DefaultTreeLeaf, TestStatus, TreeData } from "@allurereport/core-api";
2
+
3
+ export type Stats = Record<TestStatus, number> & { total?: number };
4
+
5
+ const emptyStat: Stats = { failed: 0, broken: 0, passed: 0, skipped: 0, unknown: 0 };
6
+
7
+ const statisticByIds = (tree: TreeData<DefaultTreeLeaf, DefaultTreeGroup>, ids: string[]): Stats => {
8
+ return ids
9
+ .map((id) => tree.leavesById[id])
10
+ .filter((v) => v)
11
+ .reduce(
12
+ (prev, current) => {
13
+ prev[current.status]++;
14
+ return prev;
15
+ },
16
+ { ...emptyStat },
17
+ );
18
+ };
19
+
20
+ const mergeStats = (stat1: Stats, stat2: Stats): Stats => {
21
+ Object.keys(stat1).forEach((key) => {
22
+ stat1[key as TestStatus] += stat2[key as TestStatus];
23
+ });
24
+ return stat1;
25
+ };
26
+
27
+ export const getGroupStatistics = (
28
+ tree: TreeData<DefaultTreeLeaf, DefaultTreeGroup>,
29
+ groupId: string,
30
+ statusFilter?: string,
31
+ ): Stats => {
32
+ const group = tree.groupsById[groupId];
33
+ let stat: Stats = { ...emptyStat };
34
+
35
+ if (group?.leaves?.length) {
36
+ stat = mergeStats(stat, statisticByIds(tree, group.leaves));
37
+ }
38
+
39
+ if (group?.groups?.length) {
40
+ for (const subGroupId of group.groups) {
41
+ const subGroupStat = getGroupStatistics(tree, subGroupId, statusFilter);
42
+ stat = mergeStats(stat, subGroupStat);
43
+ }
44
+ }
45
+
46
+ return stat;
47
+ };
48
+
49
+ export const getStatistics = (tree: TreeData<DefaultTreeLeaf, DefaultTreeGroup>) =>
50
+ tree.root.groups
51
+ ?.map((groupId) => {
52
+ const stat = getGroupStatistics(tree, groupId);
53
+ return { name: tree.groupsById[groupId].name, statistic: stat };
54
+ })
55
+ ?.sort((a, b) => a.name.localeCompare(b.name)) ?? [];
@@ -0,0 +1,17 @@
1
+ import { useI18n } from "@/stores/locale";
2
+
3
+ const defaultOptions: Intl.DateTimeFormatOptions = {
4
+ month: "numeric",
5
+ day: "numeric",
6
+ year: "numeric",
7
+ hour: "numeric",
8
+ minute: "numeric",
9
+ second: "numeric",
10
+ hour12: false,
11
+ };
12
+
13
+ export const timestampToDate = (timestamp: number, options = defaultOptions) => {
14
+ const date = new Date(timestamp);
15
+ const { t } = useI18n("ui");
16
+ return new Intl.DateTimeFormat("en-US", options).format(date).replace(",", ` ${t("at")}`);
17
+ };