🚀. Socket Launch Week Day 3:Socket Firewall Now Blocks Malicious VS Code and Open VSX Extensions.Learn more
Sign In

@allurereport/plugin-api

Package Overview
Dependencies
Maintainers
2
Versions
43
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@allurereport/plugin-api - npm Package Compare versions

Comparing version
3.0.0-beta.20
to
3.0.0-beta.21
+0
-7
dist/index.d.ts

@@ -9,8 +9,1 @@ export * from "./config.js";

export * from "./utils/summary.js";
export * from "./charts.js";
export * from "./charts/treeMap.js";
export * from "./charts/bar.js";
export * from "./charts/line.js";
export * from "./charts/pie.js";
export * from "./charts/heatmap.js";
export * from "./charts/comingSoon.js";

@@ -6,8 +6,1 @@ export * from "./config.js";

export * from "./utils/summary.js";
export * from "./charts.js";
export * from "./charts/treeMap.js";
export * from "./charts/bar.js";
export * from "./charts/line.js";
export * from "./charts/pie.js";
export * from "./charts/heatmap.js";
export * from "./charts/comingSoon.js";
+1
-0

@@ -26,2 +26,3 @@ import type { AttachmentLink, CiDescriptor, Statistic, TestError, TestResult, TestStatus } from "@allurereport/core-api";

reportFiles: ReportFiles;
reportUrl?: string;
ci?: CiDescriptor;

@@ -28,0 +29,0 @@ }

@@ -28,2 +28,3 @@ import type { AttachmentLink, HistoryDataPoint, HistoryTestResult, KnownTestFailure, ReportVariables, Statistic, TestCase, TestEnvGroup, TestError, TestFixtureResult, TestResult } from "@allurereport/core-api";

attachmentsByTrId: (trId: string) => Promise<AttachmentLink[]>;
retriesByTr: (tr: TestResult) => Promise<TestResult[]>;
retriesByTrId: (trId: string) => Promise<TestResult[]>;

@@ -54,2 +55,3 @@ historyByTrId: (trId: string) => Promise<HistoryTestResult[]>;

reportVariables: ReportVariables;
qualityGateResultsByRules: Record<string, QualityGateValidationResult>;
indexAttachmentByTestResult: Record<string, string[]>;

@@ -78,3 +80,4 @@ indexTestResultByHistoryId: Record<string, string[]>;

IndexFixturesByTestResult = "index-fixtures-by-test-result.json",
IndexKnownByHistoryId = "index-known-by-history-id.json"
IndexKnownByHistoryId = "index-known-by-history-id.json",
QualityGateResultsByRules = "quality-gate-results-by-rules.json"
}

@@ -18,2 +18,3 @@ export var AllureStoreDumpFiles;

