Skip to content

Commit

Permalink
add functions for tree sorting and transforming
Browse files Browse the repository at this point in the history
  • Loading branch information
epszaw committed Dec 19, 2024
1 parent 6e8ede3 commit ea63252
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 28 deletions.
1 change: 1 addition & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/plugin-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"@vitest/runner": "^2.1.8",
"@vitest/snapshot": "^2.1.8",
"allure-vitest": "^3.0.7",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
Expand Down
76 changes: 69 additions & 7 deletions packages/plugin-api/src/utils/tree.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
Comparator,
DefaultTreeGroup,
DefaultTreeLeaf,
TestResult,
Expand Down Expand Up @@ -38,19 +39,18 @@ const createTree = <T, L, G>(
addLeafToGroup: (group: TreeGroup<G>, leaf: TreeLeaf<L>) => void = () => {},
): TreeData<L, G> => {
const groupsByClassifier: Record<string, Record<string, TreeGroup<G>>> = {};

const leavesById: Record<string, TreeLeaf<L>> = {};
const groupsById: Record<string, TreeGroup<G>> = {};

const root: WithChildren = { groups: [], leaves: [] };

for (const item of data) {
const leaf = leafFactory(item);

leavesById[leaf.nodeId] = leaf;

const itemGroups = classifier(item);

let parentGroups = [root];

for (const layer of itemGroups) {
if (layer.length === 0) {
break;
Expand All @@ -66,9 +66,11 @@ const createTree = <T, L, G>(

if (groupsByClassifier[parentId][group] === undefined) {
const newGroup = groupFactory(parentId, group);

groupsByClassifier[parentId][group] = newGroup;
groupsById[newGroup.nodeId] = newGroup;
}

const currentGroup = groupsByClassifier[parentId][group];

addGroup(parentGroup, currentGroup.nodeId);
Expand All @@ -82,14 +84,16 @@ const createTree = <T, L, G>(
parentGroups.forEach((parentGroup) => addLeaf(parentGroup, leaf.nodeId));
}

// TODO: iterate over groupsById to sort leaves by start here?

return {
root,
groupsById,
leavesById,
};
};

const byLabels = (item: TestResult, labelNames: string[]): string[][] => {
export const byLabels = (item: TestResult, labelNames: string[]): string[][] => {
return labelNames.map(
(labelName) =>
item.labels.filter((label) => labelName === label.name).map((label) => label.value ?? "__unknown") ?? [],
Expand All @@ -100,13 +104,11 @@ export const createTreeByLabels = (data: TestResult[], labelNames: string[]) =>
return createTree<TestResult, DefaultTreeLeaf, DefaultTreeGroup>(
data,
(item) => byLabels(item, labelNames),
({ id, name, status, start, stop, duration, flaky }) => ({
({ id, name, status, duration, flaky }) => ({
nodeId: id,
name,
status,
duration,
stop,
start,
flaky,
}),
(parentId, groupClassifier) => ({
Expand All @@ -119,3 +121,63 @@ export const createTreeByLabels = (data: TestResult[], labelNames: string[]) =>
},
);
};

export const sortTree = <L, G>(tree: TreeData<L, G>, comparator: Comparator<TreeLeaf<L>>) => {
const { root, leavesById, groupsById } = tree;
const sortGroupLeaves = (group: TreeGroup<G>) => {
if (!comparator) {
return group;
}

if (group.groups?.length) {
group.groups.forEach((groupId) => {
sortGroupLeaves(groupsById[groupId]);
});
}

if (group.leaves?.length) {
group.leaves = group.leaves.sort((a, b) => {
const leafA = leavesById[a];
const leafB = leavesById[b];

return comparator(leafA, leafB);
});
}

return group;
};

sortGroupLeaves(root as TreeGroup<G>);

return tree;
};

export const transformTree = <L, G>(
tree: TreeData<L, G>,
transformer: (leaf: TreeLeaf<L>, idx: number) => TreeLeaf<L>,
) => {
const { root, leavesById, groupsById } = tree;
const transformGroupLeaves = (group: TreeGroup<G>) => {
if (!transformer) {
return group;
}

if (group.groups?.length) {
group.groups.forEach((groupId) => {
transformGroupLeaves(groupsById[groupId]);
});
}

if (group.leaves?.length) {
group.leaves.forEach((leaf, i) => {
leavesById[leaf] = transformer(leavesById[leaf], i);
});
}

return group;
};

transformGroupLeaves(root as TreeGroup<G>);

return tree;
};
96 changes: 94 additions & 2 deletions packages/plugin-api/test/tree.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { TestResult } from "@allurereport/core-api";
import { TestResult, TreeData, compareBy, nullsLast, ordinal } from "@allurereport/core-api";
import { randomUUID } from "node:crypto";
import { describe, expect, it } from "vitest";
import { createTreeByLabels } from "../src/index.js";
import { createTreeByLabels, sortTree, transformTree } from "../src/index.js";

const itResult = (args: Partial<TestResult>): TestResult => ({
id: randomUUID(),
Expand All @@ -21,6 +21,24 @@ const itResult = (args: Partial<TestResult>): TestResult => ({
},
...args,
});
const sampleTree = {
root: {
leaves: ["l1", "l2"],
groups: ["g1"],
},
leavesById: {
l1: { nodeId: "l1", name: "1", status: "passed", start: 5000 },
l2: { nodeId: "l2", name: "2", status: "passed", start: 3000 },
l3: { nodeId: "l3", name: "3", status: "passed", start: 4000 },
l4: { nodeId: "l4", name: "4", status: "passed", start: 0 },
l5: { nodeId: "l5", name: "5", status: "passed", start: 2000 },
l6: { nodeId: "l6", name: "6", status: "passed", start: 1000 },
},
groupsById: {
g1: { nodeId: "g1", name: "1", groups: ["g2"], leaves: ["l3", "l4"] },
g2: { nodeId: "g2", name: "2", groups: [], leaves: ["l5", "l6"] },
},
};

describe("tree builder", () => {
it("should create empty tree", async () => {
Expand Down Expand Up @@ -187,3 +205,77 @@ describe("tree builder", () => {
expect(treeByLabels.groupsById[storyBUuid]).toHaveProperty("leaves", [tr3.id]);
});
});

describe("tree transforming", () => {
it("sorts leaves if comparator is passed", () => {
const tree = JSON.parse(JSON.stringify(sampleTree));
const res = sortTree(tree as TreeData<any, any>, nullsLast(compareBy("start", ordinal())));

expect(res).toMatchObject({
root: expect.objectContaining({
leaves: ["l2", "l1"],
}),
groupsById: expect.objectContaining({
g1: { nodeId: "g1", name: "1", groups: ["g2"], leaves: ["l4", "l3"] },
g2: { nodeId: "g2", name: "2", groups: [], leaves: ["l6", "l5"] },
}),
});
});
});

describe("tree transformation", () => {
it("transforms leaves if comparator is passed", () => {
const tree = JSON.parse(JSON.stringify(sampleTree));
const res = transformTree(tree as TreeData<any, any>, (leaf, i) => ({
...leaf,
groupOrder: i + 1,
}));

expect(res).toMatchObject({
leavesById: {
l1: {
groupOrder: 1,
name: "1",
nodeId: "l1",
start: 5000,
status: "passed",
},
l2: {
groupOrder: 2,
name: "2",
nodeId: "l2",
start: 3000,
status: "passed",
},
l3: {
groupOrder: 1,
name: "3",
nodeId: "l3",
start: 4000,
status: "passed",
},
l4: {
groupOrder: 2,
name: "4",
nodeId: "l4",
start: 0,
status: "passed",
},
l5: {
groupOrder: 1,
name: "5",
nodeId: "l5",
start: 2000,
status: "passed",
},
l6: {
groupOrder: 2,
name: "6",
nodeId: "l6",
start: 1000,
status: "passed",
},
},
});
});
});
5 changes: 4 additions & 1 deletion packages/plugin-awesome/src/generators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
ordinal,
} from "@allurereport/core-api";
import type { AllureStore, ReportFiles, ResultFile } from "@allurereport/plugin-api";
import { createTreeByLabels } from "@allurereport/plugin-api";
import { createTreeByLabels, sortTree, transformTree } from "@allurereport/plugin-api";
import type {
AllureAwesomeFixtureResult,
AllureAwesomeReportOptions,
Expand Down Expand Up @@ -155,6 +155,9 @@ export const generateTree = async (
const visibleTests = tests.filter((test) => !test.hidden);
const tree = createTreeByLabels(visibleTests, labels);

sortTree(tree, nullsLast(compareBy("start", ordinal())));
transformTree(tree, (leaf, idx) => ({ ...leaf, groupOrder: idx + 1 }));

await writer.writeWidget(`${treeName}.json`, tree);
};

Expand Down
3 changes: 2 additions & 1 deletion packages/web-awesome/src/components/app/Tree/Tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ const Tree: FunctionComponent<TreeProps> = ({ tree, statusFilter, root, name, st
const toggleTree = () => {
setIsOpen(!isOpened);
};
const emptyTree = !tree?.groups?.length && !tree?.leaves?.length;

if (!tree?.groups?.length && !tree?.leaves?.length) {
if (emptyTree) {
return (
<div className={styles["tree-list"]}>
<div className={styles["tree-empty-results"]}>
Expand Down
32 changes: 15 additions & 17 deletions packages/web-awesome/src/utils/treeFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,7 @@ export const filterLeaves = (
// const newMatched = !filterOptions?.filter?.new || leaf.new;

return [queryMatched, statusMatched, flakyMatched, retryMatched].every(Boolean);
})
.sort(nullsLast(compareBy("start", ordinal())))
.map(
(leaf, i) =>
({
...leaf,
groupOrder: i + 1,
}) as AllureAwesomeTreeLeaf,
);
});

if (!filterOptions) {
return filteredLeaves;
Expand Down Expand Up @@ -79,13 +71,19 @@ export const fillTree = (payload: {
return {
...group,
leaves: filterLeaves(group.leaves, leavesById, filterOptions),
groups: group?.groups?.map((groupId) =>
fillTree({
group: groupsById[groupId],
groupsById,
leavesById,
filterOptions,
}),
),
groups: group?.groups
?.filter((groupId) => {
const subGroup = groupsById[groupId];

return subGroup?.leaves?.length || subGroup?.groups?.length;
})
?.map((groupId) =>
fillTree({
group: groupsById[groupId],
groupsById,
leavesById,
filterOptions,
}),
),
};
};
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ __metadata:
"@typescript-eslint/eslint-plugin": "npm:^8.0.0"
"@typescript-eslint/parser": "npm:^8.0.0"
"@vitest/runner": "npm:^2.1.8"
"@vitest/snapshot": "npm:^2.1.8"
allure-vitest: "npm:^3.0.7"
eslint: "npm:^8.57.0"
eslint-config-prettier: "npm:^9.1.0"
Expand Down

0 comments on commit ea63252

Please sign in to comment.