@allurereport/web-awesome 3.9.0 → 3.11.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.
Files changed (125) hide show
  1. package/dist/multi/173.app-79d160f9989770266f96.js +1 -0
  2. package/dist/multi/174.app-79d160f9989770266f96.js +1 -0
  3. package/dist/multi/252.app-79d160f9989770266f96.js +1 -0
  4. package/dist/multi/282.app-79d160f9989770266f96.js +1 -0
  5. package/dist/multi/29.app-79d160f9989770266f96.js +1 -0
  6. package/dist/multi/310.app-79d160f9989770266f96.js +1 -0
  7. package/dist/multi/416.app-79d160f9989770266f96.js +1 -0
  8. package/dist/multi/507.app-79d160f9989770266f96.js +1 -0
  9. package/dist/multi/527.app-79d160f9989770266f96.js +1 -0
  10. package/dist/multi/600.app-79d160f9989770266f96.js +1 -0
  11. package/dist/multi/605.app-79d160f9989770266f96.js +1 -0
  12. package/dist/multi/638.app-79d160f9989770266f96.js +1 -0
  13. package/dist/multi/672.app-79d160f9989770266f96.js +1 -0
  14. package/dist/multi/686.app-79d160f9989770266f96.js +1 -0
  15. package/dist/multi/725.app-79d160f9989770266f96.js +1 -0
  16. package/dist/multi/741.app-79d160f9989770266f96.js +1 -0
  17. package/dist/multi/749.app-79d160f9989770266f96.js +1 -0
  18. package/dist/multi/755.app-79d160f9989770266f96.js +1 -0
  19. package/dist/multi/894.app-79d160f9989770266f96.js +1 -0
  20. package/dist/multi/943.app-79d160f9989770266f96.js +1 -0
  21. package/dist/multi/980.app-79d160f9989770266f96.js +1 -0
  22. package/dist/multi/app-79d160f9989770266f96.js +2 -0
  23. package/dist/multi/manifest.json +25 -25
  24. package/dist/multi/styles-b7f017f505b72e148e8b.css +58 -0
  25. package/dist/single/app-36af07268613f77d3ac5.js +2 -0
  26. package/dist/single/manifest.json +1 -1
  27. package/package.json +15 -20
  28. package/src/components/BaseLayout/styles.scss +3 -1
  29. package/src/components/Footer/FooterVersion.tsx +5 -10
  30. package/src/components/Footer/index.tsx +7 -1
  31. package/src/components/Footer/styles.scss +6 -0
  32. package/src/components/Header/CiInfo/index.tsx +17 -13
  33. package/src/components/HeaderControls/index.tsx +1 -3
  34. package/src/components/KeyboardShortcuts/styles.scss +5 -5
  35. package/src/components/MainReport/index.tsx +3 -9
  36. package/src/components/MainReport/styles.scss +1 -26
  37. package/src/components/Metadata/index.tsx +27 -6
  38. package/src/components/Metadata/styles.scss +12 -0
  39. package/src/components/ReportBody/index.tsx +3 -12
  40. package/src/components/ReportBody/styles.scss +0 -21
  41. package/src/components/ReportHeader/index.tsx +25 -13
  42. package/src/components/ReportMetadata/index.tsx +35 -4
  43. package/src/components/SideBySide/styles.scss +10 -0
  44. package/src/components/SplitLayout/index.tsx +2 -9
  45. package/src/components/SplitLayout/styles.scss +4 -7
  46. package/src/components/TestResult/TrOverview.tsx +9 -2
  47. package/src/components/TestResult/TrRetriesView/TrRetriesItem.tsx +27 -1
  48. package/src/components/TestResult/TrRetriesView/styles.scss +17 -7
  49. package/src/components/TestResult/TrSetup/index.tsx +1 -1
  50. package/src/components/TestResult/TrSteps/TrAttachment.tsx +2 -1
  51. package/src/components/TestResult/TrSteps/TrAttachmentInfo.tsx +11 -3
  52. package/src/components/TestResult/TrSteps/TrBodyItems.tsx +5 -2
  53. package/src/components/TestResult/TrSteps/TrStep.tsx +6 -2
  54. package/src/components/TestResult/TrSteps/index.tsx +8 -5
  55. package/src/components/TestResult/TrTeardown/index.tsx +1 -1
  56. package/src/components/TestResult/index.tsx +2 -3
  57. package/src/components/TestResult/styles.scss +0 -5
  58. package/src/components/Tree/index.tsx +21 -1
  59. package/src/locales/ar.json +1 -0
  60. package/src/locales/az.json +1 -0
  61. package/src/locales/de.json +1 -0
  62. package/src/locales/en.json +1 -0
  63. package/src/locales/es.json +1 -0
  64. package/src/locales/fr.json +1 -0
  65. package/src/locales/he.json +1 -0
  66. package/src/locales/hy.json +1 -0
  67. package/src/locales/it.json +1 -0
  68. package/src/locales/ja.json +1 -0
  69. package/src/locales/ka.json +1 -0
  70. package/src/locales/kr.json +1 -0
  71. package/src/locales/nl.json +1 -0
  72. package/src/locales/pl.json +1 -0
  73. package/src/locales/pt.json +1 -0
  74. package/src/locales/ru.json +1 -0
  75. package/src/locales/sv.json +1 -0
  76. package/src/locales/tr.json +1 -0
  77. package/src/locales/uk.json +1 -0
  78. package/src/locales/zh-TW.json +1 -0
  79. package/src/locales/zh.json +1 -0
  80. package/src/stores/locale.ts +4 -2
  81. package/src/stores/treeSort.ts +7 -1
  82. package/src/styles/_pane-active.scss +2 -2
  83. package/src/utils/atSeparator.ts +4 -0
  84. package/src/utils/time.ts +2 -1
  85. package/src/utils/treeFilters.ts +15 -4
  86. package/test/components/Footer.test.tsx +26 -0
  87. package/test/components/Header/CiInfo.test.tsx +48 -0
  88. package/test/components/HeaderControls.test.tsx +28 -0
  89. package/test/components/ReportHeader.test.tsx +77 -0
  90. package/test/components/ReportMetadata.test.tsx +131 -0
  91. package/test/components/TestResult/TrRetriesItem.test.tsx +163 -0
  92. package/test/components/TestResult/TrSteps.test.tsx +45 -10
  93. package/test/stores/treeSort.test.ts +58 -0
  94. package/test/utils/time.test.ts +52 -0
  95. package/test/utils/treeFilters.test.ts +104 -0
  96. package/types.d.ts +22 -0
  97. package/webpack.config.js +9 -7
  98. package/dist/multi/173.app-d36b0855e3e7a53eeee9.js +0 -1
  99. package/dist/multi/174.app-d36b0855e3e7a53eeee9.js +0 -1
  100. package/dist/multi/252.app-d36b0855e3e7a53eeee9.js +0 -1
  101. package/dist/multi/282.app-d36b0855e3e7a53eeee9.js +0 -1
  102. package/dist/multi/29.app-d36b0855e3e7a53eeee9.js +0 -1
  103. package/dist/multi/310.app-d36b0855e3e7a53eeee9.js +0 -1
  104. package/dist/multi/416.app-d36b0855e3e7a53eeee9.js +0 -1
  105. package/dist/multi/507.app-d36b0855e3e7a53eeee9.js +0 -1
  106. package/dist/multi/527.app-d36b0855e3e7a53eeee9.js +0 -1
  107. package/dist/multi/600.app-d36b0855e3e7a53eeee9.js +0 -1
  108. package/dist/multi/605.app-d36b0855e3e7a53eeee9.js +0 -1
  109. package/dist/multi/638.app-d36b0855e3e7a53eeee9.js +0 -1
  110. package/dist/multi/672.app-d36b0855e3e7a53eeee9.js +0 -1
  111. package/dist/multi/686.app-d36b0855e3e7a53eeee9.js +0 -1
  112. package/dist/multi/725.app-d36b0855e3e7a53eeee9.js +0 -1
  113. package/dist/multi/741.app-d36b0855e3e7a53eeee9.js +0 -1
  114. package/dist/multi/749.app-d36b0855e3e7a53eeee9.js +0 -1
  115. package/dist/multi/755.app-d36b0855e3e7a53eeee9.js +0 -1
  116. package/dist/multi/894.app-d36b0855e3e7a53eeee9.js +0 -1
  117. package/dist/multi/943.app-d36b0855e3e7a53eeee9.js +0 -1
  118. package/dist/multi/980.app-d36b0855e3e7a53eeee9.js +0 -1
  119. package/dist/multi/app-d36b0855e3e7a53eeee9.js +0 -2
  120. package/dist/multi/styles-468416ffee9a9dea6cae.css +0 -58
  121. package/dist/single/app-62171f5f51b5954a787c.js +0 -2
  122. /package/dist/multi/{121.app-d36b0855e3e7a53eeee9.js → 121.app-79d160f9989770266f96.js} +0 -0
  123. /package/dist/multi/{779.app-d36b0855e3e7a53eeee9.js → 779.app-79d160f9989770266f96.js} +0 -0
  124. /package/dist/multi/{app-d36b0855e3e7a53eeee9.js.LICENSE.txt → app-79d160f9989770266f96.js.LICENSE.txt} +0 -0
  125. /package/dist/single/{app-62171f5f51b5954a787c.js.LICENSE.txt → app-36af07268613f77d3ac5.js.LICENSE.txt} +0 -0
