@allurereport/web-awesome 3.0.0-beta.3 → 3.0.0-beta.5

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 (89) hide show
  1. package/.eslintrc.cjs +1 -1
  2. package/CONTRIBUTING.md +34 -0
  3. package/dist/multi/{141.app-b6362ca0.js → 141.app-71d7f77e.js} +1 -1
  4. package/dist/multi/222.app-71d7f77e.js +1 -0
  5. package/dist/multi/335.app-71d7f77e.js +1 -0
  6. package/dist/multi/{34.app-b6362ca0.js → 34.app-71d7f77e.js} +1 -1
  7. package/dist/multi/349.app-71d7f77e.js +1 -0
  8. package/dist/multi/378.app-71d7f77e.js +1 -0
  9. package/dist/multi/{406.app-b6362ca0.js → 406.app-71d7f77e.js} +1 -1
  10. package/dist/multi/476.app-71d7f77e.js +1 -0
  11. package/dist/multi/{53.app-b6362ca0.js → 53.app-71d7f77e.js} +1 -1
  12. package/dist/multi/{584.app-b6362ca0.js → 584.app-71d7f77e.js} +1 -1
  13. package/dist/multi/690.app-71d7f77e.js +1 -0
  14. package/dist/multi/{747.app-b6362ca0.js → 747.app-71d7f77e.js} +1 -1
  15. package/dist/multi/{767.app-b6362ca0.js → 767.app-71d7f77e.js} +1 -1
  16. package/dist/multi/{816.app-b6362ca0.js → 816.app-71d7f77e.js} +1 -1
  17. package/dist/multi/83.app-71d7f77e.js +1 -0
  18. package/dist/multi/{873.app-b6362ca0.js → 873.app-71d7f77e.js} +1 -1
  19. package/dist/multi/{920.app-b6362ca0.js → 920.app-71d7f77e.js} +1 -1
  20. package/dist/multi/{991.app-b6362ca0.js → 991.app-71d7f77e.js} +1 -1
  21. package/dist/multi/app-71d7f77e.js +2 -0
  22. package/dist/multi/manifest.json +20 -20
  23. package/dist/multi/{styles-b6362ca0.css → styles-71d7f77e.css} +6 -6
  24. package/dist/single/app-7aa8b012.js +2 -0
  25. package/dist/single/manifest.json +1 -1
  26. package/package.json +11 -4
  27. package/src/assets/scss/_common.scss +9 -0
  28. package/src/components/app/ArrowButton/index.tsx +3 -2
  29. package/src/components/app/BaseLayout/index.tsx +5 -5
  30. package/src/components/app/ReportBody/Filters.tsx +12 -10
  31. package/src/components/app/ReportBody/HeaderActions.tsx +3 -3
  32. package/src/components/app/ReportBody/SortBy.tsx +10 -10
  33. package/src/components/app/ReportBody/context.tsx +0 -1
  34. package/src/components/app/ReportHeader/index.tsx +1 -1
  35. package/src/components/app/Tabs/index.tsx +2 -3
  36. package/src/components/app/TestResult/TestResultDescription/index.tsx +3 -3
  37. package/src/components/app/TestResult/TestResultNavigation/index.tsx +34 -37
  38. package/src/components/app/TestResult/TestResultNavigation/styles.scss +1 -1
  39. package/src/components/app/TestResult/TestResultSteps/attachment.tsx +4 -6
  40. package/src/components/app/Tree/Tree.tsx +54 -101
  41. package/src/components/app/Tree/TreeHeader.tsx +13 -12
  42. package/src/components/app/Tree/TreeItem.tsx +3 -1
  43. package/src/components/app/Tree/index.tsx +31 -7
  44. package/src/components/app/Tree/styles.scss +9 -3
  45. package/src/components/commons/Menu/index.tsx +44 -19
  46. package/src/components/commons/SearchBox/index.tsx +8 -5
  47. package/src/components/commons/SuccessRatePieChart/styles.scss +0 -1
  48. package/src/components/commons/Toggle/index.tsx +3 -2
  49. package/src/components/commons/Tooltip/index.tsx +3 -3
  50. package/src/i18n/constants.ts +21 -2
  51. package/src/i18n/locales/am.json +3 -1
  52. package/src/i18n/locales/az.json +3 -1
  53. package/src/i18n/locales/de.json +3 -1
  54. package/src/i18n/locales/en.json +4 -2
  55. package/src/i18n/locales/es.json +3 -0
  56. package/src/i18n/locales/fr.json +3 -1
  57. package/src/i18n/locales/he.json +3 -1
  58. package/src/i18n/locales/it.json +3 -1
  59. package/src/i18n/locales/ja.json +3 -1
  60. package/src/i18n/locales/ka.json +3 -1
  61. package/src/i18n/locales/kr.json +3 -1
  62. package/src/i18n/locales/nl.json +3 -1
  63. package/src/i18n/locales/pl.json +3 -1
  64. package/src/i18n/locales/pt.json +3 -1
  65. package/src/i18n/locales/ru.json +3 -1
  66. package/src/i18n/locales/sv.json +3 -1
  67. package/src/i18n/locales/tr.json +3 -1
  68. package/src/i18n/locales/zh.json +4 -2
  69. package/src/index.html +1 -0
  70. package/src/stores/chart.ts +2 -2
  71. package/src/stores/testResults.ts +26 -4
  72. package/src/stores/tree.ts +98 -4
  73. package/src/types/globals.d.ts +6 -1
  74. package/src/utils/capitalize.ts +5 -3
  75. package/src/utils/treeFilters.ts +73 -120
  76. package/test/utils/treeFilters.test.ts +424 -0
  77. package/types.d.ts +25 -4
  78. package/vitest.config.ts +12 -0
  79. package/dist/multi/222.app-b6362ca0.js +0 -1
  80. package/dist/multi/335.app-b6362ca0.js +0 -1
  81. package/dist/multi/349.app-b6362ca0.js +0 -1
  82. package/dist/multi/378.app-b6362ca0.js +0 -1
  83. package/dist/multi/476.app-b6362ca0.js +0 -1
  84. package/dist/multi/690.app-b6362ca0.js +0 -1
  85. package/dist/multi/83.app-b6362ca0.js +0 -1
  86. package/dist/multi/app-b6362ca0.js +0 -2
  87. package/dist/single/app-57ae0a60.js +0 -2
  88. /package/dist/multi/{app-b6362ca0.js.LICENSE.txt → app-71d7f77e.js.LICENSE.txt} +0 -0
  89. /package/dist/single/{app-57ae0a60.js.LICENSE.txt → app-7aa8b012.js.LICENSE.txt} +0 -0
