🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@allurereport/core-api

Package Overview
Dependencies
Maintainers
2
Versions
45
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@allurereport/core-api - npm Package Compare versions

Comparing version
3.2.0
to
3.3.1
+106
dist/categories.d.ts
import type { Statistic } from "./aggregate.js";
import type { TestLabel } from "./metadata.js";
import type { TestResult, TestStatus, TestStatusTransition } from "./model.js";
export declare const EMPTY_VALUE = "<Empty>";
export declare const STATUS_ORDER: Record<string, number>;
export declare const SEVERITY_ORDER: Record<string, number>;
export declare const TRANSITION_ORDER: Record<string, number>;
export declare const DEFAULT_ERROR_CATEGORIES: CategoryRule[];
export type TestCategories = {
roots: string[];
nodes: Record<string, CategoryNode>;
};
export type CategoryMatchingData = {
status: TestStatus;
labels: readonly TestLabel[];
message?: string;
trace?: string;
flaky: boolean;
duration?: number;
transition?: TestStatusTransition;
environment?: string;
};
export type ObjectMatcher = {
statuses?: readonly TestStatus[];
labels?: Record<string, string | RegExp>;
message?: string | RegExp;
trace?: string | RegExp;
flaky?: boolean;
transitions?: readonly TestStatusTransition[];
environments?: readonly string[];
};
export type PredicateMatcher = (d: CategoryMatchingData) => boolean;
export type Matcher = ObjectMatcher | PredicateMatcher;
export type CategoryMatcher = Matcher | readonly Matcher[];
export type CategoryGroupBuiltInSelector = "flaky" | "owner" | "severity" | "transition" | "status" | "environment" | "layer";
export type CategoryGroupCustomSelector = {
label: string;
};
export type CategoryGroupSelector = CategoryGroupBuiltInSelector | CategoryGroupCustomSelector;
export type CategoryRule = {
name: string;
matchers?: CategoryMatcher;
groupBy?: readonly CategoryGroupSelector[];
groupByMessage?: boolean;
groupEnvironments?: boolean;
expand?: boolean;
hide?: boolean;
matchedStatuses?: readonly TestStatus[];
messageRegex?: string;
traceRegex?: string;
flaky?: boolean;
};
export type CategoriesStore = {
roots: string[];
nodes: Record<string, CategoryNode>;
};
export interface CategoryDefinition extends Pick<CategoryRule, "name" | "expand" | "hide" | "groupEnvironments"> {
matchers: Matcher[];
groupBy: CategoryGroupSelector[];
groupByMessage: boolean;
index: number;
}
export type CategoryNodeProps = {
nodeId: string;
store: CategoriesStore;
activeNodeId?: string;
depth?: number;
};
export type CategoriesConfig = false | CategoryRule[] | {
rules: CategoryRule[];
};
export type CategoryNodeType = "category" | "group" | "history" | "message" | "tr";
export type CategoryNodeItem = {
id: string;
type: CategoryNodeType;
name: string;
key?: string;
value?: string;
historyId?: string;
retriesCount?: number;
transition?: TestStatusTransition;
tooltips?: Record<string, string>;
statistic?: Statistic;
childrenIds?: string[];
testId?: string;
expand?: boolean;
};
export interface CategoryTr extends Pick<TestResult, "name" | "status" | "duration" | "id" | "flaky" | "transition"> {
}
export type CategoryNode = Partial<CategoryTr> & CategoryNodeItem;
type GroupSortKey = {
missingRank: number;
primaryRank: number;
alphaKey: string;
};
export declare const normalizeCategoriesConfig: (cfg?: CategoriesConfig) => CategoryDefinition[];
export declare const matchCategoryMatcher: (matcher: Matcher, d: CategoryMatchingData) => boolean;
export declare const matchCategory: (categories: CategoryDefinition[], d: CategoryMatchingData) => CategoryDefinition | undefined;
export declare const extractErrorMatchingData: (tr: Pick<TestResult, "status" | "labels" | "error" | "flaky" | "duration" | "transition" | "environment">) => CategoryMatchingData;
export declare const buildEnvironmentSortOrder: (environmentNames: string[], defaultEnvironmentName: string) => Map<string, number>;
export declare const compareNumbers: (left: number, right: number) => 0 | 1 | -1;
export declare const compareStrings: (left: string, right: string) => number;
export declare const isMissingValue: (value: string | undefined) => boolean;
export declare const getGroupSortKey: (groupKey: string | undefined, groupValue: string | undefined, environmentOrderMap?: Map<string, number>) => GroupSortKey;
export declare const compareChildNodes: (leftNodeId: string, rightNodeId: string, nodesById: Record<string, CategoryNode>, environmentOrderMap?: Map<string, number>) => number;
export {};
export const EMPTY_VALUE = "<Empty>";
export const STATUS_ORDER = {
failed: 0,
broken: 1,
passed: 2,
skipped: 3,
unknown: 4,
};
export const SEVERITY_ORDER = {
blocker: 0,
critical: 1,
normal: 2,
minor: 3,
trivial: 4,
};
export const TRANSITION_ORDER = {
regressed: 0,
malfunctioned: 1,
new: 2,
fixed: 3,
};
export const DEFAULT_ERROR_CATEGORIES = [
{
name: "Product errors",
matchers: { statuses: ["failed"] },
},
{
name: "Test errors",
matchers: { statuses: ["broken"] },
},
];
const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
const toRegExp = (v) => (v instanceof RegExp ? v : new RegExp(v));
const isMatcherArray = (value) => Array.isArray(value);
const normalizeMatchers = (rule, index) => {
const compatKeysUsed = rule.matchedStatuses !== undefined ||
rule.messageRegex !== undefined ||
rule.traceRegex !== undefined ||
rule.flaky !== undefined;
if (rule.matchers !== undefined && compatKeysUsed) {
throw new Error(`categories[${index}] mixes canonical keys with compatibility keys`);
}
let matchers = [];
if (rule.matchers !== undefined) {
if (isMatcherArray(rule.matchers)) {
matchers = [...rule.matchers];
}
else {
matchers = [rule.matchers];
}
}
else if (compatKeysUsed) {
const compatMatcher = {};
if (rule.matchedStatuses) {
compatMatcher.statuses = rule.matchedStatuses;
}
if (rule.messageRegex !== undefined) {
compatMatcher.message = rule.messageRegex;
}
if (rule.traceRegex !== undefined) {
compatMatcher.trace = rule.traceRegex;
}
if (rule.flaky !== undefined) {
compatMatcher.flaky = rule.flaky;
}
matchers = [compatMatcher];
}
if (matchers.length === 0) {
throw new Error(`categories[${index}] must define matchers`);
}
for (let i = 0; i < matchers.length; i++) {
const m = matchers[i];
const ok = typeof m === "function" || isPlainObject(m);
if (!ok) {
throw new Error(`categories[${index}].matchers[${i}] must be object|function`);
}
}
return matchers;
};
export const normalizeCategoriesConfig = (cfg) => {
if (cfg === false) {
return [];
}
const rawRules = Array.isArray(cfg) ? cfg : (cfg?.rules ?? []);
const rules = rawRules.length ? rawRules : [];
const normalized = [];
const seen = new Map();
const applyRule = (rule, index) => {
if (!isPlainObject(rule)) {
throw new Error(`categories[${index}] must be an object`);
}
if (typeof rule.name !== "string" || !rule.name.trim()) {
throw new Error(`categories[${index}].name must be non-empty string`);
}
const matchers = normalizeMatchers(rule, index);
const existing = seen.get(rule.name);
if (existing) {
existing.matchers.push(...matchers);
return;
}
const BUILT_IN_GROUP_SELECTORS = new Set([
"flaky",
"owner",
"severity",
"transition",
"status",
"environment",
"layer",
]);
const groupBy = Array.isArray(rule.groupBy) ? [...rule.groupBy] : [];
for (const selector of groupBy) {
const isBuiltIn = typeof selector === "string" && BUILT_IN_GROUP_SELECTORS.has(selector);
const isCustom = isPlainObject(selector) &&
typeof selector.label === "string" &&
selector.label.trim().length > 0;
if (!isBuiltIn && !isCustom) {
throw new Error(`categories[${index}].groupBy contains invalid selector`);
}
}
const norm = {
name: rule.name,
matchers,
groupBy,
groupByMessage: rule.groupByMessage ?? true,
groupEnvironments: rule.groupEnvironments,
expand: rule.expand ?? false,
hide: rule.hide ?? false,
index,
};
seen.set(rule.name, norm);
normalized.push(norm);
};
rules.forEach(applyRule);
DEFAULT_ERROR_CATEGORIES.forEach((rule, index) => applyRule(rule, rules.length + index));
return normalized;
};
const matchObjectMatcher = (m, d) => {
if (m.statuses && !m.statuses.includes(d.status)) {
return false;
}
if (m.flaky !== undefined && m.flaky !== d.flaky) {
return false;
}
if (m.labels) {
for (const [labelName, expected] of Object.entries(m.labels)) {
const re = toRegExp(expected);
const values = d.labels.filter((l) => l.name === labelName).map((l) => l.value ?? "");
if (!values.some((v) => re.test(v))) {
return false;
}
}
}
if (m.message !== undefined) {
const re = toRegExp(m.message);
if (!re.test(d.message ?? "")) {
return false;
}
}
if (m.trace !== undefined) {
const re = toRegExp(m.trace);
if (!re.test(d.trace ?? "")) {
return false;
}
}
if (m.transitions && !m.transitions.includes(d.transition)) {
return false;
}
if (m.environments && !m.environments.includes(d.environment ?? EMPTY_VALUE)) {
return false;
}
return true;
};
export const matchCategoryMatcher = (matcher, d) => {
if (typeof matcher === "function") {
return matcher(d);
}
if (isPlainObject(matcher)) {
return matchObjectMatcher(matcher, d);
}
return false;
};
export const matchCategory = (categories, d) => {
for (const c of categories) {
if (c.matchers.some((m) => matchCategoryMatcher(m, d))) {
return c;
}
}
return undefined;
};
export const extractErrorMatchingData = (tr) => {
const { message, trace } = tr.error ?? {};
const labels = Array.isArray(tr.labels)
? tr.labels.map((l) => ({ name: l.name, value: l.value ?? "" }))
: [];
return {
status: tr.status,
labels,
message,
trace,
flaky: tr.flaky,
duration: tr.duration,
transition: tr.transition,
environment: tr.environment,
};
};
export const buildEnvironmentSortOrder = (environmentNames, defaultEnvironmentName) => {
const orderMap = new Map();
for (let index = 0; index < environmentNames.length; index++) {
orderMap.set(environmentNames[index], index);
}
const missingEnvironmentRank = environmentNames.length;
const defaultEnvironmentRank = environmentNames.length + 1;
orderMap.set(EMPTY_VALUE, missingEnvironmentRank);
orderMap.set(defaultEnvironmentName, defaultEnvironmentRank);
return orderMap;
};
export const compareNumbers = (left, right) => (left < right ? -1 : left > right ? 1 : 0);
export const compareStrings = (left, right) => left.localeCompare(right);
export const isMissingValue = (value) => (value ?? EMPTY_VALUE) === EMPTY_VALUE;
export const getGroupSortKey = (groupKey, groupValue, environmentOrderMap) => {
const normalizedValue = groupValue ?? EMPTY_VALUE;
const missingRank = normalizedValue === EMPTY_VALUE ? 1 : 0;
if (groupKey === "status") {
const primaryRank = STATUS_ORDER[normalizedValue] ?? 999;
return { primaryRank, missingRank, alphaKey: normalizedValue };
}
if (groupKey === "severity") {
const primaryRank = SEVERITY_ORDER[normalizedValue] ?? 999;
return { primaryRank, missingRank, alphaKey: normalizedValue };
}
if (groupKey === "transition") {
const primaryRank = TRANSITION_ORDER[normalizedValue] ?? 999;
return { primaryRank, missingRank, alphaKey: normalizedValue };
}
if (groupKey === "flaky") {
const primaryRank = normalizedValue === "true" ? 0 : 1;
return { primaryRank, missingRank, alphaKey: normalizedValue };
}
if (groupKey === "environment") {
if (environmentOrderMap) {
const primaryRank = environmentOrderMap.get(normalizedValue) ?? 1000;
return { primaryRank, missingRank: 0, alphaKey: normalizedValue };
}
return { primaryRank: 0, missingRank, alphaKey: normalizedValue };
}
return { primaryRank: 0, missingRank, alphaKey: normalizedValue };
};
export const compareChildNodes = (leftNodeId, rightNodeId, nodesById, environmentOrderMap) => {
const leftNode = nodesById[leftNodeId];
const rightNode = nodesById[rightNodeId];
const leftType = leftNode?.type ?? "";
const rightType = rightNode?.type ?? "";
if (leftType === "message" && rightType === "message") {
const leftTotal = leftNode.statistic?.total ?? 0;
const rightTotal = rightNode.statistic?.total ?? 0;
const byCountDescending = compareNumbers(rightTotal, leftTotal);
if (byCountDescending !== 0) {
return byCountDescending;
}
const byNameMessage = compareStrings(leftNode.name ?? "", rightNode.name ?? "");
if (byNameMessage !== 0) {
return byNameMessage;
}
return compareStrings(leftNodeId, rightNodeId);
}
if (leftType === "tr" && rightType === "tr") {
const leftKey = leftNode.key;
const rightKey = rightNode.key;
if (leftKey === "environment" && rightKey === "environment") {
const leftSortKey = getGroupSortKey("environment", leftNode.value, environmentOrderMap);
const rightSortKey = getGroupSortKey("environment", rightNode.value, environmentOrderMap);
const byPrimaryRank = compareNumbers(leftSortKey.primaryRank, rightSortKey.primaryRank);
if (byPrimaryRank !== 0) {
return byPrimaryRank;
}
const byMissingLast = compareNumbers(leftSortKey.missingRank, rightSortKey.missingRank);
if (byMissingLast !== 0) {
return byMissingLast;
}
const byAlpha = compareStrings(leftSortKey.alphaKey, rightSortKey.alphaKey);
if (byAlpha !== 0) {
return byAlpha;
}
return compareStrings(leftNodeId, rightNodeId);
}
}
if (leftType === "group" && rightType === "group") {
const leftGroupKey = leftNode.key ?? "";
const rightGroupKey = rightNode.key ?? "";
const byGroupKey = compareStrings(leftGroupKey, rightGroupKey);
if (byGroupKey !== 0) {
return byGroupKey;
}
const leftSortKey = getGroupSortKey(leftGroupKey, leftNode.value, environmentOrderMap);
const rightSortKey = getGroupSortKey(rightGroupKey, rightNode.value, environmentOrderMap);
const byPrimaryRank = compareNumbers(leftSortKey.primaryRank, rightSortKey.primaryRank);
if (byPrimaryRank !== 0) {
return byPrimaryRank;
}
const byMissingLast = compareNumbers(leftSortKey.missingRank, rightSortKey.missingRank);
if (byMissingLast !== 0) {
return byMissingLast;
}
const byAlpha = compareStrings(leftSortKey.alphaKey, rightSortKey.alphaKey);
if (byAlpha !== 0) {
return byAlpha;
}
return compareStrings(leftNodeId, rightNodeId);
}
const byType = compareStrings(leftType, rightType);
if (byType !== 0) {
return byType;
}
const byName = compareStrings(leftNode?.name ?? "", rightNode?.name ?? "");
if (byName !== 0) {
return byName;
}
return compareStrings(leftNodeId, rightNodeId);
};
export declare const createDictionary: <T>() => Record<string, T>;
export const createDictionary = () => Object.create(null);
+4
-2

