@allurereport/plugin-awesome 3.2.0 → 3.4.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.
@@ -0,0 +1,13 @@
1
+ import type { CategoryDefinition } from "@allurereport/core-api";
2
+ import type { AwesomeTestResult } from "@allurereport/web-awesome";
3
+ import type { AwesomeDataWriter } from "./writer.js";
4
+ export declare const applyCategoriesToTestResults: (tests: AwesomeTestResult[], categories: CategoryDefinition[]) => void;
5
+ export declare const generateCategories: (writer: AwesomeDataWriter, { tests, categories, filename, environmentCount, selectedEnvironmentCount, environments, defaultEnvironment, }: {
6
+ tests: AwesomeTestResult[];
7
+ categories: CategoryDefinition[];
8
+ filename?: string;
9
+ environmentCount?: number;
10
+ selectedEnvironmentCount?: number;
11
+ environments?: string[];
12
+ defaultEnvironment?: string;
13
+ }) => Promise<void>;
@@ -0,0 +1,219 @@
1
+ import { EMPTY_VALUE, buildEnvironmentSortOrder, compareChildNodes, extractErrorMatchingData, findLastByLabelName, incrementStatistic, matchCategory, } from "@allurereport/core-api";
2
+ import { md5 } from "@allurereport/plugin-api";
3
+ const emptyStat = () => ({
4
+ total: 0,
5
+ });
6
+ const msgKey = (m) => (m && m.trim().length ? m : EMPTY_VALUE);
7
+ const envKey = (m) => (m && m.trim().length ? m : EMPTY_VALUE);
8
+ const formatEmptyValue = (key) => {
9
+ if (key === "message") {
10
+ return "No message";
11
+ }
12
+ if (key === "transition") {
13
+ return "No transition";
14
+ }
15
+ if (key === "environment") {
16
+ return "No environment";
17
+ }
18
+ return `No ${key}`;
19
+ };
20
+ const hasEnvironmentSelector = (category) => category.groupBy.some((selector) => selector === "environment");
21
+ const computeGroupEnvironments = (category, environmentCount, isSingleEnvironmentSelected) => {
22
+ if (isSingleEnvironmentSelected) {
23
+ return false;
24
+ }
25
+ if (category.groupEnvironments !== undefined) {
26
+ return category.groupEnvironments;
27
+ }
28
+ if (environmentCount > 1 && !hasEnvironmentSelector(category)) {
29
+ return true;
30
+ }
31
+ return false;
32
+ };
33
+ const displayGroupValue = (key, value) => (value === EMPTY_VALUE ? formatEmptyValue(key) : value);
34
+ const formatGroupName = (key, value) => `${key}: ${displayGroupValue(key, value)}`;
35
+ export const applyCategoriesToTestResults = (tests, categories) => {
36
+ for (const tr of tests) {
37
+ const matchingData = extractErrorMatchingData(tr);
38
+ const matched = matchCategory(categories, matchingData);
39
+ if (!matched) {
40
+ tr.categories = [];
41
+ continue;
42
+ }
43
+ tr.categories = [{ id: matched.id, name: matched.name }];
44
+ }
45
+ };
46
+ const extractGroupValue = (selector, testResult) => {
47
+ if (selector === "flaky") {
48
+ const flakyValue = testResult.flaky ? "true" : "false";
49
+ return { key: "flaky", value: flakyValue, name: formatGroupName("flaky", flakyValue) };
50
+ }
51
+ if (selector === "transition") {
52
+ const transitionValue = testResult.transition ?? EMPTY_VALUE;
53
+ return { key: "transition", value: transitionValue, name: formatGroupName("transition", transitionValue) };
54
+ }
55
+ if (selector === "status") {
56
+ const statusValue = testResult.status ?? "unknown";
57
+ return { key: "status", value: statusValue, name: formatGroupName("status", statusValue) };
58
+ }
59
+ if (selector === "environment") {
60
+ const environmentValue = envKey(testResult.environment);
61
+ return { key: "environment", value: environmentValue, name: formatGroupName("environment", environmentValue) };
62
+ }
63
+ if (selector === "owner") {
64
+ const ownerValue = findLastByLabelName(testResult.labels, "owner") ?? EMPTY_VALUE;
65
+ return { key: "owner", value: ownerValue, name: formatGroupName("owner", ownerValue) };
66
+ }
67
+ if (selector === "severity") {
68
+ const fallbackValue = selector === "severity" ? "normal" : EMPTY_VALUE;
69
+ const builtInValue = findLastByLabelName(testResult.labels, selector) ?? fallbackValue;
70
+ return {
71
+ key: selector,
72
+ value: builtInValue,
73
+ name: formatGroupName(selector, builtInValue),
74
+ };
75
+ }
76
+ if (selector === "layer") {
77
+ const layerValue = findLastByLabelName(testResult.labels, "layer") ?? EMPTY_VALUE;
78
+ return { key: "layer", value: layerValue, name: formatGroupName("layer", layerValue) };
79
+ }
80
+ const labelName = selector.label;
81
+ const labelValue = findLastByLabelName(testResult.labels, labelName) ?? EMPTY_VALUE;
82
+ return { key: labelName, value: labelValue, name: formatGroupName(labelName, labelValue) };
83
+ };
84
+ const buildGroupLevels = (category, testResult, matchingData, environmentCount, isSingleEnvironmentSelected) => {
85
+ const levels = [];
86
+ for (const selector of category.groupBy) {
87
+ const groupValue = extractGroupValue(selector, testResult);
88
+ levels.push({ type: "group", key: groupValue.key, value: groupValue.value, name: groupValue.name });
89
+ }
90
+ if (category.groupByMessage) {
91
+ const messageValue = msgKey(matchingData.message);
92
+ levels.push({
93
+ type: "message",
94
+ key: "message",
95
+ value: messageValue,
96
+ name: displayGroupValue("message", messageValue),
97
+ });
98
+ }
99
+ const groupEnvironments = computeGroupEnvironments(category, environmentCount, isSingleEnvironmentSelected);
100
+ if (groupEnvironments) {
101
+ const testKeyValue = testResult.historyId ?? testResult.id;
102
+ const testDisplayName = testResult.name ?? testKeyValue;
103
+ levels.push({
104
+ type: "history",
105
+ key: "historyId",
106
+ value: testKeyValue,
107
+ name: testDisplayName,
108
+ });
109
+ }
110
+ return levels;
111
+ };
112
+ export const generateCategories = async (writer, { tests, categories, filename = "categories.json", environmentCount = 0, selectedEnvironmentCount, environments = [], defaultEnvironment = "default", }) => {
113
+ const visible = tests.filter((t) => !t.hidden);
114
+ const environmentOrderMap = buildEnvironmentSortOrder(environments, defaultEnvironment);
115
+ const nodes = {};
116
+ const roots = [];
117
+ const childrenMap = new Map();
118
+ const categoryOrder = categories.filter((category) => !category.hide).map((category) => category.id);
119
+ const categoryNodeIds = new Map();
120
+ const categoryTouched = new Set();
121
+ const duplicateChecker = (node) => {
122
+ var _a;
123
+ nodes[_a = node.id] ?? (nodes[_a] = node);
124
+ return nodes[node.id];
125
+ };
126
+ const attachChild = (parentId, childId) => {
127
+ const set = childrenMap.get(parentId) ?? new Set();
128
+ set.add(childId);
129
+ childrenMap.set(parentId, set);
130
+ };
131
+ const bumpStat = (nodeId, status) => {
132
+ const node = nodes[nodeId];
133
+ node.statistic ?? (node.statistic = emptyStat());
134
+ incrementStatistic(node.statistic, status);
135
+ };
136
+ const ensureCategoryNode = (category) => {
137
+ const catId = categoryNodeIds.get(category.id) ?? `cat:${md5(category.id)}`;
138
+ if (!categoryNodeIds.has(category.id)) {
139
+ categoryNodeIds.set(category.id, catId);
140
+ }
141
+ duplicateChecker({
142
+ id: catId,
143
+ type: "category",
144
+ name: category.name,
145
+ statistic: emptyStat(),
146
+ childrenIds: [],
147
+ expand: category.expand,
148
+ });
149
+ return catId;
150
+ };
151
+ for (const tr of visible) {
152
+ const matchingData = extractErrorMatchingData(tr);
153
+ const matchedCategory = matchCategory(categories, matchingData);
154
+ if (!matchedCategory || matchedCategory.hide) {
155
+ continue;
156
+ }
157
+ const catId = ensureCategoryNode(matchedCategory);
158
+ categoryTouched.add(matchedCategory.id);
159
+ bumpStat(catId, tr.status);
160
+ const environmentValue = envKey(tr.environment);
161
+ const isSingleEnvironmentSelected = selectedEnvironmentCount === 1;
162
+ const groupEnvironments = computeGroupEnvironments(matchedCategory, environmentCount, isSingleEnvironmentSelected);
163
+ const levels = buildGroupLevels(matchedCategory, tr, matchingData, environmentCount, isSingleEnvironmentSelected);
164
+ const leafName = groupEnvironments ? formatGroupName("environment", environmentValue) : tr.name;
165
+ let parentId = catId;
166
+ for (const level of levels) {
167
+ const levelId = `${level.type}:${md5(`${parentId}\n${level.key}\n${level.value}`)}`;
168
+ const historyId = level.type === "history" ? level.value : undefined;
169
+ duplicateChecker({
170
+ id: levelId,
171
+ type: level.type,
172
+ name: level.name,
173
+ key: level.key,
174
+ value: level.value,
175
+ historyId,
176
+ statistic: emptyStat(),
177
+ childrenIds: [],
178
+ });
179
+ bumpStat(levelId, tr.status);
180
+ attachChild(parentId, levelId);
181
+ parentId = levelId;
182
+ }
183
+ tr.categories = [
184
+ {
185
+ name: matchedCategory.name,
186
+ grouping: levels.map((l) => ({ key: l.key, value: l.value, name: l.name })),
187
+ },
188
+ ];
189
+ duplicateChecker({
190
+ id: tr.id,
191
+ type: "tr",
192
+ name: leafName,
193
+ key: groupEnvironments ? "environment" : undefined,
194
+ value: groupEnvironments ? environmentValue : undefined,
195
+ status: tr.status,
196
+ duration: tr.duration,
197
+ flaky: tr.flaky,
198
+ retriesCount: tr.retriesCount,
199
+ transition: tr.transition,
200
+ tooltips: tr.tooltips,
201
+ });
202
+ attachChild(parentId, tr.id);
203
+ }
204
+ for (const [parentNodeId, childNodeIds] of childrenMap.entries()) {
205
+ const sortedChildIds = Array.from(childNodeIds).sort((leftChildId, rightChildId) => compareChildNodes(leftChildId, rightChildId, nodes, environmentOrderMap));
206
+ nodes[parentNodeId].childrenIds = sortedChildIds;
207
+ }
208
+ for (const categoryId of categoryOrder) {
209
+ if (!categoryTouched.has(categoryId)) {
210
+ continue;
211
+ }
212
+ const id = categoryNodeIds.get(categoryId);
213
+ if (id) {
214
+ roots.push(id);
215
+ }
216
+ }
217
+ const store = { roots, nodes };
218
+ await writer.writeWidget(filename, store);
219
+ };
@@ -1,5 +1,7 @@
1
- import type { TestFixtureResult, TestResult, TestStepResult } from "@allurereport/core-api";
1
+ import { type TestFixtureResult, type TestResult, type TestStepResult } from "@allurereport/core-api";
2
2
  import type { AwesomeFixtureResult, AwesomeTestResult, AwesomeTestStepResult } from "@allurereport/web-awesome";
