@allurereport/plugin-awesome 3.20.1 → 3.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -59,6 +59,7 @@ The plugin accepts the following options:
59
59
  | `appendTitlePath`| Special marker for `groupBy`. Forces a final grouping by `titlePath` after all label-based groups. | `boolean` | `false` |
60
60
  | `stepTreeExpansion` | Default expansion policy for step trees in test details. | `"collapsed" \| "expand_failed_only" \| "expanded"` | `"expand_failed_only"` |
61
61
  | `defaultSortBy` | Default sort order for the test tree. Accepted values: `order,asc`, `order,desc`, `duration,asc`, `duration,desc`, `name,asc`, `name,desc`, `status,asc`, `status,desc`. User's manual selection is preserved in `localStorage` and takes priority over this value. | `string` | `order,asc` |
62
+ | `ideaLinks` | Adds an "Open in IDEA" link to tests that have a `testClass` label, pointing to the IntelliJ IDEA built-in server (`http://localhost:<port>/api/file?file=<path>`). Pass `true` for defaults or `{ port, fileExtension, sourceRoot }` (defaults: `63342`, `java`, none; `sourceRoot` is e.g. `src/test/java`). The link is added only when `testClass` looks like a class name (dot-separated identifiers). | `boolean \| { port?: number; fileExtension?: string; sourceRoot?: string }` | `false` |
62
63
 
63
64
  ### Default sort order
64
65
 
@@ -198,6 +198,7 @@ export const generateCategories = async (writer, { tests, categories, filename =
198
198
  duration: tr.duration,
199
199
  flaky: tr.flaky,
200
200
  retriesCount: tr.retriesCount,
201
+ retriesStatusChange: tr.retriesStatusChange,
201
202
  transition: tr.transition,
202
203
  tooltips: tr.tooltips,
203
204
  });
@@ -1,7 +1,8 @@
1
- import { type TestFixtureResult, type TestResult, type TestStepResult } from "@allurereport/core-api";
2
- import type { ReportFixtureResult, ReportTestResult, ReportTestStepResult } from "@allurereport/plugin-api";
1
+ import { type TestResult } from "@allurereport/core-api";
2
+ import type { ReportTestResult } from "@allurereport/plugin-api";
3
+ import type { IdeaLinksOptions } from "./model.js";
3
4
  export declare const convertTestResult: (tr: TestResult, options?: {
4
5
  hideLabels?: readonly (string | RegExp)[];
6
+ ideaLinks?: boolean | IdeaLinksOptions;
7
+ resolutionEvaluatedAt?: number;
5
8
  }) => ReportTestResult;
6
- export declare const convertTestStepResult: (tsr: TestStepResult) => ReportTestStepResult;
7
- export declare const convertFixtureResult: (fr: TestFixtureResult) => ReportFixtureResult;
@@ -1,7 +1,22 @@
1
- import { createDictionary, isStep, redactParameters, shouldHideLabel, } from "@allurereport/core-api";
1
+ import { createDictionary, getMutedResolutionState, redactParameters, shouldHideLabel, } from "@allurereport/core-api";
2
+ import { convertTestStepResult } from "@allurereport/plugin-api/internal/report";
2
3
  import MarkdownIt from "markdown-it";
3
4
  const md = new MarkdownIt();
4
5
  const markdownToHtml = (value) => (value ? md.render(value) : undefined);