@@ -1,3 +1,3 @@
1
1
  {
2
- "main.js": "app-62171f5f51b5954a787c.js"
2
+ "main.js": "app-36af07268613f77d3ac5.js"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allurereport/web-awesome",
3
- "version": "3.9.0",
3
+ "version": "3.11.0",
4
4
  "description": "The static files for Allure Awesome Report",
5
5
  "keywords": [
6
6
  "allure",
@@ -30,11 +30,11 @@
30
30
  "lint:fix": "oxlint --import-plugin --fix src test features stories"
31
31
  },
32
32
  "dependencies": {
33
- "@allurereport/charts-api": "3.9.0",
34
- "@allurereport/core-api": "3.9.0",
35
- "@allurereport/plugin-api": "3.9.0",
36
- "@allurereport/web-commons": "3.9.0",
37
- "@allurereport/web-components": "3.9.0",
33
+ "@allurereport/charts-api": "3.11.0",
34
+ "@allurereport/core-api": "3.11.0",
35
+ "@allurereport/plugin-api": "3.11.0",
36
+ "@allurereport/web-commons": "3.11.0",
37
+ "@allurereport/web-components": "3.11.0",
38
38
  "@preact/signals": "^2.6.1",
39
39
  "clsx": "^2.1.1",
40
40
  "d3-shape": "^3.2.0",
@@ -58,12 +58,12 @@
58
58
  "@types/babel__core": "^7.20.5",
59
59
  "@types/d3-shape": "^3.1.6",
60
60
  "@types/md5": "^2.3.5",
61
- "@types/node": "^20.17.9",
61
+ "@types/node": "^20",
62
62
  "@types/prismjs": "^1.26.5",
63
63
  "@vitest/runner": "^3.2.4",
64
64
  "@vitest/snapshot": "^3.2.4",
65
- "allure-js-commons": "^3.3.3",
66
- "allure-vitest": "^3.3.2",
65
+ "allure-js-commons": "^3",
66
+ "allure-vitest": "^3",
67
67
  "autoprefixer": "^10.4.20",
68
68
  "babel-loader": "^9.2.1",
69
69
  "babel-plugin-prismjs": "^2.1.0",
@@ -76,25 +76,20 @@
76
76
  "jsdom": "^26.1.0",
77
77
  "mini-css-extract-plugin": "^2.9.1",
78
78
  "npm-run-all2": "^7.0.1",
79
- "postcss": "^8.5.6",
80
- "rimraf": "^6.0.1",
79
+ "postcss": "^8.5.10",
80
+ "rimraf": "^6",
81
81
  "sass": "^1.79.1",
82
82
  "sass-loader": "^16.0.1",
83
83
  "split.js": "^1.6.5",
84
84
  "style-loader": "^4.0.0",
85
85
  "svg-sprite-loader": "^6.0.11",
86
86
  "terser-webpack-plugin": "^5.3.14",
87
- "typescript": "^5.6.3",
87
+ "typescript": "^5",
88
88
  "vite": "^8.0.9",
89
- "vitest": "^3.2.4",
89
+ "vitest": "^4.1.0",
90
90
  "webpack": "^5.99.9",
91
91
  "webpack-cli": "^5.1.4",
92
- "webpack-dev-server": "^5.2.2",
92
+ "webpack-dev-server": "^5.2.4",
93
93
  "webpack-manifest-plugin": "^5.0.0"
94
- },
95
- "browserslist": [
96
- "last 1 version",
97
- "> 1%",
98
- "IE 11"
99
- ]
94
+ }
100
95
  }