@@ -60,7 +60,9 @@
60
60
  "status-desc-short": "הפוך"
61
61
  },
62
62
  "empty": {
63
- "no-results": "לא נמצאו תוצאות",
63
+ "no-results": "אין תוצאות",
64
+ "no-tests-found": "לא נמצאו תוצאות",
65
+ "clear-filters": "נקה מסננים",
64
66
  "no-attachments-results": "לא נמצאה מידע על קבצים מצורפים",
65
67
  "no-history-results": "לא נמצאה מידע על היסטוריה",
66
68
  "no-retries-results": "לא נמצאה מידע על נסיונות חוזרים"
@@ -60,7 +60,9 @@
60
60
  "status-desc-short": "Invertito"
61
61
  },
62
62
  "empty": {
63
- "no-results": "Nessun risultato trovato",
63
+ "no-results": "Nessun risultato",
64
+ "no-tests-found": "Nessun risultato trovato",
65
+ "clear-filters": "Cancella i filtri",
64
66
  "no-attachments-results": "Nessuna informazione sugli allegati disponibile",
65
67
  "no-history-results": "Nessuna informazione sulla cronologia disponibile",
66
68
  "no-retries-results": "Nessuna informazione sui ritentativi disponibile"
@@ -60,7 +60,9 @@
60
60
  "status-desc-short": "逆順"
61
61
  },
