@allurereport/plugin-api
Advanced tools
+0
-7
@@ -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"; |
+0
-7
@@ -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 @@ } |
+4
-1
@@ -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" | ||
| } |
+1
-0
@@ -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 = {})); |
+2
-2
| { | ||
| "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": { |
-130
| 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); | ||
| } | ||
| }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
24584
-66.74%20
-61.54%512
-70.09%1
Infinity%+ Added
- Removed