@@ -28,4 +28,6 @@ import type { TestLabel } from "./metadata.js";

export interface AllureHistory {
readHistory(branch?: string): Promise<HistoryDataPoint[]>;
appendHistory(history: HistoryDataPoint, branch?: string): Promise<void>;
readHistory(params?: {
branch?: string;
}): Promise<HistoryDataPoint[]>;
appendHistory(history: HistoryDataPoint): Promise<void>;
}

@@ -13,2 +13,3 @@ export type * from "./aggregate.js";

export * from "./static.js";
export * from "./categories.js";
export * from "./utils/step.js";

@@ -25,1 +26,2 @@ export type * from "./utils/tree.js";

export * from "./utils/strings.js";
export * from "./utils/dictionary.js";
export * from "./constants.js";
export * from "./ci.js";
export * from "./static.js";
export * from "./categories.js";
export * from "./utils/step.js";

@@ -14,1 +15,2 @@ export * from "./utils/time.js";

export * from "./utils/strings.js";
export * from "./utils/dictionary.js";
import type { TestLabel } from "../index.js";
export declare const findByLabelName: (labels: TestLabel[], name: string) => string | undefined;
export declare const findLastByLabelName: (labels: TestLabel[], name: string) => string | undefined;
export const findByLabelName = (labels, name) => {
return labels.find((label) => label.name === name)?.value;
};
export const findLastByLabelName = (labels, name) => {
for (let i = labels.length - 1; i >= 0; i -= 1) {
if (labels[i].name === name) {
return labels[i].value;
}
}
return undefined;
};
{
"name": "@allurereport/core-api",
"version": "3.2.0",
"version": "3.3.1",
"description": "Allure Core API",

@@ -5,0 +5,0 @@ "keywords": [