62
62
  "empty": {
63
- "no-results": "結果が見つかりません",
63
+ "no-results": "結果がありません",
64
+ "no-tests-found": "結果が見つかりません",
65
+ "clear-filters": "フィルターをクリア",
64
66
  "no-attachments-results": "添付ファイル情報が利用できません",
65
67
  "no-history-results": "履歴情報が利用できません",
66
68
  "no-retries-results": "再試行情報が利用できません"
@@ -60,7 +60,9 @@
60
60
  "status-desc-short": "შებრუნებული"
61
61
  },
62
62
  "empty": {
63
- "no-results": "შედეგები არ მოიძებნა",
63
+ "no-results": "შედეგები არ არის",
64
+ "no-tests-found": "შედეგები არ მოიძებნა",
65
+ "clear-filters": "ფილტრების გასუფთავება",
64
66
  "no-attachments-results": "დანართების ინფორმაცია არ არის ხელმისაწვდომი",
65
67
  "no-history-results": "ისტორიის ინფორმაცია არ არის ხელმისაწვდომი",
66
68
  "no-retries-results": "ხელახალი ცდების ინფორმ���ცია არ არის ხელმისაწვდომი"
@@ -60,7 +60,9 @@
60
60
  "status-desc-short": "역순"
61
61
  },
62
62
  "empty": {
63
- "no-results": "결과를 찾을 수 없습니다",
63
+ "no-results": "결과 없음",
64
+ "no-tests-found": "결과를 찾을 수 없음",
65
+ "clear-filters": "필터 지우기",
64
66
  "no-attachments-results": "첨부파일 정보를 사용할 수 없습니다",
65
67
  "no-history-results": "기록 정보를 사용할 수 없습니다",
66
68
  "no-retries-results": "재시도 정보를 사용할 수 없습니다"
@@ -60,7 +60,9 @@
60
60
  "status-desc-short": "Omgekeerd"
61
61
  },
62
62
  "empty": {
63
- "no-results": "Geen resultaten gevonden",
63
+ "no-results": "Geen resultaten",
64
+ "no-tests-found": "Geen resultaten gevonden",
65
+ "clear-filters": "Filters wissen",
64
66
  "no-attachments-results": "Geen bijlageninformatie beschikbaar",
65
67
  "no-history-results": "Geen geschiedenisinformatie beschikbaar",
66
68
  "no-retries-results": "Geen herhalingsinformatie beschikbaar"
@@ -57,7 +57,9 @@
57
57
  "status-desc-short": "Domyślnie"
58
58
  },
59
59
  "empty": {
60
- "no-results": "Nie znaleziono wyników",
60
+ "no-results": "Brak wyników",
61
+ "no-tests-found": "Nie znaleziono wyników",
62
+ "clear-filters": "Wyczyść filtry",
61
63
  "no-attachments-results": "Brak dostępnych informacji o załącznikach",
62
64
  "no-history-results": "Brak dostępnych informacji o historii",
63
65
  "no-retries-results": "Brak dostępnych informacji o ponownych próbach"
@@ -60,7 +60,9 @@
60
60
  "status-desc-short": "Invertido"
61
61
  },
62
62
  "empty": {
63
- "no-results": "Nenhum resultado encontrado",
63
+ "no-results": "Sem resultados",
64
+ "no-tests-found": "Nenhum resultado encontrado",
65
+ "clear-filters": "Limpar filtros",
64
66
  "no-attachments-results": "Nenhuma informação de anexos disponível",
65
67
  "no-history-results": "Nenhuma informação de histórico disponível",
66
68
  "no-retries-results": "Nenhuma informação de repetições disponível"
@@ -57,7 +57,9 @@
57
57
  "status-desc-short": "По обычному"
58
58
  },