@@ -12,8 +12,10 @@
12
12
  .wrapper {
13
13
  max-width: 920px;
14
14
  width: 100%;
15
+ display: flex;
15
16
  flex-direction: column;
16
- margin: auto;
17
+ margin: 0 auto;
18
+ min-height: 100%;
17
19
  }
18
20
 
19
21
  .content {
@@ -3,11 +3,13 @@ import { Text } from "@allurereport/web-components";
3
3
  import { useState } from "preact/hooks";
4
4
  import type { AwesomeReportOptions } from "types";
5
5
 
6
- import { currentLocaleIso } from "@/stores";
6
+ import { useI18n } from "@/stores";
7
+ import { timestampToDate } from "@/utils/time";
7
8
 
8
9
  import * as styles from "./styles.scss";
9
10
 
10
11
  export const FooterVersion = () => {
12
+ const { t } = useI18n("ui");
11
13
  const [createdAt] = useState(() => {
12
14
  const reportOptions = getReportOptions<AwesomeReportOptions>();
13
15
  if (reportOptions?.createdAt) {
@@ -27,18 +29,11 @@ export const FooterVersion = () => {
27
29
  return undefined;
28
30
  });
29
31
 
30
- const formattedCreatedAt = new Date(createdAt as number).toLocaleDateString(currentLocaleIso.value as string, {
31
- month: "numeric",
32
- day: "numeric",
33
- year: "numeric",
34
- hour: "numeric",
35
- minute: "numeric",
36
- second: "numeric",
37
- });
32
+ const formattedCreatedAt = timestampToDate(createdAt as number);
38
33
 
39
34
  return (
40
35
  <Text type="paragraph" size="m" className={styles.version}>
41
- {formattedCreatedAt}
36
+ {t("generated")} {formattedCreatedAt}
42
37
  {currentVersion && <span> Ver: {currentVersion}</span>}
43
38
  </Text>
44
39
  );
@@ -1,10 +1,13 @@
1
+ import { LanguagePicker } from "@allurereport/web-components";
1
2
  import type { ClassValue } from "clsx";
2
3
  import { clsx } from "clsx";
3
4
 
4
5
  import { FooterLogo } from "@/components/Footer/FooterLogo";
5
6
  import { FooterVersion } from "@/components/Footer/FooterVersion";
7
+ import { currentLocale, setLocale } from "@/stores/locale";
6
8
 
7
9
  import * as styles from "@/components/BaseLayout/styles.scss";
10
+ import * as footerStyles from "@/components/Footer/styles.scss";
8
11
 
9
12
  interface FooterProps {
10
13
  className?: ClassValue;
@@ -13,7 +16,10 @@ export const Footer = ({ className }: FooterProps) => {
13
16
  return (
14
17
  <div className={clsx(styles.below, className)}>
15
18
  <FooterLogo />
16
- <FooterVersion />
19
+ <div className={footerStyles["footer-controls"]}>
20
+ <LanguagePicker locale={currentLocale.value} setLocale={setLocale} />
21
+ <FooterVersion />
22
+ </div>
17
23
  </div>
18
24
  );
19
25
  };
@@ -12,3 +12,9 @@
12
12
  .version {
13
13
  color: var(--color-text-muted);
14
14
  }
15
+
16
+ .footer-controls {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: 8px;
20
+ }
@@ -13,7 +13,7 @@ interface CiInfoProps {
13
13
  }
14
14
 
15
15
  interface CiIconProps {
16
- type: CiDescriptor["type"];
16
+ type?: CiDescriptor["type"];
17
17
  }
18
18
 
19
19
  export const CiIcon = ({ type }: CiIconProps) => {
@@ -47,24 +47,20 @@ export const CiIcon = ({ type }: CiIconProps) => {
47
47
  export const CiInfo = ({ className }: CiInfoProps) => {
48
48
  const { ci } = getReportOptions<AwesomeReportOptions>();
49
49
 
50
- if (!ci) {
51
- return null;
52
- }
50
+ const ciLink = ci ? ci.pullRequestUrl || ci.jobRunUrl || ci.jobUrl : undefined;
51
+ const ciLabel = getCiLabel(ci, ciLink);
52
+ const safeLink = sanitizeExternalUrl(ciLink);
53
53
 
54
- const link = ci.pullRequestUrl || ci.jobRunUrl || ci.jobUrl;
55
- const safeLink = sanitizeExternalUrl(link);
56
- const label = ci.pullRequestName || ci.jobRunName || ci.jobName || link;
57
-
58
- if (!link) {
54
+ if (!ciLabel) {
59
55
  return null;
60
56
  }
61
57
 
62
58
  if (!safeLink) {
63
59
  return (
64
60
  <span className={clsx(styles["ci-info"], className)}>
65
- <CiIcon type={ci.type} />
61
+ <CiIcon type={ci?.type} />
66
62
  <Text type="paragraph" size="m" bold>
67
- {label}
63
+ {ciLabel}
68
64
  </Text>
69
65
  </span>
70
66
  );
@@ -72,10 +68,18 @@ export const CiInfo = ({ className }: CiInfoProps) => {
72
68
 
73
69
  return (
74
70
  <a className={clsx(styles["ci-info"], className)} href={safeLink} target="_blank" rel="noopener noreferrer">
75
- <CiIcon type={ci.type} />
71
+ <CiIcon type={ci?.type} />
76
72
  <Text type="paragraph" size="m" bold>
77
- {label}
73
+ {ciLabel}
78
74
  </Text>
79
75
  </a>
80
76
  );
81
77
  };
78
+
79
+ const getCiLabel = (ci?: CiDescriptor, link?: string) => {
80
+ if (!ci) {
81
+ return undefined;
82
+ }
83
+
84
+ return ci.pullRequestName || ci.jobRunName || ci.jobName || link;
85
+ };
@@ -1,10 +1,9 @@
1
1
  import { themeStore, toggleUserTheme } from "@allurereport/web-commons";
2
- import { LanguagePicker, ThemeButton } from "@allurereport/web-components";
2
+ import { ThemeButton } from "@allurereport/web-components";
3
3
  import { computed } from "@preact/signals";
4
4
 
5
5
  import { EnvironmentPicker } from "@/components/EnvironmentPicker";
6
6
  import ToggleLayout from "@/components/ToggleLayout";
7
- import { currentLocale, setLocale } from "@/stores/locale";
8
7
 
9
8
  interface HeaderControlsProps {
10
9
  className?: string;
@@ -16,7 +15,6 @@ export const HeaderControls = ({ className }: HeaderControlsProps) => {
16
15
  return (
17
16
  <div className={className}>
18
17
  <EnvironmentPicker />
19
- <LanguagePicker locale={currentLocale.value} setLocale={setLocale} />
20
18
  <ToggleLayout />
21
19
  <ThemeButton theme={selectedTheme.value} toggleTheme={toggleUserTheme} />
22
20
  </div>
@@ -22,8 +22,8 @@
22
22
  overflow: auto;
23
23
  padding: 12px 16px;
24
24
  border-radius: 8px;
25
- border: 1px solid var(--on-border-muted);
26
- background: var(--bg-base-primary);
25
+ border: 1px solid var(--color-border-default);
26
+ background: var(--color-bg-raised);
27
27
  box-shadow: 0 8px 24px rgb(0 0 0 / 16%);
28
28
  }
29
29
 
@@ -43,7 +43,7 @@
43
43
 
44
44
  .groupTitle {
45
45
  margin-bottom: 6px;
46
- color: var(--on-text-secondary);
46
+ color: var(--color-text-secondary);
47
47
  }
48
48
 
49
49
  .item {
@@ -58,8 +58,8 @@
58
58
  flex-shrink: 0;
59
59
  font-family: var(--font-family-mono);
60
60
  font-size: var(--font-size-xs);
61
- color: var(--on-text-secondary);
62
- background: var(--bg-base-secondary);
61
+ color: var(--color-text-secondary);
62
+ background: var(--color-bg-neutral);
63
63
  border-radius: 4px;
64
64
  padding: 2px 6px;
65
65
  }
@@ -13,7 +13,7 @@ import { reportStatsStore, useI18n } from "@/stores";
13
13
  import { categoriesStore } from "@/stores/categories";
14
14
  import { currentEnvironment } from "@/stores/env";
15
15
  import { globalsStore } from "@/stores/globals";
16
- import { activePane, focusTreePane } from "@/stores/keyboard";
16
+ import { focusTreePane } from "@/stores/keyboard";
17
17
  import { isSplitMode } from "@/stores/layout";
18
18
  import { qualityGateStore } from "@/stores/qualityGate";
19
19
  import {
@@ -129,15 +129,9 @@ const MainReport = () => {
129
129
  return null;
130
130
  };
131
131
 
132
- const treePaneActive = isSplitMode.value && activePane.value === "tree";
133
-
134
132
  return (
135
133
  <div
136
- className={clsx(
137
- styles.content,
138
- isSplitMode.value && styles["scroll-inside"],
139
- treePaneActive && styles["pane-active"],
140
- )}
134
+ className={clsx(styles.content, isSplitMode.value && styles["scroll-inside"])}
141
135
  onMouseDown={() => focusTreePane()}
142
136
  >
143
137
  <div className={styles["main-report-header"]}>
@@ -146,7 +140,7 @@ const MainReport = () => {
146
140
  <div className={styles["main-report-tabs"]}>
147
141
  <NavTabs initialTab={initialTab}>
148
142
  <RootTabRouteSync />
149
- <div className={styles["main-report-tabs-layout"]}>
143
+ <div>
150
144
  <div className={styles["main-report-tabs-nav"]}>
151
145
  <NavTabsList>
152
146
  <Loadable
@@ -6,20 +6,13 @@
6
6
  background: var(--color-bg-primary);
7
7
  border-radius: 12px;
8
8
  width: 100%;
9
+ flex: 1 1 auto;
9
10
  }
10
11
 
11
12
  .scroll-inside {
12
13
  display: flex;
13
14
  flex-direction: column;
14
- overflow: hidden;
15
- height: 100%;
16
- min-height: 0;
17
15
  border-radius: 0;
18
- @include paneActive.split-pane-indicator;
19
-
20
- &.pane-active {
21
- @include paneActive.split-pane-indicator-active;
22
- }
23
16
  }
24
17
 
25
18
  .main-report-header {
@@ -27,20 +20,7 @@
27
20
  }
28
21
 
29
22
  .main-report-tabs {
30
- display: flex;
31
- flex: 1 1 auto;
32
- flex-direction: column;
33
- min-height: 0;
34
23
  padding: 0 24px;
35
- overflow: hidden;
36
- }
37
-
38
- .main-report-tabs-layout {
39
- display: flex;
40
- flex: 1 1 auto;
41
- flex-direction: column;
42
- min-height: 0;
43
- overflow: hidden;
44
24
  }
45
25
 
46
26
  .main-report-tabs-nav {
@@ -48,10 +28,5 @@
48
28
  }
49
29
 
50
30
  .main-report-tabs-content {
51
- display: flex;
52
- flex: 1 1 auto;
53
- flex-direction: column;
54
- min-height: 0;
55
- overflow: hidden;
56
31
  border-top: 1px solid var(--color-border-default);
57
32
  }
@@ -1,4 +1,5 @@
1
- import { Button, ButtonLink, Menu, Text, allureIcons } from "@allurereport/web-components";
1
+ import { sanitizeExternalUrl } from "@allurereport/core-api";
2
+ import { Button, ButtonLink, Menu, SvgIcon, Text, allureIcons } from "@allurereport/web-components";
2
3
  import clsx from "clsx";
3
4
  import type { FunctionalComponent } from "preact";
4
5
  import { useState } from "preact/hooks";
@@ -23,8 +24,8 @@ export const MetadataList: FunctionalComponent<MetadataProps & { columns?: numbe
23
24
  style={{ gridTemplateColumns: `repeat(${columns}, ${100 / columns - 5}%)` }}
24
25
  data-testid={"metadata-list"}
25
26
  >
26
- {envInfo?.map(({ name, values, value }) => (
27
- <MetadataKeyValue key={name} size={size} title={name} value={value} values={values} />
27
+ {envInfo?.map(({ name, values, value, url }, index) => (
28
+ <MetadataKeyValue key={`${name}-${index}`} size={size} title={name} value={value} values={values} url={url} />
28
29
  ))}
29
30
  </div>
30
31
  );
@@ -160,12 +161,31 @@ const MetaDataOwnerLabel: FunctionalComponent<{
160
161
  const MetaDataKeyLabel: FunctionalComponent<{
161
162
  name: string;
162
163
  size?: "s" | "m";
164
+ url?: string;
163
165
  value: string;
164
- }> = ({ name, size = "s", value }) => {
166
+ }> = ({ name, size = "s", url, value }) => {
165
167
  if (name === "owner") {
166
168
  return <MetaDataOwnerLabel value={value} size={size} />;
167
169
  }
168
170
 
171
+ const safeUrl = sanitizeExternalUrl(url);
172
+
173
+ if (safeUrl) {
174
+ return (
175
+ <a
176
+ className={clsx(styles["report-metadata-keyvalue-wrapper"], styles["report-metadata-keyvalue-link"])}
177
+ href={safeUrl}
178
+ target="_blank"
179
+ rel="noopener noreferrer"
180
+ >
181
+ <Text type={"ui"} size={size} bold className={styles["report-metadata-keyvalue-value"]}>
182
+ {value}
183
+ </Text>
184
+ <SvgIcon id={allureIcons.lineGeneralLinkExternal} size="s" />
185
+ </a>
186
+ );
187
+ }
188
+
169
189
  return (
170
190
  <Menu
171
191
  size="xl"
@@ -186,10 +206,11 @@ const MetaDataKeyLabel: FunctionalComponent<{
186
206
 
187
207
  const MetadataKeyValue: FunctionalComponent<{
188
208
  title: string;
209
+ url?: string;
189
210
  value?: string;
190
211
  values?: string[];
191
212
  size?: "s" | "m";
192
- }> = ({ title, value, values, size = "m" }) => {
213
+ }> = ({ title, url, value, values, size = "m" }) => {
193
214
  return (
194
215
  <div className={styles["report-metadata-keyvalue"]} data-testid={"metadata-item"}>
195
216
  <Text
@@ -208,7 +229,7 @@ const MetadataKeyValue: FunctionalComponent<{
208
229
  </div>
209
230
  ) : (
210
231
  <div className={styles["report-metadata-values"]} data-testid={"metadata-item-value"}>
211
- <MetaDataKeyLabel value={value ?? ""} name={title} />
232
+ <MetaDataKeyLabel value={value ?? ""} name={title} url={url} />
212
233
  </div>
213
234
  )}
214
235
  </div>
@@ -85,6 +85,18 @@
85
85
  line-height: 16px;
86
86
  }
87
87
 
88
+ .report-metadata-keyvalue-link {
89
+ display: inline-flex;
90
+ align-items: center;
91
+ gap: 4px;
92
+ color: inherit;
93
+ text-decoration: none;
94
+
95
+ &:hover {
96
+ text-decoration: underline;
97
+ }
98
+ }
99
+
88
100
  .report-metadata-keyvalue-value {
89
101
  width: max-content;
90
102
  height: max-content;
@@ -1,10 +1,8 @@
1
1
  import { capitalize, statusesList } from "@allurereport/core-api";
2
2
  import { Counter, Loadable } from "@allurereport/web-components";
3
- import clsx from "clsx";
4
3
 
5
4
  import { reportStatsStore, statsByEnvStore } from "@/stores";
6
5
  import { currentEnvironment } from "@/stores/env";
7
- import { isSplitMode } from "@/stores/layout";
8
6
  import { useI18n } from "@/stores/locale";
9
7
  import { setTreeStatus, treeStatus } from "@/stores/treeFilters/store";
10
8
 
@@ -22,7 +20,7 @@ const Header = () => {
22
20
  const { t } = useI18n("statuses");
23
21
 
24
22
  return (
25
- <header className={styles.header}>
23
+ <header className={styles.header} data-tree-sticky-header>
26
24
  <HeaderActions />
27
25
  <div className={styles.headerRow}>
28
26
  <ReportTabsList>
@@ -64,24 +62,17 @@ const Header = () => {
64
62
  };
65
63
 
66
64
  const Body = () => {
67
- const split = isSplitMode.value;
68
-
69
65
  return (
70
- <div
71
- className={clsx(styles.body, split && styles["body-split"])}
72
- {...(split ? { "data-tree-scroll-container": true } : {})}
73
- >
66
+ <div className={styles.body}>
74
67
  <TreeList />
75
68
  </div>
76
69
  );
77
70
  };
78
71
 
79
72
  export const ReportBody = () => {
80
- const split = isSplitMode.value;
81
-
82
73
  return (
83
74
  <ReportContentProvider>
84
- <section className={clsx(split && styles.split)}>
75
+ <section>
85
76
  <Header />
86
77
  <Body />
87
78
  </section>
@@ -1,11 +1,3 @@
1
- .split {
2
- display: flex;
3
- flex: 1 1 auto;
4
- flex-direction: column;
5
- min-height: 0;
6
- overflow: hidden;
7
- }
8
-
9
1
  .header {
10
2
  padding: 24px 0 0;
11
3
  display: flex;
@@ -18,19 +10,6 @@
18
10
  z-index: 10;
19
11
  }
20
12
 
21
- .split .header {
22
- position: static;
23
- z-index: auto;
24
- flex-shrink: 0;
25
- }
26
-
27
- .split .body-split {
28
- flex: 1 1 auto;
29
- min-height: 0;
30
- overflow: auto;
31
- scrollbar-width: thin;
32
- }
33
-
34
13
  .headerRow {
35
14
  display: flex;
36
15
  flex-direction: row;
@@ -1,3 +1,4 @@
1
+ import { formatDuration } from "@allurereport/core-api";
1
2
  import { getReportOptions } from "@allurereport/web-commons";
2
3
  import { Heading, Loadable, Text, TooltipWrapper } from "@allurereport/web-components";
3
4
  import type { AwesomeReportOptions } from "types";
@@ -5,22 +6,28 @@ import type { AwesomeReportOptions } from "types";
5
6
  import { ReportHeaderLogo } from "@/components/ReportHeader/ReportHeaderLogo";
6
7
  import { ReportHeaderPie } from "@/components/ReportHeader/ReportHeaderPie";
7
8
  import { TrStatus } from "@/components/TestResult/TrStatus";
8
- import { currentLocaleIso, useI18n } from "@/stores";
9
+ import { useI18n } from "@/stores";
9
10
  import { globalsStore } from "@/stores/globals";
11
+ import { timestampToDate } from "@/utils/time";
10
12
 
11
13
  import * as styles from "./styles.scss";
12
14
 
15
+ const reportDateOptions: Intl.DateTimeFormatOptions = {
16
+ month: "long",
17
+ day: "numeric",
18
+ year: "numeric",
19
+ hour: "numeric",
20
+ minute: "numeric",
21
+ second: "numeric",
22
+ };
23
+
13
24
  export const ReportHeader = () => {
14
- const { reportName, createdAt } = getReportOptions<AwesomeReportOptions>() ?? {};
25
+ const { reportName, createdAt, runSummary } = getReportOptions<AwesomeReportOptions>() ?? {};
15
26
  const { t } = useI18n("ui");
16
- const formattedCreatedAt = new Date(createdAt as number).toLocaleDateString(currentLocaleIso.value as string, {
17
- month: "long",
18
- day: "numeric",
19
- year: "numeric",
20
- hour: "numeric",
21
- minute: "numeric",
22
- second: "numeric",
23
- });
27
+ const formattedCreatedAt = timestampToDate(createdAt as number, reportDateOptions);
28
+ const formattedReportTime = runSummary
29
+ ? `${timestampToDate(runSummary.start, reportDateOptions)} (${formatDuration(runSummary.duration)})`
30
+ : formattedCreatedAt;
24
31
 
25
32
  return (
26
33
  <div className={styles["report-header"]}>
@@ -41,14 +48,19 @@ export const ReportHeader = () => {
41
48
  </div>
42
49
  <Text type="paragraph" size="m" className={styles["report-date"]} data-testid="report-data">
43
50
  {code === undefined
44
- ? formattedCreatedAt
51
+ ? formattedReportTime
45
52
  : exitCode.actual !== undefined
46
53
  ? t("finishedAtBoth", {
47
- formattedCreatedAt,
54
+ // Keep the existing i18n parameter name; the value can now be either a timestamp or run interval.
55
+ formattedCreatedAt: formattedReportTime,
48
56
  actual: exitCode.actual,
49
57
  original: exitCode.original,
50
58
  })
51
- : t("finishedAtOriginal", { formattedCreatedAt, original: exitCode.original })}
59
+ : t("finishedAtOriginal", {
60
+ // Keep the existing i18n parameter name; the value can now be either a timestamp or run interval.
61
+ formattedCreatedAt: formattedReportTime,
62
+ original: exitCode.original,
63
+ })}
52
64
  </Text>
53
65
  </div>
54
66
  );