3
- export declare const convertTestResult: (tr: TestResult) => AwesomeTestResult;
3
+ export declare const convertTestResult: (tr: TestResult, options?: {
4
+ hideLabels?: readonly (string | RegExp)[];
5
+ }) => AwesomeTestResult;
4
6
  export declare const convertTestStepResult: (tsr: TestStepResult) => AwesomeTestStepResult;
5
7
  export declare const convertFixtureResult: (fr: TestFixtureResult) => AwesomeFixtureResult;
@@ -1,3 +1,4 @@
1
+ import { createDictionary, shouldHideLabel, } from "@allurereport/core-api";
1
2
  import MarkdownIt from "markdown-it";
2
3
  const md = new MarkdownIt();
3
4
  const markdownToHtml = (value) => (value ? md.render(value) : undefined);
@@ -8,9 +9,10 @@ const mapLabelsByName = (labels) => {
8
9
  acc[name].push(value);
9
10
  }
10
11
  return acc;
11
- }, {});
12
+ }, createDictionary());
12
13
  };
13
- export const convertTestResult = (tr) => {
14
+ export const convertTestResult = (tr, options = {}) => {
15
+ const labels = tr.labels.filter(({ name }) => !shouldHideLabel(name, options.hideLabels));
14
16
  return {
15
17
  id: tr.id,
16
18
  name: tr.name,
@@ -24,8 +26,8 @@ export const convertTestResult = (tr) => {
24
26
  muted: tr.muted,
25
27
  known: tr.known,
26
28
  hidden: tr.hidden,
27
- labels: tr.labels,
28
- groupedLabels: mapLabelsByName(tr.labels),
29
+ labels,
30
+ groupedLabels: mapLabelsByName(labels),
29
31
  parameters: tr.parameters,
30
32
  links: tr.links,
31
33
  steps: tr.steps,
@@ -1,7 +1,7 @@
1
- import type { AwesomeTestResult } from "@allurereport/web-awesome";
1
+ import type { TestResult } from "@allurereport/core-api";
2
2
  import type { AwesomeOptions } from "./model.js";
3
3
  type Writer = {
4
4
  writeWidget(fileName: string, data: any): Promise<void>;
5
5
  };
6
- export declare const generateTimeline: (writer: Writer, trs: AwesomeTestResult[], options: AwesomeOptions) => Promise<void>;
6
+ export declare const generateTimeline: (writer: Writer, trs: TestResult[], options: AwesomeOptions, environmentIdByTrId: Map<string, string>) => Promise<void>;
7
7
  export {};
@@ -2,7 +2,7 @@ const DEFAULT_MIN_DURATION = 1;
2
2
  const DEFAULT_TIMELINE_OPTIONS = {
3
3
  minDuration: DEFAULT_MIN_DURATION,
4
4
  };
5
- export const generateTimeline = async (writer, trs, options) => {
5
+ export const generateTimeline = async (writer, trs, options, environmentIdByTrId) => {
6
6
  const { timeline = DEFAULT_TIMELINE_OPTIONS } = options;
7
7
  const { minDuration = DEFAULT_MIN_DURATION } = timeline;
8
8
  const result = [];
@@ -16,7 +16,8 @@ export const generateTimeline = async (writer, trs, options) => {
16
16
  if (duration < minDuration) {
17
17
  continue;
18
18
  }
19
- const { host, thread } = test.groupedLabels;
19
+ const host = test.labels?.find(({ name }) => name === "host")?.value;
20
+ const thread = test.labels?.find(({ name }) => name === "thread")?.value;
20
21
  if (!host?.length || !thread?.length) {
21
22
  continue;
22
23
  }
@@ -26,9 +27,10 @@ export const generateTimeline = async (writer, trs, options) => {
26
27
  name: test.name,
27
28
  status: test.status,
28
29
  hidden: test.hidden,
29
- host: host[0],
30
- thread: thread[0],
31
- environment: test.environment,
30
+ host,
31
+ thread,
32
+ environment: environmentIdByTrId.get(test.id) ?? test.environment,
33
+ environmentName: test.environment,
32
34
  start: test.start,
33
35
  duration,
34
36
  });
@@ -1,10 +1,12 @@
1
- import { type AttachmentLink, type EnvironmentItem, type Statistic, type TestEnvGroup, type TestError, type TestResult } from "@allurereport/core-api";
1
+ import { type AttachmentLink, type EnvironmentIdentity, type EnvironmentItem, type Statistic, type TestEnvGroup, type TestError, type TestResult } from "@allurereport/core-api";
2
2
  import type { AllureStore, ExitCode, PluginContext, QualityGateValidationResult, ReportFiles, ResultFile } from "@allurereport/plugin-api";
3
3
  import type { AwesomeTestResult } from "@allurereport/web-awesome";
4
4
  import type { AwesomeOptions, TemplateManifest } from "./model.js";
5
5
  import type { AwesomeDataWriter, ReportFile } from "./writer.js";
6
6
  export declare const readTemplateManifest: (singleFileMode?: boolean) => Promise<TemplateManifest>;
7
- export declare const generateTestResults: (writer: AwesomeDataWriter, store: AllureStore, trs: TestResult[]) => Promise<AwesomeTestResult[]>;
7
+ export declare const generateTestResults: (writer: AwesomeDataWriter, store: AllureStore, trs: TestResult[], options?: {
8
+ hideLabels?: readonly (string | RegExp)[];
9
+ }) => Promise<AwesomeTestResult[]>;
8
10
  export declare const generateTestCases: (writer: AwesomeDataWriter, trs: AwesomeTestResult[]) => Promise<void>;
9
11
  export declare const generateTestEnvGroups: (writer: AwesomeDataWriter, groups: TestEnvGroup[]) => Promise<void>;
10
12
  export declare const generateNav: (writer: AwesomeDataWriter, trs: AwesomeTestResult[], filename?: string) => Promise<void>;
@@ -17,7 +19,7 @@ export declare const generateVariables: (writer: AwesomeDataWriter, store: Allur
17
19
  export declare const generateStatistic: (writer: AwesomeDataWriter, data: {
18
20
  stats: Statistic;
19
21
  statsByEnv: Map<string, Statistic>;
20
- envs: string[];
22
+ envs: EnvironmentIdentity[];
21
23
  }) => Promise<void>;
22
24
  export declare const generateAttachmentsFiles: (writer: AwesomeDataWriter, attachmentLinks: AttachmentLink[], contentFunction: (id: string) => Promise<ResultFile | undefined>) => Promise<Map<string, string> | undefined>;
23
25
  export declare const generateHistoryDataPoints: (writer: AwesomeDataWriter, store: AllureStore) => Promise<Map<string, string>>;
@@ -1,12 +1,12 @@
1
- import { defaultChartsConfig } from "@allurereport/charts-api";
2
- import { compareBy, createBaseUrlScript, createFontLinkTag, createReportDataScript, createScriptTag, createStylesLinkTag, incrementStatistic, nullsLast, ordinal, } from "@allurereport/core-api";
3
- import { createTreeByLabels, createTreeByLabelsAndTitlePath, createTreeByTitlePath, filterTree, preciseTreeLabels, sortTree, transformTree, } from "@allurereport/plugin-api";
4
- import { generateCharts, getPieChartValues } from "@allurereport/web-commons";
5
- import Handlebars from "handlebars";
6
1
  import { randomUUID } from "node:crypto";
7
2
  import { readFile } from "node:fs/promises";
8
3
  import { createRequire } from "node:module";
9
4
  import { basename, join } from "node:path";
5
+ import { defaultChartsConfig } from "@allurereport/charts-api";
6
+ import { compareBy, createBaseUrlScript, createFontLinkTag, createReportDataScript, stringifyForInlineScript, createScriptTag, createStylesLinkTag, incrementStatistic, joinPosixPath, nullsLast, ordinal, } from "@allurereport/core-api";
7
+ import { createTreeByLabels, createTreeByLabelsAndTitlePath, createTreeByTitlePath, filterTree, preciseTreeLabels, sortTree, transformTree, } from "@allurereport/plugin-api";
8
+ import { generateCharts, getPieChartValues } from "@allurereport/web-commons";
9
+ import Handlebars from "handlebars";
10
10
  import { convertFixtureResult, convertTestResult } from "./converters.js";
11
11
  const require = createRequire(import.meta.url);
12
12
  const template = `<!DOCTYPE html>
@@ -58,9 +58,9 @@ const createBreadcrumbs = (convertedTr) => {
58
58
  acc[label.name].push(label.value || "");
59
59
  return acc;
60
60
  }, {});
61
- const parentSuites = labelsByType.parentSuite || [""];
62
- const suites = labelsByType.suite || [""];
63
- const subSuites = labelsByType.subSuite || [""];
61
+ const parentSuites = labelsByType.parentSuite ?? [""];
62
+ const suites = labelsByType.suite ?? [""];
63
+ const subSuites = labelsByType.subSuite ?? [""];
64
64
  return parentSuites.reduce((acc, parentSuite) => {
65
65
  suites.forEach((suite) => {
66
66
  subSuites.forEach((subSuite) => {
@@ -73,12 +73,12 @@ const createBreadcrumbs = (convertedTr) => {
73
73
  return acc;
74
74
  }, []);
75
75
  };
76
- export const generateTestResults = async (writer, store, trs) => {
76
+ export const generateTestResults = async (writer, store, trs, options = {}) => {
77
77
  let convertedTrs = [];
78
78
  for (const tr of trs) {
79
79
  const trFixtures = await store.fixturesByTrId(tr.id);
80
80
  const convertedTrFixtures = trFixtures.map(convertFixtureResult);
81
- const convertedTr = convertTestResult(tr);
81
+ const convertedTr = convertTestResult(tr, { hideLabels: options.hideLabels });
82
82
  convertedTr.history = (await store.historyByTrId(tr.id)) ?? [];
83
83
  convertedTr.retries = await store.retriesByTrId(tr.id);
84
84
  convertedTr.retriesCount = convertedTr.retries.length;
@@ -108,7 +108,7 @@ export const generateTestCases = async (writer, trs) => {
108
108
  };
109
109
  export const generateTestEnvGroups = async (writer, groups) => {
110
110
  for (const group of groups) {
111
- const src = join("test-env-groups", `${group.id}.json`);
111
+ const src = joinPosixPath("test-env-groups", `${group.id}.json`);
112
112
  await writer.writeData(src, group);
113
113
  }
114
114
  };
@@ -189,7 +189,7 @@ const buildTreeByTitlePath = (tests) => {
189
189
  };
190
190
  };
191
191
  const buildTreeByLabelsAndTitlePathCombined = (tests, labels) => createTreeByLabelsAndTitlePath(tests, labels, leafFactory, undefined, (group, leaf) => incrementStatistic(group.statistic, leaf.status));
192
- const leafFactory = ({ id, name, status, duration, flaky, start, retry, retriesCount, transition, tooltips, historyId, groupedLabels, }) => {
192
+ const leafFactory = ({ id, name, status, duration, flaky, start, retry, retriesCount, transition, tooltips, historyId, groupedLabels, categories, }) => {
193
193
  const leaf = {
194
194
  nodeId: id,
195
195
  id: historyId ?? id,
@@ -206,22 +206,25 @@ const leafFactory = ({ id, name, status, duration, flaky, start, retry, retriesC
206
206
  if (groupedLabels.tag && groupedLabels.tag.length > 0) {
207
207
  leaf.tags = groupedLabels.tag;
208
208
  }
209
+ if (categories?.length) {
210
+ leaf.categories = categories.map((category) => category.name).filter(Boolean);
211
+ }
209
212
  return leaf;
210
213
  };
211
214
  export const generateEnvironmentJson = async (writer, env) => {
212
215
  await writer.writeWidget("allure_environment.json", env);
213
216
  };
214
217
  export const generateEnvirontmentsList = async (writer, store) => {
215
- const environments = await store.allEnvironments();
218
+ const environments = await store.allEnvironmentIdentities();
216
219
  await writer.writeWidget("environments.json", environments);
217
220
  };
218
221
  export const generateVariables = async (writer, store) => {
219
222
  const reportVariables = await store.allVariables();
220
- const environments = await store.allEnvironments();
223
+ const environments = await store.allEnvironmentIdentities();
221
224
  await writer.writeWidget("variables.json", reportVariables);
222
225
  for (const env of environments) {
223
- const envVariables = await store.envVariables(env);
224
- await writer.writeWidget(join(env, "variables.json"), envVariables);
226
+ const envVariables = await store.envVariablesByEnvironmentId(env.id);
227
+ await writer.writeWidget(joinPosixPath(env.id, "variables.json"), envVariables);
225
228
  }
226
229
  };
227
230
  export const generateStatistic = async (writer, data) => {
@@ -229,12 +232,12 @@ export const generateStatistic = async (writer, data) => {
229
232
  await writer.writeWidget("statistic.json", stats);
230
233
  await writer.writeWidget("pie_chart.json", getPieChartValues(stats));
231
234
  for (const env of envs) {
232
- const envStats = statsByEnv.get(env);
235
+ const envStats = statsByEnv.get(env.id);
233
236
  if (!envStats) {
234
237
  continue;
235
238
  }
236
- await writer.writeWidget(join(env, "statistic.json"), envStats);
237
- await writer.writeWidget(join(env, "pie_chart.json"), envStats);
239
+ await writer.writeWidget(joinPosixPath(env.id, "statistic.json"), envStats);
240
+ await writer.writeWidget(joinPosixPath(env.id, "pie_chart.json"), envStats);
238
241
  }
239
242
  };
240
243
  export const generateAttachmentsFiles = async (writer, attachmentLinks, contentFunction) => {
@@ -340,7 +343,7 @@ export const generateStaticFiles = async (payload) => {
340
343
  headTags: headTags.join("\n"),
341
344
  bodyTags: bodyTags.join("\n"),
342
345
  reportFilesScript: createReportDataScript(reportDataFiles),
343
- reportOptions: JSON.stringify(reportOptions),
346
+ reportOptions: stringifyForInlineScript(reportOptions),
344
347
  analyticsEnable: true,
345
348
  allureVersion,
346
349
  reportUuid,
@@ -366,17 +369,21 @@ export const generateAllCharts = async (writer, store, options, context) => {
366
369
  };
367
370
  export const generateTreeFilters = async (writer, testResults) => {
368
371
  const trTags = new Set();
372
+ const trCategories = new Set();
369
373
  for (const tr of testResults) {
370
- if (tr.labels.length === 0) {
371
- continue;
372
- }
373
- Object.values(tr.groupedLabels.tag ?? []).forEach((tag) => {
374
+ for (const tag of tr.groupedLabels.tag ?? []) {
374
375
  trTags.add(tag);
376
+ }
377
+ tr.categories?.forEach((category) => {
378
+ if (category.name) {
379
+ trCategories.add(category.name);
380
+ }
375
381
  });
376
382
  }
377
- if (trTags.size === 0) {
383
+ if (trTags.size === 0 && trCategories.size === 0) {
378
384
  return Promise.resolve();
379
385
  }
380
386
  const tags = Array.from(trTags).sort((a, b) => a.localeCompare(b));
381
- await writer.writeWidget("tree-filters.json", { tags });
387
+ const categories = Array.from(trCategories).sort((a, b) => a.localeCompare(b));
388
+ await writer.writeWidget("tree-filters.json", { tags, categories });
382
389
  };
package/dist/plugin.js CHANGED
@@ -10,44 +10,89 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
10
10
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
11
  };
12
12
  var _AwesomePlugin_writer, _AwesomePlugin_generate;
13
- import { getWorstStatus } from "@allurereport/core-api";
14
- import { convertToSummaryTestResult, } from "@allurereport/plugin-api";
13
+ import { incrementStatistic, joinPosixPath } from "@allurereport/core-api";
14
+ import { createPluginSummary, } from "@allurereport/plugin-api";
15
15
  import { preciseTreeLabels } from "@allurereport/plugin-api";
16
- import { join } from "node:path";
17
- import { filterEnv } from "./environments.js";
16
+ import { applyCategoriesToTestResults, generateCategories } from "./categories.js";
18
17
  import { generateTimeline } from "./generateTimeline.js";
19
18
  import { generateAllCharts, generateAttachmentsFiles, generateEnvironmentJson, generateEnvirontmentsList, generateGlobals, generateHistoryDataPoints, generateNav, generateQualityGateResults, generateStaticFiles, generateStatistic, generateTestCases, generateTestEnvGroups, generateTestResults, generateTree, generateTreeFilters, generateVariables, } from "./generators.js";
20
19
  import { InMemoryReportDataWriter, ReportFileDataWriter } from "./writer.js";
20
+ const statisticByTestResults = async (store, testResults) => {
21
+ const statistic = { total: 0 };
22
+ for (const testResult of testResults) {
23
+ if (testResult.hidden) {
24
+ continue;
25
+ }
26
+ statistic.total++;
27
+ incrementStatistic(statistic, testResult.status);
28
+ if ((await store.retriesByTrId(testResult.id)).length > 0) {
29
+ statistic.retries = (statistic.retries ?? 0) + 1;
30
+ }
31
+ if (testResult.flaky) {
32
+ statistic.flaky = (statistic.flaky ?? 0) + 1;
33
+ }
34
+ if (testResult.transition === "new") {
35
+ statistic.new = (statistic.new ?? 0) + 1;
36
+ }
37
+ }
38
+ return statistic;
39
+ };
21
40
  export class AwesomePlugin {
22
41
  constructor(options = {}) {
23
42
  this.options = options;
24
43
  _AwesomePlugin_writer.set(this, void 0);
25
44
  _AwesomePlugin_generate.set(this, async (context, store) => {
26
45
  const { singleFile, groupBy = [], filter, appendTitlePath } = this.options ?? {};
46
+ const hideLabels = context.hideLabels;
47
+ const categories = context.categories ?? [];
27
48
  const environmentItems = await store.metadataByKey("allure_environment");
28
- const reportEnvironments = await store.allEnvironments();
29
49
  const attachments = await store.allAttachments();
30
50
  const allTrs = await store.allTestResults({ includeHidden: true, filter });
31
51
  const statistics = await store.testsStatistic(filter);
32
- const environments = await store.allEnvironments();
52
+ const environments = await store.allEnvironmentIdentities();
33
53
  const envStatistics = new Map();
34
54
  const allTestEnvGroups = await store.allTestEnvGroups();
35
55
  const globalAttachments = await store.allGlobalAttachments();
36
56
  const globalExitCode = await store.globalExitCode();
37
57
  const globalErrors = await store.allGlobalErrors();
38
- const qualityGateResults = await store.qualityGateResultsByEnv();
39
- for (const env of environments) {
40
- envStatistics.set(env, await store.testsStatistic(filterEnv(env, filter)));
41
- }
58
+ const qualityGateResults = await store.qualityGateResultsByEnvironmentId();
59
+ const envResultsById = new Map();
60
+ const envIdByTrId = new Map();
61
+ environments.forEach(({ id }) => {
62
+ envResultsById.set(id, []);
63
+ });
64
+ await Promise.all(allTrs.map(async (tr) => {
65
+ const environmentId = await store.environmentIdByTrId(tr.id);
66
+ if (!environmentId) {
67
+ return;
68
+ }
69
+ envIdByTrId.set(tr.id, environmentId);
70
+ if (!envResultsById.has(environmentId)) {
71
+ envResultsById.set(environmentId, []);
72
+ }
73
+ envResultsById.get(environmentId).push(tr);
74
+ }));
75
+ await Promise.all(environments.map(async ({ id }) => {
76
+ envStatistics.set(id, await statisticByTestResults(store, envResultsById.get(id) ?? []));
77
+ }));
42
78
  await generateStatistic(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), {
43
79
  stats: statistics,
44
80
  statsByEnv: envStatistics,
45
81
  envs: environments,
46
82
  });
47
83
  await generateAllCharts(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), store, this.options, context);
48
- const convertedTrs = await generateTestResults(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), store, allTrs);
84
+ const convertedTrs = await generateTestResults(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), store, allTrs, { hideLabels });
85
+ applyCategoriesToTestResults(convertedTrs, categories);
86
+ await generateCategories(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), {
87
+ tests: convertedTrs,
88
+ categories,
89
+ environmentCount: environments.length,
90
+ environments: environments.map(({ name }) => name),
91
+ defaultEnvironment: "default",
92
+ selectedEnvironmentCount: environments.length,
93
+ });
49
94
  const hasGroupBy = groupBy.length > 0;
50
- await generateTimeline(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), convertedTrs, this.options);
95
+ await generateTimeline(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), allTrs, this.options, envIdByTrId);
51
96
  const treeLabels = hasGroupBy
52
97
  ? preciseTreeLabels(groupBy, convertedTrs, ({ labels }) => labels.map(({ name }) => name))
53
98
  : [];
@@ -56,12 +101,31 @@ export class AwesomePlugin {
56
101
  await generateTree(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), "tree.json", treeLabels, convertedTrs, { appendTitlePath });
57
102
  await generateNav(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), convertedTrs, "nav.json");
58
103
  await generateTestEnvGroups(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), allTestEnvGroups);
59
- for (const reportEnvironment of reportEnvironments) {
60
- const envConvertedTrs = convertedTrs.filter(({ environment }) => environment === reportEnvironment);
61
- await generateTree(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), join(reportEnvironment, "tree.json"), treeLabels, envConvertedTrs, {
104
+ const envConvertedTrsById = new Map();
105
+ convertedTrs.forEach((tr) => {
106
+ const environmentId = envIdByTrId.get(tr.id);
107
+ if (!environmentId) {
108
+ return;
109
+ }
110
+ if (!envConvertedTrsById.has(environmentId)) {
111
+ envConvertedTrsById.set(environmentId, []);
112
+ }
113
+ envConvertedTrsById.get(environmentId).push(tr);
114
+ });
115
+ for (const reportEnvironment of environments) {
116
+ const envConvertedTrs = envConvertedTrsById.get(reportEnvironment.id) ?? [];
117
+ await generateTree(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), joinPosixPath(reportEnvironment.id, "tree.json"), treeLabels, envConvertedTrs, {
62
118
  appendTitlePath,
63
119
  });
64
- await generateNav(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), envConvertedTrs, join(reportEnvironment, "nav.json"));
120
+ await generateNav(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), envConvertedTrs, joinPosixPath(reportEnvironment.id, "nav.json"));
121
+ await generateCategories(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), {
122
+ tests: envConvertedTrs,
123
+ categories,
124
+ environmentCount: 1,
125
+ defaultEnvironment: "default",
126
+ selectedEnvironmentCount: 1,
127
+ filename: joinPosixPath(reportEnvironment.id, "categories.json"),
128
+ });
65
129
  }
66
130
  await generateTreeFilters(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), convertedTrs);
67
131
  await generateEnvirontmentsList(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), store);
@@ -114,29 +178,19 @@ export class AwesomePlugin {
114
178
  };
115
179
  }
116
180
  async info(context, store) {
117
- const allTrs = await store.allTestResults({ filter: this.options.filter });
118
- const newTrs = await store.allNewTestResults(this.options.filter);
119
- const retryTrs = allTrs.filter((tr) => !!tr?.retries?.length);
120
- const flakyTrs = allTrs.filter((tr) => !!tr?.flaky);
121
- const duration = allTrs.reduce((acc, { duration: trDuration = 0 }) => acc + trDuration, 0);
122
- const worstStatus = getWorstStatus(allTrs.map(({ status }) => status));
123
- const createdAt = allTrs.reduce((acc, { stop }) => Math.max(acc, stop || 0), 0);
124
- return {
181
+ return createPluginSummary({
125
182
  name: this.options.reportName || context.reportName,
126
- stats: await store.testsStatistic(this.options.filter),
127
- status: worstStatus ?? "passed",
128
- duration,
129
- createdAt,
130
183
  plugin: "Awesome",
131
- newTests: newTrs.map(convertToSummaryTestResult),
132
- flakyTests: flakyTrs.map(convertToSummaryTestResult),
133
- retryTests: retryTrs.map(convertToSummaryTestResult),
134
184
  meta: {
135
185
  reportId: context.reportUuid,
136
186
  singleFile: this.options.singleFile ?? false,
137
187
  withTestResultsLinks: true,
138
188
  },
139
- };
189
+ filter: this.options.filter,
190
+ ci: context.ci,
191
+ history: context.history,
192
+ store,
193
+ });
140
194
  }
141
195
  }
142
196
  _AwesomePlugin_writer = new WeakMap(), _AwesomePlugin_generate = new WeakMap();
package/dist/writer.js CHANGED
@@ -6,7 +6,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
6
6
  var _InMemoryReportDataWriter_data;
7
7
  import { mkdir, writeFile } from "node:fs/promises";
8
8
  import { resolve } from "node:path";
9
- import { join as joinPosix } from "node:path/posix";
9
+ import { joinPosixPath } from "@allurereport/core-api";
10
10
  export class FileSystemReportDataWriter {
11
11
  constructor(output) {
12
12
  this.output = output;
@@ -37,19 +37,19 @@ export class InMemoryReportDataWriter {
37
37
  _InMemoryReportDataWriter_data.set(this, {});
38
38
  }
39
39
  async writeData(fileName, data) {
40
- const dist = joinPosix("data", fileName);
40
+ const dist = joinPosixPath("data", fileName);
41
41
  __classPrivateFieldGet(this, _InMemoryReportDataWriter_data, "f")[dist] = Buffer.from(JSON.stringify(data), "utf-8");
42
42
  }
43
43
  async writeWidget(fileName, data) {
44
- const dist = joinPosix("widgets", fileName);
44
+ const dist = joinPosixPath("widgets", fileName);
45
45
  __classPrivateFieldGet(this, _InMemoryReportDataWriter_data, "f")[dist] = Buffer.from(JSON.stringify(data), "utf-8");
46
46
  }
47
47
  async writeTestCase(test) {
48
- const dist = joinPosix("data", "test-results", `${test.id}.json`);
48
+ const dist = joinPosixPath("data", "test-results", `${test.id}.json`);
49
49
  __classPrivateFieldGet(this, _InMemoryReportDataWriter_data, "f")[dist] = Buffer.from(JSON.stringify(test), "utf-8");
50
50
  }
51
51
  async writeAttachment(fileName, file) {
52
- const dist = joinPosix("data", "attachments", fileName);
52
+ const dist = joinPosixPath("data", "attachments", fileName);
53
53
  const content = await file.asBuffer();
54
54
  if (content) {
55
55
  __classPrivateFieldGet(this, _InMemoryReportDataWriter_data, "f")[dist] = content;
@@ -68,19 +68,19 @@ export class ReportFileDataWriter {
68
68
  this.reportFiles = reportFiles;
69
69
  }
70
70
  async writeData(fileName, data) {
71
- await this.reportFiles.addFile(joinPosix("data", fileName), Buffer.from(JSON.stringify(data), "utf-8"));
71
+ await this.reportFiles.addFile(joinPosixPath("data", fileName), Buffer.from(JSON.stringify(data), "utf-8"));
72
72
  }
73
73
  async writeWidget(fileName, data) {
74
- await this.reportFiles.addFile(joinPosix("widgets", fileName), Buffer.from(JSON.stringify(data), "utf-8"));
74
+ await this.reportFiles.addFile(joinPosixPath("widgets", fileName), Buffer.from(JSON.stringify(data), "utf-8"));
75
75
  }
76
76
  async writeAttachment(source, file) {
77
77
  const contentBuffer = await file.asBuffer();
78
78
  if (!contentBuffer) {
79
79
  return;
80
80
  }
81
- await this.reportFiles.addFile(joinPosix("data", "attachments", source), contentBuffer);
81
+ await this.reportFiles.addFile(joinPosixPath("data", "attachments", source), contentBuffer);
82
82
  }
83
83
  async writeTestCase(test) {
84
- await this.reportFiles.addFile(joinPosix("data", "test-results", `${test.id}.json`), Buffer.from(JSON.stringify(test), "utf8"));
84
+ await this.reportFiles.addFile(joinPosixPath("data", "test-results", `${test.id}.json`), Buffer.from(JSON.stringify(test), "utf8"));
85
85
  }
86
86
  }
package/package.json CHANGED
@@ -1,61 +1,50 @@
1
1
  {
2
2
  "name": "@allurereport/plugin-awesome",
3
- "version": "3.2.0",
3
+ "version": "3.4.0",
4
4
  "description": "Allure Awesome Plugin – brand new HTML report with modern design and new features",
5
5
  "keywords": [
6
6
  "allure",
7
- "testing",
8
- "report",
7
+ "html",
9
8
  "plugin",
10
- "html"
9
+ "report",
10
+ "testing"
11
11
  ],
12
- "repository": "https://github.com/allure-framework/allure3",
13
12
  "license": "Apache-2.0",
14
13
  "author": "Qameta Software",
14
+ "repository": "https://github.com/allure-framework/allure3",
15
+ "files": [
16
+ "./dist"
17
+ ],
15
18
  "type": "module",
16
- "exports": {
17
- ".": "./dist/index.js"
18
- },
19
19
  "main": "./dist/index.js",
20
20
  "module": "./dist/index.js",
21
21
  "types": "./dist/index.d.ts",
22
- "files": [
23
- "./dist"
24
- ],
22
+ "exports": {
23
+ ".": "./dist/index.js"
24
+ },
25
25
  "scripts": {
26
26
  "build": "run clean && tsc --project ./tsconfig.json",
27
27
  "clean": "rimraf ./dist",
28
- "eslint": "eslint ./src/**/*.{js,jsx,ts,tsx}",
29
- "eslint:format": "eslint --fix ./src/**/*.{js,jsx,ts,tsx}",
30
- "test": "rimraf ./out && vitest run"
28
+ "test": "rimraf ./out && vitest run",
29
+ "lint": "oxlint --import-plugin src test features stories",
30
+ "lint:fix": "oxlint --import-plugin --fix src test features stories"
31
31
  },
32
32
  "dependencies": {
33
- "@allurereport/charts-api": "3.2.0",
34
- "@allurereport/core-api": "3.2.0",
35
- "@allurereport/plugin-api": "3.2.0",
36
- "@allurereport/web-awesome": "3.2.0",
37
- "@allurereport/web-commons": "3.2.0",
33
+ "@allurereport/charts-api": "3.4.0",
34
+ "@allurereport/core-api": "3.4.0",
35
+ "@allurereport/plugin-api": "3.4.0",
36
+ "@allurereport/web-awesome": "3.4.0",
37
+ "@allurereport/web-commons": "3.4.0",
38
38
  "d3-shape": "^3.2.0",
39
- "handlebars": "^4.7.8",
39
+ "handlebars": "^4.7.9",
40
40
  "markdown-it": "^14.1.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@stylistic/eslint-plugin": "^2.6.1",
44
43
  "@types/d3-shape": "^3.1.6",
45
- "@types/eslint": "^8.56.11",
46
44
  "@types/markdown-it": "^14.1.2",
47
45
  "@types/node": "^20.17.9",
48
- "@typescript-eslint/eslint-plugin": "^8.0.0",
49
- "@typescript-eslint/parser": "^8.0.0",
50
46
  "@vitest/runner": "^2.1.9",
51
47
  "allure-vitest": "^3.3.3",
52
- "eslint": "^8.57.0",
53
- "eslint-config-prettier": "^9.1.0",
54
- "eslint-plugin-import": "^2.29.1",
55
- "eslint-plugin-jsdoc": "^50.0.0",
56
- "eslint-plugin-n": "^17.10.1",
57
- "eslint-plugin-no-null": "^1.0.2",
58
- "eslint-plugin-prefer-arrow": "^1.2.3",
59
48
  "rimraf": "^6.0.1",
60
49
  "typescript": "^5.6.3",
61
50
  "vitest": "^2.1.9"
@@ -1,3 +0,0 @@
1
- import type { TestResult } from "@allurereport/core-api";
2
- import type { TestResultFilter } from "@allurereport/plugin-api";
3
- export declare const filterEnv: (env: string, filter?: TestResultFilter) => (testResult: TestResult) => boolean;
@@ -1,8 +0,0 @@
1
- export const filterEnv = (env, filter) => {
2
- return (testResult) => {
3
- if (testResult.environment !== env) {
4
- return false;
5
- }
6
- return filter ? filter(testResult) : true;
7
- };
8
- };