59
59
  "empty": {
60
- "no-results": "Результатов не найдено",
60
+ "no-results": "Нет результатов",
61
+ "no-tests-found": "Результаты не найдены",
62
+ "clear-filters": "Очистить фильтры",
61
63
  "no-attachments-results": "Информация о вложениях отсутствует",
62
64
  "no-history-results": "Информация об истории отсутствует",
63
65
  "no-retries-results": "Информация о перезапусках отсутствует"
@@ -60,7 +60,9 @@
60
60
  "status-desc-short": "Omvänd"
61
61
  },
62
62
  "empty": {
63
- "no-results": "Inga resultat hittades",
63
+ "no-results": "Inga resultat",
64
+ "no-tests-found": "Inga resultat hittades",
65
+ "clear-filters": "Rensa filter",
64
66
  "no-attachments-results": "Ingen bilaga information tillgänglig",
65
67
  "no-history-results": "Ingen historik information tillgänglig",
66
68
  "no-retries-results": "Ingen omtagningar information tillgänglig"
@@ -60,7 +60,9 @@
60
60
  "status-desc-short": "Ters"
61
61
  },
62
62
  "empty": {
63
- "no-results": "Sonuç bulunamadı",
63
+ "no-results": "Sonuç yok",
64
+ "no-tests-found": "Sonuç bulunamadı",
65
+ "clear-filters": "Filtreleri temizle",
64
66
  "no-attachments-results": "Ek bilgisi mevcut değil",
65
67
  "no-history-results": "Geçmiş bilgisi mevcut değil",
66
68
  "no-retries-results": "Tekrar deneme bilgisi mevcut değil"
@@ -60,7 +60,9 @@
60
60
  "status-desc-short": "反转"
61
61
  },
62
62
  "empty": {
63
- "no-results": "未找到结果",
63
+ "no-results": "没有结果",
64
+ "no-tests-found": "未找到结果",
65
+ "clear-filters": "清除过滤器",
64
66
  "no-attachments-results": "没有附件信息",
65
67
  "no-history-results": "没有历史信息",
66
68
  "no-retries-results": "没有重试信息"
@@ -112,4 +114,4 @@
112
114
  "errors": {
113
115
  "missedAttachment": "未找到附件"
114
116
  }
115
- }
117
+ }
package/src/index.html CHANGED
@@ -26,6 +26,7 @@
26
26
  theme: "light",
27
27
  reportLanguage: "ru",
28
28
  createdAt: 1731513697651,
29
+ groupBy: "suite",
29
30
  };
30
31
  </script>
31
32
  <script async>
@@ -21,12 +21,12 @@ export const fetchPieChartData = async () => {
21
21
  pieChartStore.value = {
22
22
  data: res,
23
23
  error: undefined,
24
- loading: false
24
+ loading: false,
25
25
  };
26
26
  } catch (err) {
27
27
  pieChartStore.value = {
28
28
  error: err.message,
29
- loading: false
29
+ loading: false,
30
30
  };
31
31
  }
32
32
  };
@@ -9,6 +9,30 @@ export const testResultStore = signal<StoreSignalState<Record<string, AllureAwes
9
9
  data: undefined,
10
10
  });
11
11
 