6
+ const javaClassNameRegexp = /^[\p{L}_$][\p{L}\p{N}_$]*(\.[\p{L}_$][\p{L}\p{N}_$]*)*$/u;
7
+ const createIdeaLink = (labels, options) => {
8
+ const { port = 63342, fileExtension = "java", sourceRoot = "" } = options === true ? {} : options || {};
9
+ const testClass = labels.find(({ name, value }) => name === "testClass" && value)?.value;
10
+ const classPath = testClass?.split("$")[0].replaceAll(".", "/");
11
+ if (!testClass || !classPath || !javaClassNameRegexp.test(testClass)) {
12
+ return [];
13
+ }
14
+ const root = sourceRoot.replace(/^\/+|\/+$/g, "");
15
+ const file = `${root ? `${root}/` : ""}${classPath}.${fileExtension.replace(/^\./, "")}`;
16
+ return [
17
+ { name: "Open in IDEA", type: "idea", url: `http://localhost:${port}/api/file?file=${encodeURIComponent(file)}` },
18
+ ];
19
+ };
5
20
  const mapLabelsByName = (labels) => {
6
21
  return labels.reduce((acc, { name, value }) => {
7
22
  acc[name] = acc[name] || [];
@@ -30,16 +45,20 @@ export const convertTestResult = (tr, options = {}) => {
30
45
  known: tr.known,
31
46
  resolution: tr.resolution,
32
47
  resolutionComment: tr.resolutionComment,
48
+ resolutionExpiresAt: tr.resolutionExpiresAt,
49
+ resolutionState: getMutedResolutionState(tr, options.resolutionEvaluatedAt),
33
50
  isRetry: tr.isRetry,
34
51
  labels,
35
52
  groupedLabels: mapLabelsByName(labels),
36
53
  parameters: redactParameters(tr.parameters),
37
- links: tr.links,
54
+ links: options.ideaLinks ? [...tr.links, ...createIdeaLink(tr.labels, options.ideaLinks)] : tr.links,
38
55
  steps: (tr.steps ?? []).map(convertTestStepResult),
39
56
  error: tr.error,
40
57
  errors: tr.errors,
41
58
  testCase: tr.testCase,
42
59
  descriptionHtml: tr.descriptionHtml ?? markdownToHtml(tr.description),
60
+ preconditionHtml: tr.preconditionHtml ?? markdownToHtml(tr.precondition),
61
+ expectedResultHtml: tr.expectedResultHtml ?? markdownToHtml(tr.expectedResult),
43
62
  environment: tr.environment,
44
63
  setup: [],
45
64
  teardown: [],
@@ -51,23 +70,3 @@ export const convertTestResult = (tr, options = {}) => {
51
70
  titlePath: tr.titlePath || [],
52
71
  };
53
72
  };
54
- export const convertTestStepResult = (tsr) => {
55
- if (isStep(tsr)) {
56
- return {
57
- ...tsr,
58
- parameters: redactParameters(tsr.parameters),
59
- steps: (tsr.steps ?? []).map(convertTestStepResult),
60
- };
61
- }
62
- return tsr;
63
- };
64
- export const convertFixtureResult = (fr) => {
65
- return {
66
- id: fr.id,
67
- type: fr.type,
68
- name: fr.name,
69
- status: fr.status,
70
- steps: fr.steps.map(convertTestStepResult),
71
- duration: fr.duration,
72
- };
73
- };
@@ -1,13 +1,14 @@
1
1
  import { type AttachmentLink, type EnvironmentIdentity, type EnvironmentItem, type HistoryTestResultUrlResolver, type MetricSample, type Statistic, type TestEnvGroup, type TestResult } from "@allurereport/core-api";
2
- import type { AllureStore, ReportExecutorInfo, ReportRunSummary, ReportTestResult, GlobalAttachmentLink, ExitCode, PluginContext, PluginGlobalError, QualityGateValidationResult, ReportFiles, ResultFile, TestResultRelatedData } from "@allurereport/plugin-api";
3
- import type { AwesomeOptions, TemplateManifest } from "./model.js";
2
+ import type { AllureStore, ReportExecutorInfo, ReportGitInfo, ReportRunSummary, ReportTestResult, GlobalAttachmentLink, ExitCode, PluginContext, PluginGlobalError, QualityGateValidationResult, ReportFiles, ResultFile, TestResultRelatedData } from "@allurereport/plugin-api";
3
+ import type { AwesomeOptions, IdeaLinksOptions } from "./model.js";
4
4
  import type { AwesomeDataWriter, ReportFile } from "./writer.js";
5
- export declare const readTemplateManifest: (_singleFileMode?: boolean) => Promise<TemplateManifest>;
6
5
  export declare const generateTestResults: (_writer: AwesomeDataWriter, store: AllureStore, trs: TestResult[], options: {
7
6
  pluginId: string;
8
7
  hideLabels?: readonly (string | RegExp)[];
8
+ ideaLinks?: boolean | IdeaLinksOptions;
9
9
  related?: TestResultRelatedData;
10
10
  resolveHistoryUrl?: HistoryTestResultUrlResolver;
11
+ resolutionEvaluatedAt?: number;
11
12
  }) => Promise<ReportTestResult[]>;
12
13
  export declare const generateTestCases: (writer: AwesomeDataWriter, trs: ReportTestResult[]) => Promise<void>;
13
14
  export declare const generateTestEnvGroups: (writer: AwesomeDataWriter, groups: TestEnvGroup[]) => Promise<void>;
@@ -29,7 +30,6 @@ export declare const generateStatistic: (writer: AwesomeDataWriter, data: {
29
30
  envs: EnvironmentIdentity[];
30
31
  }) => Promise<void>;
31
32
  export declare const generateAttachmentsFiles: (writer: AwesomeDataWriter, attachmentLinks: AttachmentLink[], contentFunction: (id: string) => Promise<ResultFile | undefined>) => Promise<Map<string, string>>;
32
- export declare const generateHistoryDataPoints: (writer: AwesomeDataWriter, store: AllureStore) => Promise<Map<string, string>>;
33
33
  export declare const generateGlobals: (writer: AwesomeDataWriter, payload: {
34
34
  globalExitCode?: ExitCode;
35
35
  globalAttachments?: GlobalAttachmentLink[];
@@ -50,6 +50,8 @@ export declare const generateStaticFiles: (payload: AwesomeOptions & {
50
50
  reportDataFiles: ReportFile[];
51
51
  reportUuid: string;
52
52
  reportName: string;
53
+ overviewHref?: string;
54
+ git?: ReportGitInfo;
53
55
  executor?: ReportExecutorInfo;
54
56
  runSummary?: ReportRunSummary;
55
57
  runSummaryByEnv?: Record<string, ReportRunSummary>;
@@ -1,11 +1,12 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { defaultChartsConfig } from "@allurereport/charts-api";
3
- import { appLoaderFaviconDataUri, appLoaderStyles, compareBy, createAppLoaderMarkup, createBaseUrlScript, createFaviconLinkTag, createFontLinkTag, createReportDataScript, stringifyForInlineScript, createScriptTag, createStylesLinkTag, incrementStatistic, joinPosixPath, nullsLast, ordinal, redactParameters, severityLabelName, } from "@allurereport/core-api";
3
+ import { appLoaderFaviconDataUri, appLoaderStyles, compareBy, createAppLoaderMarkup, createBaseUrlScript, createFaviconLinkTag, createFontLinkTag, injectReportDataScript, reportDataScriptPlaceholder, stringifyForInlineScript, createScriptTag, createStylesLinkTag, hasRetriesStatusChange, incrementStatistic, joinPosixPath, nullsLast, ordinal, parseResolutionExpiresAt, redactParameters, severityLabelName, } from "@allurereport/core-api";
4
4
  import { collapseTreeGroups, createTreeByLabels, createTreeByLabelsAndTitlePath, createTreeByTitlePath, preciseTreeLabels, processTree, } from "@allurereport/plugin-api";
5
- import { copyReportStaticAssets, getReportStaticAsset, readReportStaticAssets, } from "@allurereport/plugin-api/static-assets";
5
+ import { convertFixtureResult, mapConcurrently, writeConcurrently } from "@allurereport/plugin-api/internal/report";
6
+ import { createSingleFileScripts, copyReportStaticAssets, getReportStaticAsset, readReportStaticAssets, } from "@allurereport/plugin-api/static-assets";
6
7
  import { generateCharts, getPieChartValues } from "@allurereport/web-commons";
7
8
  import Handlebars from "handlebars";
8
- import { convertFixtureResult, convertTestResult } from "./converters.js";
9
+ import { convertTestResult } from "./converters.js";
9
10
  const reportStaticArchive = new URL("../dist/static/report.tar", import.meta.url);
10
11
  const template = `<!DOCTYPE html>
11
12
  <html dir="ltr" lang="en">
@@ -46,10 +47,6 @@ const template = `<!DOCTYPE html>
46
47
  </html>
47
48
  `;
48
49
  const compiledTemplate = Handlebars.compile(template);
49
- export const readTemplateManifest = async (_singleFileMode) => {
50
- const { manifest } = await readReportStaticAssets(reportStaticArchive);
51
- return manifest;
52
- };
53
50
  const createBreadcrumbs = (convertedTr) => {
54
51
  const labelsByType = convertedTr.labels.reduce((acc, label) => {
55
52
  if (!acc[label.name]) {
@@ -73,18 +70,6 @@ const createBreadcrumbs = (convertedTr) => {
73
70
  return acc;
74
71
  }, []);
75
72
  };
76
- const writeConcurrently = async (items, write, concurrency = 64) => {
77
- for (let i = 0; i < items.length; i += concurrency) {
78
- await Promise.all(items.slice(i, i + concurrency).map(write));
79
- }
80
- };
81
- const mapConcurrently = async (items, map, concurrency = 8) => {
82
- const results = [];
83
- for (let i = 0; i < items.length; i += concurrency) {
84
- results.push(...(await Promise.all(items.slice(i, i + concurrency).map(map))));
85
- }
86
- return results;
87
- };
88
73
  export const generateTestResults = async (_writer, store, trs, options) => {
89
74
  let convertedTrs = [];
90
75
  const related = options.related ?? (await store.relatedByTestResultIds(trs.map(({ id }) => id)));
@@ -96,6 +81,8 @@ export const generateTestResults = async (_writer, store, trs, options) => {
96
81
  .map(convertFixtureResult);
97
82
  const convertedTr = convertTestResult(tr, {
98
83
  hideLabels: options.hideLabels,
84
+ ideaLinks: options.ideaLinks,
85
+ resolutionEvaluatedAt: options.resolutionEvaluatedAt,
99
86
  });
100
87
  convertedTr.history = (related.historyByTrId.get(tr.id) ?? []).map((item) => ({
101
88
  ...item,
@@ -104,6 +91,7 @@ export const generateTestResults = async (_writer, store, trs, options) => {
104
91
  convertedTr.retries = related.retriesByTrId.get(tr.id) ?? [];
105
92
  convertedTr.retriesCount = convertedTr.retries.length;
106
93
  convertedTr.retry = convertedTr.retriesCount > 0;
94
+ convertedTr.retriesStatusChange = hasRetriesStatusChange(tr, convertedTr.retries);
107
95
  convertedTr.isRetry = tr.isRetry;
108
96
  convertedTr.setup = convertedTrFixtures.filter((f) => f.type === "before");
109
97
  convertedTr.teardown = convertedTrFixtures.filter((f) => f.type === "after");
@@ -137,7 +125,9 @@ export const generateNav = async (writer, trs, filename = "nav.json") => {
137
125
  };
138
126
  const SEARCHABLE_LABELS = new Set([
139
127
  "owner",
128
+ "parentSuite",
140
129
  "suite",
130
+ "subSuite",
141
131
  "package",
142
132
  "testClass",
143
133
  "testMethod",
@@ -175,6 +165,7 @@ const searchDocumentFactory = (test) => {
175
165
  id: test.id,
176
166
  nodeId: test.id,
177
167
  name: test.name,
168
+ titlePath: joinSearchValues(test.titlePath ?? []),
178
169
  fullName: test.fullName,
179
170
  retryHash: test.retryHash,
180
171
  labels: joinSearchValues(labels),
@@ -282,7 +273,7 @@ const buildTreeByTitlePath = (tests) => {
282
273
  };
283
274
  };
284
275
  const buildTreeByLabelsAndTitlePathCombined = (tests, labels) => collapseTreeGroups(createTreeByLabelsAndTitlePath(tests, labels, leafFactory, undefined, (group, leaf) => incrementStatistic(group.statistic, leaf.status)));
285
- const leafFactory = ({ id, name, status, duration, flaky, start, retry, retriesCount, resolution, transition, tooltips, retryHash, groupedLabels, categories, parameters, }) => {
276
+ const leafFactory = ({ id, name, status, duration, flaky, start, retry, retriesCount, retriesStatusChange, resolution, resolutionExpiresAt, resolutionState, transition, tooltips, retryHash, groupedLabels, categories, parameters, }) => {
286
277
  const unresolvedFailure = (status === "failed" || status === "broken") && !resolution;
287
278
  const parameterValues = redactParameters(parameters).flatMap(({ value }) => (value ? [value] : []));
288
279
  const leaf = {
@@ -295,7 +286,10 @@ const leafFactory = ({ id, name, status, duration, flaky, start, retry, retriesC
295
286
  start,
296
287
  retry,
297
288
  retriesCount,
289
+ retriesStatusChange,
298
290
  resolution,
291
+ resolutionExpiresAt,
292
+ resolutionState,
299
293
  resolutionStatus: resolution ?? (unresolvedFailure ? "none" : undefined),
300
294
  transition,
301
295
  tooltips,
@@ -332,6 +326,7 @@ const resolutionTestResultFactory = (test, index) => ({
332
326
  transition: test.transition,
333
327
  retry: test.retry,
334
328
  retriesCount: test.retriesCount,
329
+ retriesStatusChange: test.retriesStatusChange,
335
330
  resolution: test.resolution,
336
331
  groupOrder: index + 1,
337
332
  tooltips: test.tooltips,
@@ -342,14 +337,21 @@ export const generateResolutionCategories = async (writer, tests, filename = "re
342
337
  .filter((test) => !test.isRetry && test.resolution)
343
338
  .forEach((test) => {
344
339
  const resolution = test.resolution;
340
+ const mutedExpirationKey = test.resolutionExpiresAt === undefined
341
+ ? "forever"
342
+ : (parseResolutionExpiresAt(test.resolutionExpiresAt)?.toString() ?? `invalid:${test.resolutionExpiresAt}`);
345
343
  const groupId = resolution === "issue"
346
344
  ? `${resolution}:${test.resolutionIssue?.id ?? test.id}`
347
- : `${resolution}:${test.resolutionComment ?? test.id}`;
345
+ : [resolution, test.resolutionComment ?? test.id, resolution === "muted" ? mutedExpirationKey : undefined]
346
+ .filter(Boolean)
347
+ .join(":");
348
348
  const group = groupsById.get(groupId) ?? {
349
349
  id: groupId,
350
350
  resolution,
351
351
  name: getResolutionGroupName(test),
352
352
  comment: test.resolutionComment ?? test.resolutionIssue?.comment,
353
+ ...(test.resolutionExpiresAt ? { expiresAt: test.resolutionExpiresAt } : {}),
354
+ ...(test.resolutionState ? { resolutionState: test.resolutionState } : {}),
353
355
  issue: test.resolutionIssue,
354
356
  testResults: [],
355
357
  };
@@ -406,15 +408,6 @@ export const generateAttachmentsFiles = async (writer, attachmentLinks, contentF
406
408
  });
407
409
  return new Map(writtenAttachments.filter((attachment) => attachment !== undefined));
408
410
  };
409
- export const generateHistoryDataPoints = async (writer, store) => {
410
- const result = new Map();
411
- const allHistoryPoints = await store.allHistoryDataPoints();
412
- for (const historyPoint of allHistoryPoints.slice(0, 6)) {
413
- const src = `history/${historyPoint.uuid}.json`;
414
- await writer.writeData(src, historyPoint);
415
- }
416
- return result;
417
- };
418
411
  export const generateGlobals = async (writer, payload) => {
419
412
  const { globalExitCode, globalAttachments = [], globalAttachmentsByEnv = {}, globalErrors = [], globalErrorsByEnv = {}, contentFunction, } = payload;
420
413
  const globals = {
@@ -478,7 +471,7 @@ export const generateQualityGateResults = async (writer, qualityGateResults = {}
478
471
  await writer.writeWidget("quality-gate.json", resultsWithTrees);
479
472
  };
480
473
  export const generateStaticFiles = async (payload) => {
481
- const { id, reportName = "Allure Report", reportLanguage = "en", logo = "", theme = "auto", groupBy, reportFiles, reportDataFiles, reportUuid, allureVersion, layout = "base", defaultSection = "", ci, executor, runSummary, runSummaryByEnv, stepTreeExpansion, defaultSortBy, } = payload;
474
+ const { id, reportName = "Allure Report", reportLanguage = "en", logo = "", theme = "auto", groupBy, reportFiles, reportDataFiles, reportUuid, allureVersion, layout = "base", defaultSection = "", ci, git, executor, runSummary, runSummaryByEnv, stepTreeExpansion, defaultSortBy, overviewHref, } = payload;
482
475
  const staticAssets = await readReportStaticAssets(reportStaticArchive);
483
476
  const { manifest } = staticAssets;
484
477
  const headTags = [];
@@ -506,8 +499,7 @@ export const generateStaticFiles = async (payload) => {
506
499
  const mainCssContent = getReportStaticAsset(staticAssets, mainCss);
507
500
  headTags.push(createStylesLinkTag(`data:text/css;base64,${mainCssContent.toString("base64")}`));
508
501
  }
509
- const mainJsContent = getReportStaticAsset(staticAssets, mainJs);
510
- bodyTags.push(createScriptTag(`data:text/javascript;base64,${mainJsContent.toString("base64")}`));
502
+ bodyTags.push(createSingleFileScripts(staticAssets.files, mainJs));
511
503
  }
512
504
  const now = Date.now();
513
505
  const reportOptions = {
@@ -521,6 +513,7 @@ export const generateStaticFiles = async (payload) => {
521
513
  groupBy: groupBy?.length ? groupBy : [],
522
514
  cacheKey: now.toString(),
523
515
  ci,
516
+ git,
524
517
  executor,
525
518
  runSummary,
526
519
  runSummaryByEnv,
@@ -530,12 +523,13 @@ export const generateStaticFiles = async (payload) => {
530
523
  defaultSection,
531
524
  stepTreeExpansion,
532
525
  defaultSortBy,
526
+ overviewHref,
533
527
  };
534
528
  try {
535
529
  const html = compiledTemplate({
536
530
  headTags: headTags.join("\n"),
537
531
  bodyTags: bodyTags.join("\n"),
538
- reportFilesScript: createReportDataScript(reportDataFiles),
532
+ reportFilesScript: reportDataScriptPlaceholder,
539
533
  reportOptions: stringifyForInlineScript(reportOptions),
540
534
  analyticsEnable: true,
541
535
  allureVersion,
@@ -543,7 +537,7 @@ export const generateStaticFiles = async (payload) => {
543
537
  reportName,
544
538
  singleFile: payload.singleFile,
545
539
  });
546
- await reportFiles.addFile("index.html", Buffer.from(html, "utf8"));
540
+ await reportFiles.addFile("index.html", injectReportDataScript(html, reportDataFiles));
547
541
  }
548
542
  catch (err) {
549
543
  if (err instanceof RangeError) {
package/dist/model.d.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  import type { ChartOptions } from "@allurereport/charts-api";
2
2
  import type { CiDescriptor, EnvironmentsConfig, TestResult } from "@allurereport/core-api";
3
3
  import type { StepTreeExpansion } from "@allurereport/plugin-api";
4
+ export type IdeaLinksOptions = {
5
+ port?: number;
6
+ fileExtension?: string;
7
+ sourceRoot?: string;
8
+ };
4
9
  export type AwesomeOptions = {
5
10
  reportName?: string;
6
11
  singleFile?: boolean;
@@ -22,6 +27,6 @@ export type AwesomeOptions = {
22
27
  appendTitlePath?: boolean;
23
28
  stepTreeExpansion?: StepTreeExpansion;
24
29
  defaultSortBy?: string;
30
+ ideaLinks?: boolean | IdeaLinksOptions;
25
31
  };
26
- export type TemplateManifest = Record<string, string>;
27
32
  export type AwesomePluginOptions = AwesomeOptions;
package/dist/plugin.js CHANGED
@@ -10,14 +10,17 @@ 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_generateAfterStart, _AwesomePlugin_generate;
13
- import { incrementStatistic, validateEnvironmentId, joinPosixPath, } from "@allurereport/core-api";
13
+ import { incrementStatistic, hasRetriesStatusChange, isIgnoredResolution, validateEnvironmentId, joinPosixPath, } from "@allurereport/core-api";
14
+ import { collectGitHead } from "@allurereport/git";
14
15
  import { createPluginSummary, } from "@allurereport/plugin-api";
15
16
  import { preciseTreeLabels } from "@allurereport/plugin-api";
17
+ import { generateHistoryDataPoints } from "@allurereport/plugin-api/internal/report";
18
+ import { validateChartsOptions } from "@allurereport/web-commons";
16
19
  import { applyCategoriesToTestResults, generateCategories } from "./categories.js";
17
20
  import { generateTimeline } from "./generateTimeline.js";
18
- import { generateAllCharts, generateAttachmentsFiles, generateEnvironmentJson, generateEnvirontmentsList, generateGlobals, generateHistoryDataPoints, generateMetricsWidget, generateNav, generateQualityGateResults, generateResolutionCategories, generateSearchIndex, generateStaticFiles, generateStatistic, generateTestCases, generateTestEnvGroups, generateTestResults, generateTree, generateTreeFilters, generateVariables, getRunSummary, } from "./generators.js";
21
+ import { generateAllCharts, generateAttachmentsFiles, generateEnvironmentJson, generateEnvirontmentsList, generateGlobals, generateMetricsWidget, generateNav, generateQualityGateResults, generateResolutionCategories, generateSearchIndex, generateStaticFiles, generateStatistic, generateTestCases, generateTestEnvGroups, generateTestResults, generateTree, generateTreeFilters, generateVariables, getRunSummary, } from "./generators.js";
19
22
  import { InMemoryReportDataWriter, ReportFileDataWriter } from "./writer.js";
20
- const statisticByTestResults = (testResults, trIdsWithRetries) => {
23
+ const statisticByTestResults = (testResults, retriesByTrId) => {
21
24
  const statistic = { total: 0 };
22
25
  const incrementResolution = (testResult) => {
23
26
  if (testResult.resolution === "issue") {
@@ -38,9 +41,13 @@ const statisticByTestResults = (testResults, trIdsWithRetries) => {
38
41
  continue;
39
42
  }
40
43
  incrementStatistic(statistic, testResult.status);
41
- if (trIdsWithRetries.has(testResult.id)) {
44
+ const retries = retriesByTrId.get(testResult.id) ?? [];
45
+ if (retries.length > 0) {
42
46
  statistic.retries = (statistic.retries ?? 0) + 1;
43
47
  }
48
+ if (hasRetriesStatusChange(testResult, retries)) {
49
+ statistic.retriesStatusChange = (statistic.retriesStatusChange ?? 0) + 1;
50
+ }
44
51
  if (testResult.flaky) {
45
52
  statistic.flaky = (statistic.flaky ?? 0) + 1;
46
53
  }
@@ -51,7 +58,6 @@ const statisticByTestResults = (testResults, trIdsWithRetries) => {
51
58
  }
52
59
  return statistic;
53
60
  };
54
- const isActiveStatisticTestResult = (testResult) => testResult.resolution !== "muted" && testResult.resolution !== "accepted";
55
61
  export class AwesomePlugin {
56
62
  constructor(options = {}) {
57
63
  this.options = options;
@@ -64,6 +70,7 @@ export class AwesomePlugin {
64
70
  });
65
71
  _AwesomePlugin_generate.set(this, async (context, store) => {
66
72
  const { singleFile, groupBy = [], filter, appendTitlePath } = this.options ?? {};
73
+ const resolutionEvaluatedAt = Date.now();
67
74
  const perf = context.perf;
68
75
  const measure = (name, fn) => perf?.measure(name, fn) ?? fn();
69
76
  const hideLabels = context.hideLabels;
@@ -117,15 +124,9 @@ export class AwesomePlugin {
117
124
  });
118
125
  const trsByEnvId = new Map();
119
126
  await measure("stats", async () => {
120
- const trIdsWithRetries = new Set();
121
127
  const envStatistics = new Map();
122
128
  const pieEnvStatistics = new Map();
123
- related.retriesByTrId.forEach((retries, trId) => {
124
- if (retries.length > 0) {
125
- trIdsWithRetries.add(trId);
126
- }
127
- });
128
- const pieStatistics = statisticByTestResults(allTrs.filter(isActiveStatisticTestResult), trIdsWithRetries);
129
+ const pieStatistics = statisticByTestResults(allTrs.filter((testResult) => !isIgnoredResolution(testResult, resolutionEvaluatedAt)), related.retriesByTrId);
129
130
  for (const tr of allTrs) {
130
131
  const environmentId = envIdByTrId.get(tr.id);
131
132
  if (!environmentId) {
@@ -141,8 +142,8 @@ export class AwesomePlugin {
141
142
  }
142
143
  await Promise.all(environments.map(async ({ id }) => {
143
144
  const envTrs = trsByEnvId.get(id) ?? [];
144
- envStatistics.set(id, statisticByTestResults(envTrs, trIdsWithRetries));
145
- pieEnvStatistics.set(id, statisticByTestResults(envTrs.filter(isActiveStatisticTestResult), trIdsWithRetries));
145
+ envStatistics.set(id, statisticByTestResults(envTrs, related.retriesByTrId));
146
+ pieEnvStatistics.set(id, statisticByTestResults(envTrs.filter((testResult) => !isIgnoredResolution(testResult, resolutionEvaluatedAt)), related.retriesByTrId));
146
147
  }));
147
148
  await generateStatistic(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), {
148
149
  stats: statistics,
@@ -164,8 +165,10 @@ export class AwesomePlugin {
164
165
  const convertedTrs = await measure("convert", () => generateTestResults(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), store, allTrs, {
165
166
  pluginId: context.id,
166
167
  hideLabels,
168
+ ideaLinks: this.options.ideaLinks,
167
169
  related,
168
170
  resolveHistoryUrl: context.history?.resolveTestResultUrl,
171
+ resolutionEvaluatedAt,
169
172
  }));
170
173
  if (perf) {
171
174
  perf.count("convertedTestResults.count", convertedTrs.length, {
@@ -263,7 +266,9 @@ export class AwesomePlugin {
263
266
  reportFiles: context.reportFiles,
264
267
  reportUuid: context.reportUuid,
265
268
  reportName: context.reportName,
269
+ overviewHref: context.overviewHref,
266
270
  ci: context.ci,
271
+ git: collectGitHead(),
267
272
  executor,
268
273
  runSummary,
269
274
  runSummaryByEnv,
@@ -285,6 +290,7 @@ export class AwesomePlugin {
285
290
  this.done = async (context, store) => {
286
291
  await __classPrivateFieldGet(this, _AwesomePlugin_generateAfterStart, "f").call(this, context, store);
287
292
  };
293
+ validateChartsOptions(options.charts ?? []);
288
294
  }
289
295
  async info(context, store) {
290
296
  const perf = context.perf;
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allurereport/plugin-awesome",
3
- "version": "3.20.1",
3
+ "version": "3.22.0",
4
4
  "description": "Allure Awesome Plugin – brand new HTML report with modern design and new features",
5
5
  "keywords": [
6
6
  "allure",
@@ -23,31 +23,37 @@
23
23
  ".": "./dist/index.js"
24
24
  },
25
25
  "scripts": {
26
- "build": "run clean && tsc --project ./tsconfig.json && node ../../scripts/pack-report-static.mjs ../web-awesome/dist/multi ./dist/static/report.tar",
26
+ "build": "run clean && tsc --project ./tsconfig.json && node ../../scripts/pack-report-static.mjs ../web-awesome/dist/multi ./dist/static/report.tar --allow-chunks",
27
27
  "clean": "rimraf ./dist",
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"
28
+ "test": "rimraf out && vp test run",
29
+ "test:unit": "yarn test",
30
+ "lint": "vp lint --import-plugin src test features stories",
31
+ "lint:fix": "vp lint --import-plugin --fix src test features stories"
31
32
  },
32
33
  "dependencies": {
33
- "@allurereport/charts-api": "3.20.1",
34
- "@allurereport/core-api": "3.20.1",
35
- "@allurereport/plugin-api": "3.20.1",
36
- "@allurereport/web-commons": "3.20.1",
34
+ "@allurereport/charts-api": "3.22.0",
35
+ "@allurereport/core-api": "3.22.0",
36
+ "@allurereport/git": "3.22.0",
37
+ "@allurereport/plugin-api": "3.22.0",
38
+ "@allurereport/web-commons": "3.22.0",
37
39
  "d3-shape": "^3.2.0",
38
- "handlebars": "^4.7.9",
39
- "markdown-it": "^14.2.0"
40
+ "handlebars": "4.7.10",
41
+ "markdown-it": "^15.0.3"
40
42
  },
41
43
  "devDependencies": {
42
- "@allurereport/web-awesome": "3.20.1",
44
+ "@allurereport/common-config": "3.22.0",
45
+ "@allurereport/web-awesome": "3.22.0",
43
46
  "@types/d3-shape": "^3.1.8",
44
47
  "@types/markdown-it": "^14.1.2",
45
48
  "@types/node": "^20",
49
+ "@vitest/coverage-istanbul": "5.0.4",
46
50
  "@vitest/runner": "^2",
47
51
  "allure-js-commons": "^3",
48
52
  "allure-vitest": "^3",
49
53
  "rimraf": "^6",
50
54
  "typescript": "^5",
51
- "vitest": "^4.1.0"
55
+ "vite": "@voidzero-dev/vite-plus-core@1.2.0",
56
+ "vite-plus": "1.2.0",
57
+ "vitest": "5.0.4"
52
58
  }
53
59
  }