AllureStoreDumpFiles["IndexKnownByHistoryId"] = "index-known-by-history-id.json";
AllureStoreDumpFiles["QualityGateResultsByRules"] = "quality-gate-results-by-rules.json";
})(AllureStoreDumpFiles || (AllureStoreDumpFiles = {}));
{
"name": "@allurereport/plugin-api",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.21",
"description": "Allure Plugin API",

@@ -29,3 +29,3 @@ "keywords": [

"dependencies": {
"@allurereport/core-api": "3.0.0-beta.20"
"@allurereport/core-api": "3.0.0-beta.21"
},

@@ -32,0 +32,0 @@ "devDependencies": {

import type { BarChartType, BarGroup, BarGroupMode, BaseTrendSliceMetadata, ChartDataType, ChartId, ChartMode, ChartType, HeatMapSerie, HistoryDataPoint, HistoryTestResult, PieSlice, SeverityLevel, Statistic, TestResult, TestStatus, TreeMapChartType, TreeMapNode, TrendPoint, TrendPointId, TrendSlice, TrendSliceId } from "@allurereport/core-api";
export type ExecutionIdFn = (executionOrder: number) => string;
export type ExecutionNameFn = (executionOrder: number) => string;
export type TrendMetadataFnOverrides = {
executionIdAccessor?: ExecutionIdFn;
executionNameAccessor?: ExecutionNameFn;
};
export type TrendDataType = TestStatus | SeverityLevel;
export type TrendStats<T extends TrendDataType> = Record<T, number>;
export type TrendCalculationResult<T extends TrendDataType> = {
points: Record<TrendPointId, TrendPoint>;
series: Record<T, TrendPointId[]>;
};
export interface GenericTrendChartData<SeriesType extends string, Metadata extends BaseTrendSliceMetadata = BaseTrendSliceMetadata> {
type: ChartType.Trend;
dataType: ChartDataType;
mode: ChartMode;
title?: string;
points: Record<TrendPointId, TrendPoint>;
slices: Record<TrendSliceId, TrendSlice<Metadata>>;
series: Record<SeriesType, TrendPointId[]>;
min: number;
max: number;
}
export type StatusTrendChartData = GenericTrendChartData<TestStatus>;
export type SeverityTrendChartData = GenericTrendChartData<SeverityLevel>;
export type TrendChartData = StatusTrendChartData | SeverityTrendChartData;
export interface BarChartData {
type: ChartType.Bar;
dataType: BarChartType;
mode: ChartMode;
title?: string;
data: BarGroup<string, string>[];
keys: readonly string[];
indexBy: string;
groupMode: BarGroupMode;
}
export interface TreeMapChartData {
type: ChartType.TreeMap;
dataType: TreeMapChartType;
title?: string;
treeMap: TreeMapNode;
}
export interface HeatMapChartData<T extends Record<string, any> = {}> {
type: ChartType.HeatMap;
title?: string;
data: HeatMapSerie<T>[];
}
export interface PieChartData {
type: ChartType.Pie;
title?: string;
slices: PieSlice[];
percentage: number;
}
export interface ComingSoonChartData {
type: ChartType.ComingSoon;
title?: string;
}
export type GeneratedChartData = TrendChartData | PieChartData | BarChartData | ComingSoonChartData | TreeMapChartData | HeatMapChartData;
export type GeneratedChartsData = Record<ChartId, GeneratedChartData>;
export type TrendChartOptions = {
type: ChartType.Trend;
dataType: ChartDataType;
mode?: ChartMode;
title?: string;
limit?: number;
metadata?: TrendMetadataFnOverrides;
};
export type PieChartOptions = {
type: ChartType.Pie;
title?: string;
};
export type BarChartOptions = {
type: ChartType.Bar;
dataType: BarChartType;
mode?: ChartMode;
title?: string;
limit?: number;
};
export type TreeMapChartOptions = {
type: ChartType.TreeMap;
dataType: TreeMapChartType;
title?: string;
};
export type HeatMapChartOptions = {
type: ChartType.HeatMap;
title?: string;
};
export type ComingSoonChartOptions = {
type: ChartType.ComingSoon;
title?: string;
};
export type ChartOptions = TrendChartOptions | PieChartOptions | BarChartOptions | ComingSoonChartOptions | TreeMapChartOptions | HeatMapChartOptions;
export interface AllureChartsStoreData {
historyDataPoints: HistoryDataPoint[];
testResults: TestResult[];
statistic: Statistic;
}
export interface TrendDataAccessor<T extends TrendDataType> {
getCurrentData: (storeData: AllureChartsStoreData) => TrendStats<T>;
getHistoricalData: (historyPoint: HistoryDataPoint) => TrendStats<T>;
getAllValues: () => readonly T[];
}
export interface BarDataAccessor<G extends string, T extends string> {
getItems: (storeData: AllureChartsStoreData, limitedHistoryDataPoints: HistoryDataPoint[], isFullHistory: boolean) => BarGroup<G, T>[];
getGroupKeys: () => readonly T[];
getGroupMode: () => BarGroupMode;
}
export interface TreeMapDataAccessor<T extends TreeMapNode> {
getTreeMap: (storeData: AllureChartsStoreData) => T;
}
export interface HeatMapDataAccessor<T extends Record<string, unknown> = {}> {
getHeatMap: (storeData: AllureChartsStoreData) => HeatMapSerie<T>[];
}
export declare const DEFAULT_CHART_HISTORY_LIMIT = 10;
export declare const limitHistoryDataPoints: (historyDataPoints: HistoryDataPoint[], limit: number) => HistoryDataPoint[];
export declare const createEmptySeries: <T extends string>(items: readonly T[]) => Record<T, string[]>;
export declare const createEmptyStats: <T extends string>(items: readonly T[]) => Record<T, number>;
export declare const normalizeStatistic: <T extends string>(statistic: Partial<Record<T, number>>, itemType: readonly T[]) => Record<T, number>;
export declare const hasLabels: <T extends string, TR extends TestResult | HistoryTestResult>(test: TR, labelHierarchy: T[]) => boolean;
export declare const isChildrenLeavesOnly: <T extends TreeMapNode>(node: T) => boolean;
export declare const defaultChartsConfig: ({
type: string;
title: string;
dataType?: undefined;
} | {
type: string;
dataType: string;
title: string;
})[];
export const DEFAULT_CHART_HISTORY_LIMIT = 10;
export const limitHistoryDataPoints = (historyDataPoints, limit) => {
if (limit <= 0 || historyDataPoints.length === 0) {
return [];
}
const clampedLimit = Math.max(0, Math.floor(limit));
return historyDataPoints.slice(0, clampedLimit);
};
export const createEmptySeries = (items) => items.reduce((acc, item) => ({ ...acc, [item]: [] }), {});
export const createEmptyStats = (items) => items.reduce((acc, item) => ({ ...acc, [item]: 0 }), {});
export const normalizeStatistic = (statistic, itemType) => {
return itemType.reduce((acc, item) => {
acc[item] = statistic[item] ?? 0;
return acc;
}, {});
};
export const hasLabels = (test, labelHierarchy) => test.labels?.some((label) => {
const { name } = label;
return name && labelHierarchy.includes(name);
}) ?? false;
export const isChildrenLeavesOnly = (node) => {
return node.children ? node.children.every((child) => child.children === undefined) : false;
};
export const defaultChartsConfig = [
{
type: "pie",
title: "Current status",
},
{
type: "trend",
dataType: "status",
title: "Status dynamics",
},
{
type: "bar",
dataType: "statusBySeverity",
title: "Test result severities",
},
{
type: "bar",
dataType: "statusTrend",
title: "Status change dynamics",
},
{
type: "bar",
dataType: "statusChangeTrend",
title: "Test base growth dynamics",
},
{
type: "treemap",
dataType: "coverageDiff",
title: "Coverage diff map",
},
{
type: "treemap",
dataType: "successRateDistribution",
title: "Success rate disctribution",
},
{
type: "heatmap",
title: "Problems distribution by environment",
},
{
type: "bar",
title: "Stability rate disctribution",
},
{
type: "bar",
title: "Duration by layer histogram",
},
{
type: "bar",
title: "Performance dynamics",
},
{
type: "bar",
title: "FBSU age pyramid",
},
{
type: "funnel",
title: "Testing pyramid",
},
];
import type { TreeMapNode } from "@allurereport/core-api";
import type { TreeMapDataAccessor } from "../../charts.js";
type ChangeType = "new" | "deleted" | "enabled" | "disabled" | "unchanged";
type SubtreeMetrics = {
totalTests: number;
newCount: number;
deletedCount: number;
disabledCount: number;
enabledCount: number;
};
type LeafMetrics = {
changeType: ChangeType;
};
type GroupMetrics = Omit<SubtreeMetrics, "totalTests">;
type ExtendedTreeMapNode = TreeMapNode<GroupMetrics & Partial<LeafMetrics>>;
export declare const coverageDiffTreeMapAccessor: TreeMapDataAccessor<ExtendedTreeMapNode>;
export {};
import { isChildrenLeavesOnly } from "../../charts.js";
import { md5 } from "../../utils/misc.js";
import { createTreeByLabels } from "../../utils/tree.js";
import { convertTreeDataToTreeMapNode, transformTreeMapNode } from "../treeMap.js";
import { behaviorLabels, filterTestsWithBehaviorLabels } from "./utils/behavior.js";
const groupFactoryFn = (parentId, groupClassifier) => ({
nodeId: md5((parentId ? `${parentId}.` : "") + groupClassifier),
name: groupClassifier,
value: 0,
newCount: 0,
deletedCount: 0,
disabledCount: 0,
enabledCount: 0,
});
const addLeafToGroupFn = (group, leaf) => {
group.value += leaf.value;
switch (leaf.changeType) {
case "new":
group.newCount++;
break;
case "deleted":
group.deletedCount++;
break;
case "enabled":
group.enabledCount++;
break;
case "disabled":
group.disabledCount++;
break;
}
};
const calculateColorValue = (metrics) => {
const netChange = metrics.newCount + metrics.enabledCount - (metrics.deletedCount + metrics.disabledCount);
const normalizedChange = netChange / metrics.totalTests;
return Math.max(0, Math.min(1, (normalizedChange + 1) / 2));
};
const isSkipped = (tr) => tr.status === "skipped";
const getNewTestResults = (trs, closestHtrs) => {
return trs.filter((tr) => !closestHtrs[tr.historyId]);
};
const getRemovedTestResults = (trs, closestHtrs) => {
const historyPointTestResultsAsArray = Object.values(closestHtrs);
const testResultsAsDictionary = Object.fromEntries(trs.map((tr) => [tr.historyId, tr]));
return historyPointTestResultsAsArray.filter((htr) => !testResultsAsDictionary[htr.historyId]);
};
const getEnabledTestResults = (trs, closestHtrs) => {
return trs.filter((tr) => {
const historyPointTestResult = closestHtrs[tr.historyId];
return historyPointTestResult && isSkipped(historyPointTestResult) && !isSkipped(tr);
});
};
const getDisabledTestResults = (trs, closestHtrs) => {
return trs.filter((tr) => {
const historyPointTestResult = closestHtrs[tr.historyId];
return historyPointTestResult && !isSkipped(historyPointTestResult) && isSkipped(tr);
});
};
const calculateSubtreeMetrics = (node) => {
if (!node.children || node.children.length === 0) {
const changeType = node?.changeType;
return {
totalTests: 1,
newCount: changeType === "new" ? 1 : 0,
deletedCount: changeType === "deleted" ? 1 : 0,
disabledCount: changeType === "disabled" ? 1 : 0,
enabledCount: changeType === "enabled" ? 1 : 0,
};
}
let totalTests = 0;
let newCount = 0;
let deletedCount = 0;
let disabledCount = 0;
let enabledCount = 0;
for (const child of node.children) {
const childMetrics = calculateSubtreeMetrics(child);
totalTests += childMetrics.totalTests;
newCount += childMetrics.newCount;
deletedCount += childMetrics.deletedCount;
disabledCount += childMetrics.disabledCount;
enabledCount += childMetrics.enabledCount;
}
return { totalTests, newCount, deletedCount, disabledCount, enabledCount };
};
const createCoverageDiffTreeMap = (trs, closestHtrs) => {
const newTrs = getNewTestResults(trs, closestHtrs);
const removedHtrs = getRemovedTestResults(trs, closestHtrs);
const enabledTrs = getEnabledTestResults(trs, closestHtrs);
const disabledTrs = getDisabledTestResults(trs, closestHtrs);
const newTestsById = new Map(newTrs.map((tr) => [tr.historyId, tr]));
const deletedTestsById = new Map(removedHtrs.map((htr) => [htr.historyId, htr]));
const enabledTestsById = new Map(enabledTrs.map((tr) => [tr.historyId, tr]));
const disabledTestsById = new Map(disabledTrs.map((tr) => [tr.historyId, tr]));
const allTests = [...trs, ...removedHtrs];
const getChangeType = (historyId) => {
if (newTestsById.has(historyId)) {
return "new";
}
if (deletedTestsById.has(historyId)) {
return "deleted";
}
if (enabledTestsById.has(historyId)) {
return "enabled";
}
if (disabledTestsById.has(historyId)) {
return "disabled";
}
return "unchanged";
};
const leafFactoryFnWithMaps = (test) => {
const changeType = getChangeType(test.historyId);
return {
nodeId: test.id,
name: test.name,
value: 1,
changeType,
};
};
const treeByLabels = createTreeByLabels(allTests, behaviorLabels, leafFactoryFnWithMaps, groupFactoryFn, addLeafToGroupFn);
const convertedTree = convertTreeDataToTreeMapNode(treeByLabels, (node, isGroup) => {
const baseNode = {
id: node.name,
value: isGroup ? undefined : node.value,
};
if (isGroup) {
const group = node;
return {
...baseNode,
newCount: group.newCount,
deletedCount: group.deletedCount,
disabledCount: group.disabledCount,
enabledCount: group.enabledCount,
};
}
else {
const leaf = node;
return {
...baseNode,
changeType: leaf.changeType,
newCount: leaf.changeType === "new" ? 1 : 0,
deletedCount: leaf.changeType === "deleted" ? 1 : 0,
disabledCount: leaf.changeType === "disabled" ? 1 : 0,
enabledCount: leaf.changeType === "enabled" ? 1 : 0,
};
}
}, () => ({
id: "root",
newCount: 0,
deletedCount: 0,
disabledCount: 0,
enabledCount: 0,
}));
return transformTreeMapNode(convertedTree, (node) => {
const subtreeMetrics = calculateSubtreeMetrics(node);
const colorValue = calculateColorValue(subtreeMetrics);
const { totalTests, ...restSubtreeMetrics } = subtreeMetrics;
if (isChildrenLeavesOnly(node)) {
return {
...node,
value: totalTests,
children: undefined,
colorValue,
...restSubtreeMetrics,
};
}
return {
...node,
colorValue,
...restSubtreeMetrics,
};
});
};
export const coverageDiffTreeMapAccessor = {
getTreeMap: ({ testResults, historyDataPoints }) => {
const testsWithBehaviorLabels = filterTestsWithBehaviorLabels(testResults);
const closestHdp = historyDataPoints[0];
const closestHtrs = closestHdp.testResults;
const closestHtrsWithBehaviorLabels = filterTestsWithBehaviorLabels(Object.values(closestHtrs));
const closestHtrsWithBehaviorLabelsById = Object.fromEntries(closestHtrsWithBehaviorLabels.map((htr) => [htr.historyId, htr]));
return createCoverageDiffTreeMap(testsWithBehaviorLabels, closestHtrsWithBehaviorLabelsById);
},
};
import type { HeatMapDataAccessor } from "../../charts.js";
export declare const problemsDistributionHeatMapAccessor: HeatMapDataAccessor;
import { filterIncludedInSuccessRate } from "@allurereport/core-api";
const groupTestsByEnvironment = (testResults) => {
return testResults.reduce((acc, testResult) => {
const key = testResult.environment;
if (key) {
const bucket = acc[key] || (acc[key] = []);
bucket.push(testResult);
}
return acc;
}, {});
};
const groupByExactLabel = (testResults, labelNames) => {
return testResults.reduce((acc, testResult) => {
const labels = testResult.labels;
if (!labels) {
return acc;
}
for (const label of labels) {
const key = label.value;
if (labelNames.includes(label.name) && key) {
const bucket = acc[key] || (acc[key] = []);
bucket.push(testResult);
}
}
return acc;
}, {});
};
const makeHeatMapSerie = (env, testResults) => {
const testResultsByExactLabel = groupByExactLabel(testResults, ["feature"]);
const data = [];
for (const [labelValue, testsByLabelValue] of Object.entries(testResultsByExactLabel)) {
const testsTotal = testsByLabelValue.length;
const totalNegative = testsByLabelValue.reduce((acc, test) => acc + (test.status !== "passed" ? 1 : 0), 0);
data.push({
x: labelValue,
y: totalNegative / testsTotal,
});
}
return {
id: env,
data: data.sort((a, b) => (a.y || 0) - (b.y || 0)),
};
};
const makeHeatMapData = (testsByEnvironment) => {
return Object.entries(testsByEnvironment).map(([env, tests]) => makeHeatMapSerie(env, tests));
};
const filterTestResultsBySignificantStatus = (testResults) => {
return testResults.filter(filterIncludedInSuccessRate);
};
const filterTestResultsByLabelNames = (testResults, labelNames) => {
return testResults.filter((test) => test.labels?.some((l) => labelNames.includes(l.name)));
};
export const problemsDistributionHeatMapAccessor = {
getHeatMap: ({ testResults }) => {
const filteredTestResults = filterTestResultsBySignificantStatus(filterTestResultsByLabelNames(testResults, ["feature"]));
const testsResultsByEnvironment = groupTestsByEnvironment(filteredTestResults);
const data = makeHeatMapData(testsResultsByEnvironment);
const totals = new Map();
for (const serie of data) {
const total = serie.data.reduce((acc, sd) => acc + (sd.y || 0), 0);
totals.set(serie.id, total);
}
return data.sort((a, b) => totals.get(a.id) - totals.get(b.id));
},
};
import type { SeverityLevel } from "@allurereport/core-api";
import type { TrendDataAccessor } from "../../charts.js";
export declare const severityTrendDataAccessor: TrendDataAccessor<SeverityLevel>;
import { severityLabelName, severityLevels } from "@allurereport/core-api";
import { createEmptyStats } from "../../charts.js";
const processTestResults = (testResults) => {
return testResults.reduce((acc, test) => {
const severityLabel = test.labels?.find((label) => label.name === severityLabelName);
const severity = severityLabel?.value?.toLowerCase();
if (severity) {
acc[severity] = (acc[severity] ?? 0) + 1;
}
return acc;
}, createEmptyStats(severityLevels));
};
export const severityTrendDataAccessor = {
getCurrentData: ({ testResults }) => {
return processTestResults(testResults);
},
getHistoricalData: (historyPoint) => {
return processTestResults(Object.values(historyPoint.testResults));
},
getAllValues: () => severityLevels,
};
import type { SeverityLevel, TestStatus } from "@allurereport/core-api";
import type { BarDataAccessor } from "../../charts.js";
export declare const statusBySeverityBarDataAccessor: BarDataAccessor<SeverityLevel, TestStatus>;
import { BarGroupMode, severityLabelName, severityLevels, statusesList } from "@allurereport/core-api";
const processTestResults = (testResults) => {
const resultMap = {
blocker: undefined,
critical: undefined,
normal: undefined,
minor: undefined,
trivial: undefined,
};
severityLevels.forEach((severity) => {
resultMap[severity] = statusesList.reduce((acc, status) => ({ ...acc, [status]: 0 }), {});
});
testResults.forEach((test) => {
const severityLabel = test.labels?.find((label) => label.name === severityLabelName);
const severity = severityLabel?.value?.toLowerCase();
if (severity && resultMap[severity]) {
resultMap[severity][test.status] = (resultMap[severity][test.status] ?? 0) + 1;
}
});
return Object.entries(resultMap).reduce((acc, [severity, values]) => {
if (values) {
acc.push({ groupId: severity, ...values });
}
return acc;
}, []);
};
export const statusBySeverityBarDataAccessor = {
getItems: ({ testResults }) => {
return processTestResults(testResults);
},
getGroupKeys: () => statusesList,
getGroupMode: () => BarGroupMode.Grouped,
};
import type { NewKey, RemovedKey, TestStatus } from "@allurereport/core-api";
import { type BarDataAccessor } from "../../charts.js";
export type StatusChangeTrendKeys = NewKey<TestStatus> | RemovedKey<TestStatus>;
export declare const statusChangeTrendBarAccessor: BarDataAccessor<string, StatusChangeTrendKeys>;
import { BarGroupMode, capitalize } from "@allurereport/core-api";
import { createEmptyStats } from "../../charts.js";
const newGroupKeys = ["newPassed", "newFailed", "newBroken", "newSkipped", "newUnknown"];
const removedGroupKeys = [
"removedPassed",
"removedFailed",
"removedBroken",
"removedSkipped",
"removedUnknown",
];
const groupKeys = [...newGroupKeys, ...removedGroupKeys];
const getNewKey = (status) => {
const capitalizedStatus = capitalize(status);
return capitalizedStatus ? `new${capitalizedStatus}` : undefined;
};
const getRemovedKey = (status) => {
const capitalizedStatus = capitalize(status);
return capitalizedStatus ? `removed${capitalizedStatus}` : undefined;
};
const isHistoryIdIn = (trs, historyId) => {
return trs.some((tr) => tr.historyId === historyId);
};
const getDeletedFrom = (trs, hdpTrs) => {
const stats = createEmptyStats(groupKeys);
for (const hdpTr of hdpTrs) {
if (!isHistoryIdIn(trs, hdpTr.historyId)) {
const key = getRemovedKey(hdpTr.status);
if (key) {
stats[key]--;
}
}
}
return stats;
};
const getNewFrom = (trs, hdpTrs) => {
const stats = createEmptyStats(groupKeys);
for (const tr of trs) {
if (!isHistoryIdIn(hdpTrs, tr.historyId)) {
const key = getNewKey(tr.status);
if (key) {
stats[key]++;
}
}
}
return stats;
};
const getPointStats = (currentTrs, hdpTrs) => {
const emptyStats = createEmptyStats(groupKeys);
const newStats = getNewFrom(currentTrs, hdpTrs);
const deletedStats = getDeletedFrom(currentTrs, hdpTrs);
return Object.keys(emptyStats).reduce((acc, key) => {
const newStat = newStats[key] ?? 0;
const deletedStat = deletedStats[key] ?? 0;
acc[key] = newStat + deletedStat;
return acc;
}, {});
};
const getCurrentStats = (testResults, hdpTrs) => {
return {
groupId: "current",
...getPointStats(testResults, hdpTrs),
};
};
const getHistoricalStats = (hdps) => {
const trendData = [];
for (let i = 0; i < hdps.length; i++) {
const currentHdp = hdps[i];
const currentHdpTrs = Object.values(currentHdp.testResults);
const previousHdpTrs = i + 1 < hdps.length ? Object.values(hdps[i + 1].testResults) : [];
trendData.push({
groupId: `Point ${hdps.length - i - 1}`,
...getPointStats(currentHdpTrs, previousHdpTrs),
});
}
return trendData;
};
const getTrendData = (currentTrs, hdps) => {
const historicalStats = getHistoricalStats(hdps);
const currentStats = getCurrentStats(currentTrs, Object.values(hdps[0].testResults));
return [currentStats, ...historicalStats];
};
export const statusChangeTrendBarAccessor = {
getItems: ({ testResults }, limitedHdps, isFullHistory) => {
let trendData = getTrendData(testResults, limitedHdps);
if (!isFullHistory) {
trendData = trendData.slice(0, -1);
}
return trendData.reverse();
},
getGroupKeys: () => groupKeys,
getGroupMode: () => BarGroupMode.Stacked,
};
import type { TestStatus } from "@allurereport/core-api";
import type { TrendDataAccessor } from "../../charts.js";
export declare const statusTrendDataAccessor: TrendDataAccessor<TestStatus>;
import { statusesList } from "@allurereport/core-api";
import { createEmptyStats } from "../../charts.js";
export const statusTrendDataAccessor = {
getCurrentData: ({ statistic }) => {
return {
...createEmptyStats(statusesList),
...statistic,
};
},
getHistoricalData: (historyPoint) => {
return Object.values(historyPoint.testResults).reduce((stat, test) => {
if (test.status) {
stat[test.status] = (stat[test.status] ?? 0) + 1;
}
return stat;
}, createEmptyStats(statusesList));
},
getAllValues: () => statusesList,
};
import type { TestStatus } from "@allurereport/core-api";
import { type BarDataAccessor } from "../../charts.js";
type TrendKey = Extract<TestStatus, "passed" | "failed" | "broken">;
export declare const statusTrendBarAccessor: BarDataAccessor<string, TrendKey>;
export {};
import { BarGroupMode, htrsByTr } from "@allurereport/core-api";
import { createEmptyStats } from "../../charts.js";
const groupKeys = ["passed", "failed", "broken"];
const isGroupKey = (key) => groupKeys.includes(key);
const getSignedValueByStatus = (status) => (status === "passed" ? 1 : -1);
const hasSignificantStatus = (htr) => isGroupKey(htr.status);
const getLastSignificantStatus = (history = []) => {
const significantHtr = history.find(hasSignificantStatus);
return significantHtr?.status;
};
const isDifferentStatuses = (currentStatus, lastSignificantStatus) => {
return (!!lastSignificantStatus &&
isGroupKey(currentStatus) &&
isGroupKey(lastSignificantStatus) &&
currentStatus !== lastSignificantStatus);
};
const getPointStats = (currentTrs, hdps) => {
const stats = createEmptyStats(groupKeys);
for (const tr of currentTrs) {
const htrs = htrsByTr(hdps, tr);
const currentStatus = tr.status;
const lastSignificantStatus = getLastSignificantStatus(htrs);
if (isDifferentStatuses(currentStatus, lastSignificantStatus)) {
stats[currentStatus] = stats[currentStatus] + getSignedValueByStatus(currentStatus);
}
}
return stats;
};
const getCurrentStats = (testResults, hdps) => {
return {
groupId: "current",
...getPointStats(testResults, hdps),
};
};
const getHistoricalStats = (hdps) => {
const trendData = [];
for (let i = 0; i < hdps.length; i++) {
const currentHdp = hdps[i];
const currentHdpTrs = Object.values(currentHdp.testResults);
const restHdps = i + 1 < hdps.length ? hdps.slice(i + 1, hdps.length) : [];
trendData.push({
groupId: `Point ${hdps.length - i - 1}`,
...getPointStats(currentHdpTrs, restHdps),
});
}
return trendData;
};
const getTrendData = (currentTrs, hdps) => {
const historicalStats = getHistoricalStats(hdps);
const currentStats = getCurrentStats(currentTrs, hdps);
return [currentStats, ...historicalStats];
};
export const statusTrendBarAccessor = {
getItems: ({ testResults }, limitedHdps, isFullHistory) => {
let trendData = getTrendData(testResults, limitedHdps);
if (!isFullHistory) {
trendData = trendData.slice(0, -1);
}
return trendData.reverse();
},
getGroupKeys: () => groupKeys,
getGroupMode: () => BarGroupMode.Stacked,
};
import type { TestResult, TreeMapNode } from "@allurereport/core-api";
import type { TreeMapDataAccessor } from "../../charts.js";
type SubtreeMetrics = {
totalTests: number;
passedTests: number;
failedTests: number;
otherTests: number;
};
type LeafMetrics = Pick<TestResult, "status">;
type GroupMetrics = Omit<SubtreeMetrics, "totalTests">;
type ExtendedTreeMapNode = TreeMapNode<GroupMetrics & Partial<LeafMetrics>>;
export declare const createSuccessRateDistributionTreeMap: (testResults: TestResult[]) => ExtendedTreeMapNode;
export declare const successRateDistributionTreeMapAccessor: TreeMapDataAccessor<ExtendedTreeMapNode>;
export {};
import { isChildrenLeavesOnly } from "../../charts.js";
import { md5 } from "../../utils/misc.js";
import { createTreeByLabels } from "../../utils/tree.js";
import { convertTreeDataToTreeMapNode, transformTreeMapNode } from "../treeMap.js";
import { behaviorLabels, filterTestsWithBehaviorLabels } from "./utils/behavior.js";
const leafFactoryFn = ({ id, name, status }) => ({
nodeId: id,
name,
status,
value: 1,
});
const groupFactoryFn = (parentId, groupClassifier) => ({
nodeId: md5((parentId ? `${parentId}.` : "") + groupClassifier),
name: groupClassifier,
value: 0,
passedTests: 0,
failedTests: 0,
otherTests: 0,
});
const addLeafToGroupFn = (group, leaf) => {
group.value += leaf.value;
group.passedTests += leaf.status === "passed" ? 1 : 0;
group.failedTests += leaf.status === "failed" ? 1 : 0;
group.otherTests += leaf?.status && !["passed", "failed"].includes(leaf.status) ? 1 : 0;
};
const calculateColorValue = ({ totalTests, passedTests }) => {
return totalTests > 0 ? passedTests / totalTests : 0;
};
const calculateSubtreeMetrics = (node) => {
if (!node.children || node.children.length === 0) {
return {
totalTests: 1,
passedTests: node?.status === "passed" ? 1 : 0,
failedTests: node?.status === "failed" ? 1 : 0,
otherTests: node?.status && !["passed", "failed"].includes(node.status) ? 1 : 0,
};
}
let totalTests = 0;
let passedTests = 0;
let failedTests = 0;
let otherTests = 0;
for (const child of node.children) {
const childMetrics = calculateSubtreeMetrics(child);
totalTests += childMetrics.totalTests;
passedTests += childMetrics.passedTests;
failedTests += childMetrics.failedTests;
otherTests += childMetrics.otherTests;
}
return { totalTests, passedTests, failedTests, otherTests };
};
export const createSuccessRateDistributionTreeMap = (testResults) => {
const treeByLabels = createTreeByLabels(testResults, behaviorLabels, leafFactoryFn, groupFactoryFn, addLeafToGroupFn);
const convertedTree = convertTreeDataToTreeMapNode(treeByLabels, (node, isGroup) => {
const baseNode = {
id: node.name,
value: isGroup ? undefined : node.value,
};
if (isGroup) {
const group = node;
return {
...baseNode,
passedTests: group.passedTests,
failedTests: group.failedTests,
otherTests: group.otherTests,
};
}
else {
const leaf = node;
return {
...baseNode,
status: leaf.status,
passedTests: leaf?.status === "passed" ? 1 : 0,
failedTests: leaf?.status === "failed" ? 1 : 0,
otherTests: leaf?.status && !["passed", "failed"].includes(leaf.status) ? 1 : 0,
};
}
}, () => ({
id: "root",
passedTests: 0,
failedTests: 0,
otherTests: 0,
}));
return transformTreeMapNode(convertedTree, (node) => {
const subtreeMetrics = calculateSubtreeMetrics(node);
const colorValue = calculateColorValue(subtreeMetrics);
const { totalTests, ...restSubtreeMetrics } = subtreeMetrics;
if (isChildrenLeavesOnly(node)) {
const value = node.children?.reduce((acc, child) => {
return acc + (child.value ?? 0);
}, 0);
return {
...node,
value,
children: undefined,
colorValue,
...restSubtreeMetrics,
};
}
return {
...node,
colorValue,
...restSubtreeMetrics,
};
});
};
export const successRateDistributionTreeMapAccessor = {
getTreeMap: ({ testResults }) => {
const testsWithBehaviorLabels = filterTestsWithBehaviorLabels(testResults);
return createSuccessRateDistributionTreeMap(testsWithBehaviorLabels);
},
};
import type { HistoryTestResult, TestResult } from "@allurereport/core-api";
export type BehaviorLabel = "epic" | "feature" | "story";
export declare const behaviorLabels: BehaviorLabel[];
export declare const hasBehaviorLabels: <T extends TestResult | HistoryTestResult>(test: T) => boolean;
export declare const filterTestsWithBehaviorLabels: <T extends TestResult | HistoryTestResult>(tests: T[]) => T[];
import { hasLabels } from "../../../charts.js";
export const behaviorLabels = ["epic", "feature", "story"];
export const hasBehaviorLabels = (test) => hasLabels(test, behaviorLabels);
export const filterTestsWithBehaviorLabels = (tests) => tests.filter(hasBehaviorLabels);
import type { AllureChartsStoreData, BarChartData, BarChartOptions, BarDataAccessor } from "../charts.js";
export declare const generateBarChartGeneric: <P extends string, T extends string>(options: BarChartOptions, storeData: AllureChartsStoreData, dataAccessor: BarDataAccessor<P, T>) => BarChartData | undefined;
export declare const generateBarChart: (options: BarChartOptions, storeData: AllureChartsStoreData) => BarChartData | undefined;
import { BarChartType, ChartMode } from "@allurereport/core-api";
import { DEFAULT_CHART_HISTORY_LIMIT, limitHistoryDataPoints } from "../charts.js";
import { statusBySeverityBarDataAccessor } from "./accessors/statusBySeverityBarAccessor.js";
import { statusChangeTrendBarAccessor } from "./accessors/statusChangeTrendBarAccessor.js";
import { statusTrendBarAccessor } from "./accessors/statusTrendBarAccessor.js";
export const generateBarChartGeneric = (options, storeData, dataAccessor) => {
const { type, dataType, title, limit = DEFAULT_CHART_HISTORY_LIMIT, mode = ChartMode.Raw } = options;
const { historyDataPoints } = storeData;
const limitedHistoryPoints = limitHistoryDataPoints(historyDataPoints, limit);
const isFullHistory = limitedHistoryPoints.length === historyDataPoints.length;
const items = dataAccessor.getItems(storeData, limitedHistoryPoints, isFullHistory);
let processedData = items;
if (mode === ChartMode.Percent) {
processedData = items.map((group) => {
const { groupId, ...values } = group;
const total = Object.values(values).reduce((sum, value) => sum + value, 0);
const nextValues = Object.keys(values).reduce((acc, valueKey) => {
acc[valueKey] = values[valueKey] / total;
return acc;
}, {});
return {
groupId,
...nextValues,
};
});
}
return {
type,
dataType,
mode,
title,
data: processedData,
keys: dataAccessor.getGroupKeys(),
groupMode: dataAccessor.getGroupMode(),
indexBy: "groupId",
};
};
export const generateBarChart = (options, storeData) => {
const newOptions = { limit: DEFAULT_CHART_HISTORY_LIMIT, ...options };
const { dataType } = newOptions;
if (dataType === BarChartType.StatusBySeverity) {
return generateBarChartGeneric(newOptions, storeData, statusBySeverityBarDataAccessor);
}
else if (dataType === BarChartType.StatusTrend) {
return generateBarChartGeneric(newOptions, storeData, statusTrendBarAccessor);
}
else if (dataType === BarChartType.StatusChangeTrend) {
return generateBarChartGeneric(newOptions, storeData, statusChangeTrendBarAccessor);
}
};
import type { ComingSoonChartData, ComingSoonChartOptions } from "../charts.js";
export declare const generateComingSoonChart: (options: ComingSoonChartOptions) => ComingSoonChartData;
import { ChartType } from "@allurereport/core-api";
export const generateComingSoonChart = (options) => {
return {
type: ChartType.ComingSoon,
title: options.title,
};
};
import type { AllureChartsStoreData, HeatMapChartData, HeatMapChartOptions, HeatMapDataAccessor } from "../charts.js";
export declare const generateHeatMapChartGeneric: <T extends Record<string, unknown>>(options: HeatMapChartOptions, storeData: AllureChartsStoreData, dataAccessor: HeatMapDataAccessor<T>) => HeatMapChartData | undefined;
export declare const generateHeatMapChart: (options: HeatMapChartOptions, storeData: AllureChartsStoreData) => HeatMapChartData | undefined;
import { problemsDistributionHeatMapAccessor } from "./accessors/problemsDistributionHeatMap.js";
export const generateHeatMapChartGeneric = (options, storeData, dataAccessor) => ({
type: options.type,
title: options.title,
data: dataAccessor.getHeatMap(storeData),
});
export const generateHeatMapChart = (options, storeData) => {
return generateHeatMapChartGeneric(options, storeData, problemsDistributionHeatMapAccessor);
};
import type { BaseTrendSliceMetadata } from "@allurereport/core-api";
import type { AllureChartsStoreData, GenericTrendChartData, TrendCalculationResult, TrendChartData, TrendChartOptions, TrendDataAccessor, TrendDataType } from "../charts.js";
import type { PluginContext } from "../plugin.js";
export declare const calculatePercentValues: <T extends TrendDataType>(stats: Record<T, number>, executionId: string, itemType: readonly T[]) => TrendCalculationResult<T>;
export declare const getTrendDataGeneric: <T extends TrendDataType, M extends BaseTrendSliceMetadata>(stats: Record<T, number>, reportName: string, executionOrder: number, itemType: readonly T[], chartOptions: TrendChartOptions) => GenericTrendChartData<T, M>;
export declare const mergeTrendDataGeneric: <T extends TrendDataType, M extends BaseTrendSliceMetadata>(trendData: GenericTrendChartData<T, M>, trendDataPart: GenericTrendChartData<T, M>, itemType: readonly T[]) => GenericTrendChartData<T, M>;
export declare const generateTrendChartGeneric: <T extends TrendDataType>(options: TrendChartOptions, storeData: AllureChartsStoreData, context: PluginContext, dataAccessor: TrendDataAccessor<T>) => GenericTrendChartData<T> | undefined;
export declare const generateTrendChart: (options: TrendChartOptions, storeData: AllureChartsStoreData, context: PluginContext) => TrendChartData | undefined;
import { ChartDataType, ChartMode } from "@allurereport/core-api";
import { DEFAULT_CHART_HISTORY_LIMIT, createEmptySeries, normalizeStatistic } from "../charts.js";
import { severityTrendDataAccessor } from "./accessors/severityTrendAccessor.js";
import { statusTrendDataAccessor } from "./accessors/statusTrendAccessor.js";
export const calculatePercentValues = (stats, executionId, itemType) => {
const points = {};
const series = createEmptySeries(itemType);
const values = Object.values(stats);
const total = values.reduce((sum, value) => sum + value, 0);
if (total === 0) {
return { points, series };
}
itemType.forEach((item) => {
const pointId = `${executionId}-${item}`;
const value = stats[item] ?? 0;
points[pointId] = {
x: executionId,
y: value / total,
};
series[item].push(pointId);
});
return { points, series };
};
const calculateRawValues = (stats, executionId, itemType) => {
const points = {};
const series = createEmptySeries(itemType);
itemType.forEach((item) => {
const pointId = `${executionId}-${item}`;
const value = stats[item] ?? 0;
points[pointId] = {
x: executionId,
y: value,
};
series[item].push(pointId);
});
return { points, series };
};
export const getTrendDataGeneric = (stats, reportName, executionOrder, itemType, chartOptions) => {
const { type, dataType, title, mode = ChartMode.Raw, metadata = {} } = chartOptions;
const { executionIdAccessor, executionNameAccessor } = metadata;
const executionId = executionIdAccessor ? executionIdAccessor(executionOrder) : `execution-${executionOrder}`;
const { points, series } = mode === ChartMode.Percent
? calculatePercentValues(stats, executionId, itemType)
: calculateRawValues(stats, executionId, itemType);
const slices = {};
const pointsAsArray = Object.values(points);
const pointsCount = pointsAsArray.length;
const values = pointsAsArray.map((point) => point.y);
const min = pointsCount ? Math.min(...values) : 0;
const max = pointsCount ? Math.max(...values) : 0;
if (pointsCount > 0) {
const executionName = executionNameAccessor ? executionNameAccessor(executionOrder) : reportName;
slices[executionId] = {
min,
max,
metadata: {
executionId,
executionName,
},
};
}
return {
type,
dataType,
mode,
title,
points,
slices,
series,
min,
max,
};
};
export const mergeTrendDataGeneric = (trendData, trendDataPart, itemType) => {
return {
...trendData,
points: {
...trendData.points,
...trendDataPart.points,
},
slices: {
...trendData.slices,
...trendDataPart.slices,
},
series: Object.entries(trendDataPart.series).reduce((series, [group, pointIds]) => {
if (Array.isArray(pointIds)) {
return {
...series,
[group]: [...(trendData.series?.[group] || []), ...pointIds],
};
}
return series;
}, trendData.series || createEmptySeries(itemType)),
min: Math.min(trendData.min ?? Infinity, trendDataPart.min),
max: Math.max(trendData.max ?? -Infinity, trendDataPart.max),
};
};
export const generateTrendChartGeneric = (options, storeData, context, dataAccessor) => {
const { limit } = options;
const historyLimit = limit && limit > 0 ? Math.max(0, limit - 1) : undefined;
const { historyDataPoints } = storeData;
const currentData = dataAccessor.getCurrentData(storeData);
const limitedHistoryPoints = historyLimit !== undefined ? historyDataPoints.slice(-historyLimit) : historyDataPoints;
const firstOriginalIndex = historyLimit !== undefined ? Math.max(0, historyDataPoints.length - historyLimit) : 0;
const convertedHistoryPoints = limitedHistoryPoints.map((point, index) => {
const originalIndex = firstOriginalIndex + index;
return {
name: point.name,
originalIndex,
statistic: dataAccessor.getHistoricalData(point),
};
});
const allValues = dataAccessor.getAllValues();
const currentTrendData = getTrendDataGeneric(normalizeStatistic(currentData, allValues), context.reportName, historyDataPoints.length + 1, allValues, options);
const historicalTrendData = convertedHistoryPoints.reduce((acc, historyPoint) => {
const trendDataPart = getTrendDataGeneric(normalizeStatistic(historyPoint.statistic, allValues), historyPoint.name, historyPoint.originalIndex + 1, allValues, options);
return mergeTrendDataGeneric(acc, trendDataPart, allValues);
}, {
type: options.type,
dataType: options.dataType,
mode: options.mode,
title: options.title,
points: {},
slices: {},
series: createEmptySeries(allValues),
min: Infinity,
max: -Infinity,
});
return mergeTrendDataGeneric(historicalTrendData, currentTrendData, allValues);
};
export const generateTrendChart = (options, storeData, context) => {
const newOptions = { limit: DEFAULT_CHART_HISTORY_LIMIT, ...options };
const { dataType } = newOptions;
if (dataType === ChartDataType.Status) {
return generateTrendChartGeneric(newOptions, storeData, context, statusTrendDataAccessor);
}
else if (dataType === ChartDataType.Severity) {
return generateTrendChartGeneric(newOptions, storeData, context, severityTrendDataAccessor);
}
};
import type { Statistic } from "@allurereport/core-api";
import type { PieChartData, PieChartOptions } from "../charts.js";
export declare const getPieChartData: (stats: Statistic, chartOptions: PieChartOptions) => PieChartData;
export declare const generatePieChart: (options: PieChartOptions, stores: {
statistic: Statistic;
}) => PieChartData;
import { getPieChartValues } from "@allurereport/core-api";
export const getPieChartData = (stats, chartOptions) => ({
type: chartOptions.type,
title: chartOptions?.title,
...getPieChartValues(stats),
});
export const generatePieChart = (options, stores) => {
const { statistic } = stores;
return getPieChartData(statistic, options);
};
import type { TreeData, TreeGroup, TreeLeaf, TreeMapNode, WithChildren } from "@allurereport/core-api";
import type { AllureChartsStoreData, TreeMapChartData, TreeMapChartOptions, TreeMapDataAccessor } from "../charts.js";
export declare const convertTreeDataToTreeMapNode: <T extends TreeMapNode, L, G>(treeData: TreeData<L, G>, transform: (treeDataNode: TreeLeaf<L> | TreeGroup<G>, isGroup: boolean, parentNode?: TreeGroup<G>) => T, transformRoot?: (root: WithChildren) => T) => T;
export declare const transformTreeMapNode: <T extends TreeMapNode>(tree: T, transform: (node: T) => T) => T;
export declare const generateTreeMapChartGeneric: <T extends TreeMapNode>(options: TreeMapChartOptions, storeData: AllureChartsStoreData, dataAccessor: TreeMapDataAccessor<T>) => TreeMapChartData | undefined;
export declare const generateTreeMapChart: (options: TreeMapChartOptions, storeData: AllureChartsStoreData) => TreeMapChartData | undefined;
import { TreeMapChartType } from "@allurereport/core-api";
import { coverageDiffTreeMapAccessor } from "./accessors/coverageDiffTreeMapAccessor.js";
import { successRateDistributionTreeMapAccessor } from "./accessors/successRateDistributionTreeMapAccessor.js";
export const convertTreeDataToTreeMapNode = (treeData, transform, transformRoot = () => ({
id: "root",
value: undefined,
})) => {
const { root, leavesById, groupsById } = treeData;
const convertNode = (nodeId, parentGroup, isGroup) => {
const node = isGroup ? groupsById[nodeId] : leavesById[nodeId];
if (!node) {
return null;
}
const treeMapNode = transform(node, isGroup, parentGroup);
if (isGroup) {
const group = node;
const children = [];
if (group.groups) {
group.groups.forEach((groupId) => {
const childNode = convertNode(groupId, group, true);
if (childNode) {
children.push(childNode);
}
});
}
if (group.leaves) {
group.leaves.forEach((leafId) => {
const childNode = convertNode(leafId, group, false);
if (childNode) {
children.push(childNode);
}
});
}
if (children.length === 0) {
return null;
}
treeMapNode.children = children;
}
return treeMapNode;
};
const rootChildren = [];
if (root.groups) {
root.groups.forEach((groupId) => {
const childNode = convertNode(groupId, root, true);
if (childNode) {
rootChildren.push(childNode);
}
});
}
if (root.leaves) {
root.leaves.forEach((leafId) => {
const childNode = convertNode(leafId, root, false);
if (childNode) {
rootChildren.push(childNode);
}
});
}
return {
children: rootChildren.length > 0 ? rootChildren : undefined,
...transformRoot(root),
};
};
export const transformTreeMapNode = (tree, transform) => {
const transformedNode = transform(tree);
if (transformedNode.children) {
const transformedChildren = transformedNode.children.map((child) => transformTreeMapNode(child, transform));
return {
...transformedNode,
children: transformedChildren,
};
}
return transformedNode;
};
export const generateTreeMapChartGeneric = (options, storeData, dataAccessor) => ({
type: options.type,
dataType: options.dataType,
title: options.title,
treeMap: dataAccessor.getTreeMap(storeData),
});
export const generateTreeMapChart = (options, storeData) => {
const { dataType } = options;
if (dataType === TreeMapChartType.SuccessRateDistribution) {
return generateTreeMapChartGeneric(options, storeData, successRateDistributionTreeMapAccessor);
}
else if (dataType === TreeMapChartType.CoverageDiff) {
return generateTreeMapChartGeneric(options, storeData, coverageDiffTreeMapAccessor);
}
};