12
+ export const testResultNavStore = signal<StoreSignalState<string[]>>({
13
+ loading: true,
14
+ error: undefined,
15
+ data: undefined,
16
+ });
17
+
18
+ export const fetchTestResultNav = async () => {
19
+ try {
20
+ const data = await fetchReportJsonData<string[]>("widgets/nav.json");
21
+
22
+ testResultNavStore.value = {
23
+ data,
24
+ error: undefined,
25
+ loading: false,
26
+ };
27
+ } catch (err) {
28
+ testResultNavStore.value = {
29
+ ...testResultNavStore.value,
30
+ error: err.message,
31
+ loading: false,
32
+ };
33
+ }
34
+ };
35
+
12
36
  export const fetchTestResult = async (testResultId: string) => {
13
37
  if (!testResultId || testResultStore.value.data?.[testResultId]) {
14
38
  return;
@@ -18,12 +42,10 @@ export const fetchTestResult = async (testResultId: string) => {
18
42
  ...testResultStore.value,
19
43
  loading: true,
20
44
  error: undefined,
21
- }
45
+ };
22
46
 
23
47
  try {
24
- const data = await fetchReportJsonData<AllureAwesomeTestResult>(
25
- `data/test-results/${testResultId}.json`,
26
- );
48
+ const data = await fetchReportJsonData<AllureAwesomeTestResult>(`data/test-results/${testResultId}.json`);
27
49
 
28
50
  testResultStore.value = {
29
51
  data: { ...testResultStore.value.data, [testResultId]: data },
@@ -1,14 +1,108 @@
1
1
  import { fetchReportJsonData } from "@allurereport/web-commons";
2
- import { signal } from "@preact/signals";
2
+ import { computed, signal } from "@preact/signals";
3
3
  import type { StoreSignalState } from "@/stores/types";
4
+ import { createRecursiveTree, isRecursiveTreeEmpty } from "@/utils/treeFilters";
5
+ import type {AllureAwesomeStatus, AllureAwesomeTree, AllureAwesomeTreeGroup} from "../../types";
4
6
 
5
- export const treeStore = signal<StoreSignalState<any>>({
7
+ export type TreeSortBy = "order" | "duration" | "status" | "alphabet";
8
+ export type TreeDirection = "asc" | "desc";
9
+ export type TreeFilters = "flaky" | "retry" | "new";
10
+ export type TreeFiltersState = {
11
+ query: string;
12
+ status: AllureAwesomeStatus;
13
+ filter: Record<TreeFilters, boolean>;
14
+ sortBy: TreeSortBy;
15
+ direction: TreeDirection;
16
+ };
17
+
18
+ export const treeStore = signal<StoreSignalState<AllureAwesomeTree>>({
6
19
  loading: true,
7
20
  error: undefined,
8
21
  data: undefined,
9
22
  });
10
23
 
11
- export const fetchTreeData = async (treeName: string) => {
24
+ export const noTests = computed(() => !Object.keys(treeStore?.value?.data?.leavesById).length);
25
+
26
+ export const treeFiltersStore = signal<TreeFiltersState>({
27
+ query: "",
28
+ status: "total",
29
+ filter: {
30
+ flaky: false,
31
+ retry: false,
32
+ new: false,
33
+ },
34
+ sortBy: "order",
35
+ direction: "asc",
36
+ });
37
+
38
+ export const filteredTree = computed(() => {
39
+ const { root, leavesById, groupsById } = treeStore.value.data;
40
+
41
+ return createRecursiveTree({
42
+ group: root as AllureAwesomeTreeGroup,
43
+ leavesById,
44
+ groupsById,
45
+ filterOptions: treeFiltersStore.value,
46
+ });
47
+ });
48
+
49
+ export const noTestsFound = computed(() => {
50
+ return isRecursiveTreeEmpty(filteredTree.value);
51
+ });
52
+
53
+ export const clearTreeFilters = () => {
54
+ treeFiltersStore.value = {
55
+ query: "",
56
+ status: "total",
57
+ filter: {
58
+ flaky: false,
59
+ retry: false,
60
+ new: false,
61
+ },
62
+ sortBy: "order",
63
+ direction: "asc",
64
+ };
65
+ };
66
+
67
+ export const setTreeQuery = (query: string) => {
68
+ treeFiltersStore.value = {
69
+ ...treeFiltersStore.value,
70
+ query,
71
+ };
72
+ };
73
+
74
+ export const setTreeStatus = (status: AllureAwesomeStatus) => {
75
+ treeFiltersStore.value = {
76
+ ...treeFiltersStore.value,
77
+ status,
78
+ };
79
+ };
80
+
81
+ export const setTreeSortBy = (sortBy: TreeSortBy) => {
82
+ treeFiltersStore.value = {
83
+ ...treeFiltersStore.value,
84
+ sortBy,
85
+ };
86
+ };
87
+
88
+ export const setTreeDirection = (direction: TreeDirection) => {
89
+ treeFiltersStore.value = {
90
+ ...treeFiltersStore.value,
91
+ direction,
92
+ };
93
+ };
94
+
95
+ export const setTreeFilter = (filterKey: TreeFilters, value: boolean) => {
96
+ treeFiltersStore.value = {
97
+ ...treeFiltersStore.value,
98
+ filter: {
99
+ ...treeFiltersStore.value.filter,
100
+ [filterKey]: value,
101
+ },
102
+ };
103
+ };
104
+
105
+ export const fetchTreeData = async () => {
12
106
  treeStore.value = {
13
107
  ...treeStore.value,
14
108
  loading: true,
@@ -16,7 +110,7 @@ export const fetchTreeData = async (treeName: string) => {
16
110
  };
17
111
 
18
112
  try {
19
- const res = await fetchReportJsonData(`widgets/${treeName}.json`);
113
+ const res = await fetchReportJsonData<AllureAwesomeTree>("widgets/tree.json");
20
114
 
21
115
  treeStore.value = {
22
116
  data: res,
@@ -2,7 +2,12 @@ declare module "*.svg" {
2
2
  const content: {
3
3
  id: string;
4
4
  };
5
+
5
6
  export default content;
6
7
  }
7
8
 
8
- declare module "*.scss";
9
+ declare module "*.scss" {
10
+ const content: Record<string, string>;
11
+
12
+ export = content;
13
+ }
@@ -1,4 +1,6 @@
1
- export function capitalize(str: string) {
2
- if (!str) return;
1
+ export const capitalize = (str: string) => {
2
+ if (!str) {
3
+ return;
4
+ }
3
5
  return str.charAt(0).toLocaleUpperCase() + str.slice(1);
4
- }
6
+ };
@@ -1,5 +1,5 @@
1
- import type { DefaultTreeData } from "@allurereport/core-api";
2
- import type { ReportContentContextValue } from "@/components/app/ReportBody/context";
1
+ import type { TreeFiltersState } from "@/stores/tree";
2
+ import type { AllureAwesomeRecursiveTree, AllureAwesomeTree, AllureAwesomeTreeGroup } from "../../types";
3
3
 
4
4
  const statusOrder = {
5
5
  failed: 1,
@@ -9,142 +9,95 @@ const statusOrder = {
9
9
  unknown: 5,
10
10
  };
11
11
 
12
- const filterByQuery = ({
13
- leaves,
14
- leavesById,
15
- query,
16
- }: {
17
- leaves: string[];
18
- leavesById: DefaultTreeData["leavesById"];
19
- query: string;
20
- }) =>
21
- leaves.filter((leafId) => {
22
- const lowerCaseQuery = query.toLocaleLowerCase();
23
- const foundById = leavesById[leafId].nodeId.toLowerCase().includes(lowerCaseQuery);
24
- const foundByName = leavesById[leafId].name.toLowerCase().includes(lowerCaseQuery);
12
+ export const filterLeaves = (
13
+ leaves: string[] = [],
14
+ leavesById: AllureAwesomeTree["leavesById"],
15
+ filterOptions?: TreeFiltersState,
16
+ ) => {
17
+ const filteredLeaves = [...leaves]
18
+ .map((leafId) => leavesById[leafId])
19
+ .filter((leaf) => {
20
+ const queryMatched = !filterOptions?.query || leaf.name.toLowerCase().includes(filterOptions.query.toLowerCase());
21
+ const statusMatched =
22
+ !filterOptions?.status || filterOptions?.status === "total" || leaf.status === filterOptions.status;
23
+ const flakyMatched = !filterOptions?.filter?.flaky || leaf.flaky;
24
+ const retryMatched = !filterOptions?.filter?.retry || leaf.retry;
25
+ // TODO: at this moment we don't have a new field implementation even in the generator
26
+ // const newMatched = !filterOptions?.filter?.new || leaf.new;
27
+
28
+ return [queryMatched, statusMatched, flakyMatched, retryMatched].every(Boolean);
29
+ });
25
30
 
26
- return foundById || foundByName;
27
- });
31
+ if (!filterOptions) {
32
+ return filteredLeaves;
33
+ }
28
34
 
29
- const sortedLeaves = ({
30
- leavesFiltered,
31
- leavesById,
32
- filterOptions,
33
- }: {
34
- leavesFiltered: string[];
35
- leavesById: DefaultTreeData["leavesById"];
36
- filterOptions: ReportContentContextValue;
37
- }) => {
38
- leavesFiltered.sort((a, b) => {
39
- const leafA = leavesById[a];
40
- const leafB = leavesById[b];
41
- const filterDirection = filterOptions.direction === "asc";
35
+ return filteredLeaves.sort((a, b) => {
36
+ const asc = filterOptions.direction === "asc";
42
37
 
43
38
  switch (filterOptions.sortBy) {
39
+ case "order":
40
+ return asc ? a.groupOrder - b.groupOrder : b.groupOrder - a.groupOrder;
44
41
  case "duration":
45
- return filterDirection
46
- ? (leafA.duration || 0) - (leafB.duration || 0)
47
- : (leafB.duration || 0) - (leafA.duration || 0);
42
+ return asc ? (a.duration || 0) - (b.duration || 0) : (b.duration || 0) - (a.duration || 0);
48
43
  case "alphabet":
49
- return filterDirection ? leafA.name.localeCompare(leafB.name) : leafB.name.localeCompare(leafA.name);
44
+ return asc ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
50
45
  case "status": {
51
- const statusA = statusOrder[leafA.status] || statusOrder.unknown;
52
- const statusB = statusOrder[leafB.status] || statusOrder.unknown;
53
- return filterDirection ? statusA - statusB : statusB - statusA;
46
+ const statusA = statusOrder[a.status] || statusOrder.unknown;
47
+ const statusB = statusOrder[b.status] || statusOrder.unknown;
48
+
49
+ return asc ? statusA - statusB : statusB - statusA;
54
50
  }
55
51
  default:
56
52
  return 0;
57
53
  }
58
54
  });
59
-
60
- if (filterOptions.direction === "desc" && ["order"].includes(filterOptions.sortBy as string)) {
61
- leavesFiltered.reverse();
62
- }
63
-
64
- return leavesFiltered;
65
55
  };
66
56
 
67
- const filterByTestType = ({
68
- leaves,
69
- leavesById,
70
- filterTypes,
71
- }: {
72
- leaves: string[];
73
- leavesById: DefaultTreeData["leavesById"];
74
- filterTypes: ReportContentContextValue["filter"];
75
- }) => {
76
- return leaves.filter((leafId) => {
77
- const leaf = leavesById[leafId];
78
- return (!filterTypes.flaky || leaf.flaky) && (!filterTypes.retry || leaf.retry) && (!filterTypes.new || leaf.new);
79
- });
80
- };
81
-
82
- export const filterLeaves = (
83
- leaves: string[],
84
- leavesById: DefaultTreeData["leavesById"],
85
- statusFilter?: string,
86
- filterOptions?: ReportContentContextValue,
87
- ): string[] => {
88
- let leavesFiltered =
89
- statusFilter === "total" || !statusFilter
90
- ? filterByQuery({
91
- leaves,
57
+ /**
58
+ * Fills the given tree from generator and returns recursive tree which includes leaves data instead of their IDs
59
+ * Filters leaves when `filterOptions` property is provided
60
+ * @param payload
61
+ */
62
+ export const createRecursiveTree = (payload: {
63
+ group: AllureAwesomeTreeGroup;
64
+ groupsById: AllureAwesomeTree["groupsById"];
65
+ leavesById: AllureAwesomeTree["leavesById"];
66
+ filterOptions?: TreeFiltersState;
67
+ }): AllureAwesomeRecursiveTree => {
68
+ const { group, groupsById, leavesById, filterOptions } = payload;
69
+ const groupLeaves = group.leaves ?? [];
70
+
71
+ return {
72
+ ...group,
73
+ // FIXME: don't have any idea, why eslint marks next line as unsafe because it actually has a correct type
74
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
75
+ leaves: filterLeaves(groupLeaves, leavesById, filterOptions),
76
+ trees: group?.groups
77
+ ?.filter((groupId) => {
78
+ const subGroup = groupsById[groupId];
79
+
80
+ return subGroup?.leaves?.length || subGroup?.groups?.length;
81
+ })
82
+ ?.map((groupId) =>
83
+ createRecursiveTree({
84
+ group: groupsById[groupId],
85
+ groupsById,
92
86
  leavesById,
93
- query: filterOptions.query,
94
- })
95
- : leaves.filter((leafId) => leavesById[leafId].status === statusFilter);
96
-
97
- leavesFiltered = filterByQuery({
98
- leaves: leavesFiltered,
99
- leavesById,
100
- query: filterOptions.query,
101
- });
102
-
103
- leavesFiltered = filterByTestType({ leaves: leavesFiltered, leavesById, filterTypes: filterOptions.filter });
104
-
105
- leavesFiltered = sortedLeaves({ leavesFiltered, leavesById, filterOptions });
106
-
107
- return leavesFiltered;
87
+ filterOptions,
88
+ }),
89
+ ),
90
+ };
108
91
  };
109
92
 
110
- export const filterGroups = (
111
- groups: string[],
112
- groupsById: DefaultTreeData["groupsById"],
113
- leavesById: DefaultTreeData["leavesById"],
114
- statusFilter?: string,
115
- filterOptions?: ReportContentContextValue,
116
- ): string[] => {
117
- const gro = groups.filter((groupId) => {
118
- const group = groupsById?.[groupId];
119
- const groupLeaves = filterLeaves((group?.leaves as string[]) || [], leavesById, statusFilter, filterOptions);
120
- const filteredSubGroups = group?.groups?.filter((subGroupId: number) => {
121
- const subGroup = groupsById?.[subGroupId] as { leaves: string[]; groups: string[] };
122
- return (
123
- filterLeaves(subGroup?.leaves || [], leavesById, statusFilter, filterOptions).length > 0 ||
124
- filterGroups(subGroup?.groups || [], groupsById, leavesById, statusFilter, filterOptions).length > 0
125
- );
126
- });
127
-
128
- return groupLeaves.length > 0 || (filteredSubGroups && filteredSubGroups.length > 0);
129
- });
130
-
131
- const sortedGroups = gro.sort((a, b) => {
132
- const leafA = groupsById[a];
133
- const leafB = groupsById[b];
134
- const filterDirection = filterOptions.direction === "asc";
135
-
136
- switch (filterOptions.sortBy) {
137
- case "alphabet": {
138
- return filterDirection ? leafA.name.localeCompare(leafB.name) : leafB.name.localeCompare(leafA.name);
139
- }
140
- default:
141
- return 0;
142
- }
143
- });
93
+ export const isRecursiveTreeEmpty = (tree: AllureAwesomeRecursiveTree) => {
94
+ if (!tree.trees?.length && !tree.leaves?.length) {
95
+ return true;
96
+ }
144
97
 
145
- if (filterOptions.direction === "desc" && ["order"].includes(filterOptions.sortBy as string)) {
146
- sortedGroups.reverse();
98
+ if (tree.leaves?.length) {
99
+ return false;
147
100
  }
148
101
 
149
- return sortedGroups;
102
+ return tree.trees?.every((subTree) => isRecursiveTreeEmpty(subTree));
150
103
  };