mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Restructure to monorepo (#6907)
* wip: restructure to monorepo Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * refactor create-release-pr to a package Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * build fixes Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * github workflow fixes Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix typo Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * add webpack-env types to core Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix github workflows Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * refactor/fix integration tests Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * lint fix Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * yarn run dev Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * eslint settings for vscode Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * move templates to right package Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * open-lens build fixes Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * integration test fix Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix nx task dependencies Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * use bash shell for unit tests in test workflow Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix test:unit for windows Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix win-ca webpack error in open-lens Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix win-ca webpack error in open-lens Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix build:app on windows Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * remove ELECTRON_BUILDER_EXTRA_ARGS Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * sync src/ from master Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * remove Makefile from core Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
1 parent
0066ca4ea2
commit
2657df2293
3321 files changed
+28699
-11447
No files matched your search
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { Composite } from "../get-composite/get-composite";
|
||||
import { compositeHasDescendant } from "./composite-has-descendant";
|
||||
import { getCompositeFor } from "../get-composite/get-composite";
|
||||
|
||||
describe("composite-has-descendant, given composite with children and grand children", () => {
|
||||
let composite: Composite<{ id: string; parentId?: string }>;
|
||||
|
||||
beforeEach(() => {
|
||||
const items = [
|
||||
{ id: "some-root-id", parentId: undefined },
|
||||
{ id: "some-child-item", parentId: "some-root-id" },
|
||||
|
||||
{
|
||||
id: "some-grand-child-item",
|
||||
parentId: "some-child-item",
|
||||
},
|
||||
];
|
||||
|
||||
const getComposite = getCompositeFor<{
|
||||
id: string;
|
||||
parentId?: string;
|
||||
}>({
|
||||
rootId: "some-root-id",
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
composite = getComposite(items);
|
||||
});
|
||||
|
||||
it("has a child as descendant", () => {
|
||||
const actual = compositeHasDescendant<typeof composite["value"]>(
|
||||
(referenceComposite) => referenceComposite.value.id === "some-child-item",
|
||||
)(composite);
|
||||
|
||||
expect(actual).toBe(true);
|
||||
});
|
||||
|
||||
it("has a grand child as descendant", () => {
|
||||
const actual = compositeHasDescendant<typeof composite["value"]>(
|
||||
(referenceComposite) =>
|
||||
referenceComposite.value.id === "some-grand-child-item",
|
||||
)(composite);
|
||||
|
||||
expect(actual).toBe(true);
|
||||
});
|
||||
|
||||
it("does not have an unrelated descendant", () => {
|
||||
const actual = compositeHasDescendant<typeof composite["value"]>(
|
||||
(referenceComposite) =>
|
||||
referenceComposite.value.id === "some-unknown-item",
|
||||
)(composite);
|
||||
|
||||
expect(actual).toBe(false);
|
||||
});
|
||||
});
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { Composite } from "../get-composite/get-composite";
|
||||
|
||||
const compositeHasDescendant = <T>(
|
||||
predicate: (referenceComposite: Composite<T>) => boolean,
|
||||
) => {
|
||||
const _compositeHasDescendant = (composite: Composite<T>): boolean =>
|
||||
predicate(composite) ||
|
||||
!!composite.children.find((childComposite) =>
|
||||
_compositeHasDescendant(childComposite),
|
||||
);
|
||||
|
||||
return _compositeHasDescendant;
|
||||
};
|
||||
|
||||
export { compositeHasDescendant };
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { Composite } from "../get-composite/get-composite";
|
||||
import { findComposite } from "./find-composite";
|
||||
import { getCompositeFor } from "../get-composite/get-composite";
|
||||
|
||||
describe("find-composite", () => {
|
||||
let composite: Composite<{ id: string; parentId?: string }>;
|
||||
|
||||
beforeEach(() => {
|
||||
const getComposite = getCompositeFor<{
|
||||
id: string;
|
||||
parentId?: string;
|
||||
}>({
|
||||
rootId: "some-root-id",
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
composite = getComposite([
|
||||
{ id: "some-root-id" },
|
||||
{ id: "some-child-id", parentId: "some-root-id" },
|
||||
{ id: "some-grandchild-id", parentId: "some-child-id" },
|
||||
{ id: "some-other-grandchild-id", parentId: "some-child-id" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("when finding root using path, does so", () => {
|
||||
const actual = findComposite("some-root-id")(composite);
|
||||
|
||||
expect(actual.id).toBe("some-root-id");
|
||||
});
|
||||
|
||||
it("when finding child using path, does so", () => {
|
||||
const actual = findComposite("some-root-id", "some-child-id")(composite);
|
||||
|
||||
expect(actual.id).toBe("some-child-id");
|
||||
});
|
||||
|
||||
it("when finding grandchild using path, does so", () => {
|
||||
const actual = findComposite(
|
||||
"some-root-id",
|
||||
"some-child-id",
|
||||
"some-grandchild-id",
|
||||
)(composite);
|
||||
|
||||
expect(actual.id).toBe("some-grandchild-id");
|
||||
});
|
||||
|
||||
it("when finding with non existing leaf-level path, throws", () => {
|
||||
expect(() => {
|
||||
findComposite(
|
||||
"some-root-id",
|
||||
"some-child-id",
|
||||
"some-non-existing-grandchild-id",
|
||||
)(composite);
|
||||
}).toThrow(`Tried to find 'some-root-id -> some-child-id -> some-non-existing-grandchild-id' from a composite, but found nothing.
|
||||
|
||||
Node 'some-root-id -> some-child-id' had only following children:
|
||||
some-grandchild-id
|
||||
some-other-grandchild-id`);
|
||||
});
|
||||
|
||||
it("when finding with non-existing mid-level path, throws", () => {
|
||||
expect(() => {
|
||||
findComposite(
|
||||
"some-root-id",
|
||||
"some-non-existing-child-id",
|
||||
"some-non-existing-grandchild-id",
|
||||
)(composite);
|
||||
}).toThrow(`Tried to find 'some-root-id -> some-non-existing-child-id -> some-non-existing-grandchild-id' from a composite, but found nothing.
|
||||
|
||||
Node 'some-root-id' had only following children:
|
||||
some-child-id`);
|
||||
});
|
||||
|
||||
it("when finding with non-existing root-level path, throws", () => {
|
||||
expect(() => {
|
||||
findComposite(
|
||||
"some-non-existing-root-id",
|
||||
"some-non-existing-child-id",
|
||||
"some-non-existing-grandchild-id",
|
||||
)(composite);
|
||||
}).toThrow(`Tried to find 'some-non-existing-root-id -> some-non-existing-child-id -> some-non-existing-grandchild-id' from a composite, but found nothing.
|
||||
|
||||
Node 'some-root-id' had only following children:
|
||||
some-child-id`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { Composite } from "../get-composite/get-composite";
|
||||
|
||||
const _findComposite = <T>(currentLeftIds: string[], currentId: string, currentRightIds: string[], composite: Composite<T>): Composite<T> => {
|
||||
const [nextId, ...nextRightIds] = currentRightIds;
|
||||
const nextLeftIds = [...currentLeftIds, currentId];
|
||||
|
||||
if (currentRightIds.length === 0 && composite.id === currentId) {
|
||||
return composite;
|
||||
}
|
||||
|
||||
const foundChildComposite = composite.children.find((child) => child.id === nextId);
|
||||
|
||||
if (foundChildComposite) {
|
||||
return _findComposite(nextLeftIds, nextId, nextRightIds, foundChildComposite);
|
||||
}
|
||||
|
||||
const fullPathString = [...currentLeftIds, currentId, ...currentRightIds].join(" -> ");
|
||||
|
||||
throw new Error(`Tried to find '${fullPathString}' from a composite, but found nothing.
|
||||
|
||||
Node '${[...currentLeftIds, composite.id].join(" -> ")}' had only following children:
|
||||
${composite.children.map((child) => child.id).join("\n")}`);
|
||||
};
|
||||
|
||||
export const findComposite =
|
||||
(...path: string[]) =>
|
||||
<T>(composite: Composite<T>): Composite<T> => {
|
||||
const [currentId, ...rightIds] = path;
|
||||
const leftIds: string[] = [];
|
||||
|
||||
return _findComposite(leftIds, currentId, rightIds, composite);
|
||||
};
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getCompositeNormalization } from "./get-composite-normalization";
|
||||
import { getCompositeFor } from "../get-composite/get-composite";
|
||||
|
||||
|
||||
describe("get-composite-normalization", () => {
|
||||
it("given a composite, flattens it to paths and composites", () => {
|
||||
const someRootItem = {
|
||||
id: "some-root-id",
|
||||
parentId: undefined,
|
||||
};
|
||||
|
||||
const someItem = {
|
||||
id: "some-id",
|
||||
parentId: "some-root-id",
|
||||
};
|
||||
|
||||
const someNestedItem = {
|
||||
id: "some-child-id",
|
||||
parentId: "some-id",
|
||||
};
|
||||
|
||||
const items = [someRootItem, someItem, someNestedItem];
|
||||
|
||||
const getComposite = getCompositeFor<{
|
||||
id: string;
|
||||
parentId?: string;
|
||||
orderNumber?: number;
|
||||
}>({
|
||||
rootId: "some-root-id",
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
const composite = getComposite(items);
|
||||
|
||||
const actual = getCompositeNormalization(composite);
|
||||
|
||||
expect(actual).toEqual([
|
||||
[["some-root-id"], expect.objectContaining({ value: someRootItem })],
|
||||
|
||||
[["some-root-id", "some-id"], expect.objectContaining({ value: someItem })],
|
||||
|
||||
[
|
||||
["some-root-id", "some-id", "some-child-id"],
|
||||
expect.objectContaining({ value: someNestedItem }),
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { Composite } from "../get-composite/get-composite";
|
||||
|
||||
export const getCompositeNormalization = <T>(composite: Composite<T>) => {
|
||||
const _normalizeComposite = <T>(
|
||||
composite: Composite<T>,
|
||||
previousPath: string[] = [],
|
||||
): (readonly [path: string[], composite: Composite<T>])[] => {
|
||||
const currentPath = [...previousPath, composite.id];
|
||||
|
||||
const pathAndCompositeTuple = [currentPath, composite] as const;
|
||||
|
||||
return [
|
||||
pathAndCompositeTuple,
|
||||
|
||||
...composite.children.flatMap((child) =>
|
||||
_normalizeComposite(child, currentPath),
|
||||
),
|
||||
];
|
||||
};
|
||||
|
||||
return _normalizeComposite(composite);
|
||||
};
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getCompositePaths } from "./get-composite-paths";
|
||||
import { sortBy } from "lodash/fp";
|
||||
import { getCompositeFor } from "../get-composite/get-composite";
|
||||
|
||||
describe("get-composite-paths", () => {
|
||||
it("given composite with transformed children, returns paths of transformed children in hierarchical order", () => {
|
||||
const someRootItem = {
|
||||
id: "some-root-id",
|
||||
};
|
||||
|
||||
const someChildItem1 = {
|
||||
id: "some-child-id-1",
|
||||
parentId: "some-root-id",
|
||||
orderNumber: 1,
|
||||
};
|
||||
|
||||
const someChildItem2 = {
|
||||
id: "some-child-id-2",
|
||||
parentId: "some-root-id",
|
||||
orderNumber: 2,
|
||||
};
|
||||
|
||||
const someGrandchildItem1 = {
|
||||
id: "some-grandchild-id-1",
|
||||
parentId: "some-child-id-1",
|
||||
orderNumber: 1,
|
||||
};
|
||||
|
||||
const someGrandchildItem2 = {
|
||||
id: "some-grandchild-id-2",
|
||||
parentId: "some-child-id-1",
|
||||
orderNumber: 2,
|
||||
};
|
||||
|
||||
const items = [
|
||||
someRootItem,
|
||||
// Note: not in order yet.
|
||||
someChildItem2,
|
||||
someChildItem1,
|
||||
someGrandchildItem2,
|
||||
someGrandchildItem1,
|
||||
];
|
||||
|
||||
const getComposite = getCompositeFor<{
|
||||
id: string;
|
||||
parentId?: string;
|
||||
orderNumber?: number;
|
||||
}>({
|
||||
rootId: "some-root-id",
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
transformChildren: children => sortBy(child => child.orderNumber, children),
|
||||
});
|
||||
|
||||
const composite = getComposite(items);
|
||||
|
||||
const actual = getCompositePaths(composite);
|
||||
|
||||
expect(actual).toEqual([
|
||||
["some-root-id"],
|
||||
["some-root-id", "some-child-id-1"],
|
||||
["some-root-id", "some-child-id-1", "some-grandchild-id-1"],
|
||||
["some-root-id", "some-child-id-1", "some-grandchild-id-2"],
|
||||
["some-root-id", "some-child-id-2"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { map } from "lodash/fp";
|
||||
import type { Composite } from "../get-composite/get-composite";
|
||||
import { getCompositeNormalization } from "../get-composite-normalization/get-composite-normalization";
|
||||
|
||||
export const getCompositePaths = (
|
||||
composite: Composite<unknown>,
|
||||
): string[][] => pipeline(composite, getCompositeNormalization, map(([path]) => path));
|
||||
@@ -0,0 +1,363 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { Composite } from "./get-composite";
|
||||
import { getCompositePaths } from "../get-composite-paths/get-composite-paths";
|
||||
import { sortBy } from "lodash/fp";
|
||||
import { getCompositeFor } from "./get-composite";
|
||||
|
||||
interface SomeItem {
|
||||
id: string;
|
||||
parentId?: string;
|
||||
orderNumber?: number;
|
||||
}
|
||||
|
||||
describe("get-composite", () => {
|
||||
it("given items and an explicit root id, creates a composite", () => {
|
||||
const someRootItem = {
|
||||
id: "some-root-id",
|
||||
someProperty: "some-root-content",
|
||||
};
|
||||
|
||||
const someIrrelevantRootItem = {
|
||||
id: "some-irrelevant-root-id",
|
||||
someProperty: "some-other-root-content",
|
||||
};
|
||||
|
||||
const someItem = {
|
||||
id: "some-id",
|
||||
parentId: "some-root-id",
|
||||
someProperty: "some-content",
|
||||
};
|
||||
|
||||
const someNestedItem = {
|
||||
id: "some-nested-id",
|
||||
parentId: "some-id",
|
||||
someProperty: "some-nested-content",
|
||||
};
|
||||
|
||||
const items = [someRootItem, someIrrelevantRootItem, someItem, someNestedItem];
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
rootId: "some-root-id",
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
const composite = getComposite(items);
|
||||
|
||||
expect(composite).toEqual({
|
||||
id: "some-root-id",
|
||||
value: someRootItem,
|
||||
|
||||
children: [
|
||||
{
|
||||
id: "some-id",
|
||||
parentId: "some-root-id",
|
||||
value: someItem,
|
||||
|
||||
children: [
|
||||
{
|
||||
id: "some-nested-id",
|
||||
parentId: "some-id",
|
||||
value: someNestedItem,
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("given items and implicit root, creates a composite", () => {
|
||||
const someRootItem = {
|
||||
id: "some-root-id",
|
||||
someProperty: "some-root-content",
|
||||
// Notice: no "parentId" makes this the implicit root.
|
||||
parentId: undefined,
|
||||
};
|
||||
|
||||
const someItem = {
|
||||
id: "some-id",
|
||||
parentId: "some-root-id",
|
||||
someProperty: "some-content",
|
||||
};
|
||||
|
||||
const someNestedItem = {
|
||||
id: "some-nested-id",
|
||||
parentId: "some-id",
|
||||
someProperty: "some-nested-content",
|
||||
};
|
||||
|
||||
const items = [someRootItem, someItem, someNestedItem];
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
// Notice: no root id
|
||||
// rootId: "some-root-id",
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
const composite = getComposite(items);
|
||||
|
||||
expect(composite).toEqual({
|
||||
id: "some-root-id",
|
||||
value: someRootItem,
|
||||
|
||||
children: [
|
||||
{
|
||||
id: "some-id",
|
||||
parentId: "some-root-id",
|
||||
value: someItem,
|
||||
|
||||
children: [
|
||||
{
|
||||
id: "some-nested-id",
|
||||
parentId: "some-id",
|
||||
value: someNestedItem,
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("given items and an unspecified root id and multiple items without parent as root, throws", () => {
|
||||
const someRootItem = {
|
||||
id: "some-root-id",
|
||||
// Notice: no "parentId" makes this a root.
|
||||
parentId: undefined,
|
||||
};
|
||||
|
||||
const someOtherRootItem = {
|
||||
id: "some-other-root-id",
|
||||
// Notice: no "parentId" makes also this a root.
|
||||
parentId: undefined,
|
||||
};
|
||||
|
||||
const items = [someRootItem, someOtherRootItem];
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
getComposite(items);
|
||||
}).toThrow(
|
||||
'Tried to get a composite, but multiple roots where encountered: "some-root-id", "some-other-root-id"',
|
||||
);
|
||||
});
|
||||
|
||||
it("given non-unique ids, throws", () => {
|
||||
const someItem = {
|
||||
id: "some-id",
|
||||
parentId: "irrelevant",
|
||||
};
|
||||
|
||||
const someOtherItem = {
|
||||
id: "some-id",
|
||||
parentId: "irrelevant",
|
||||
};
|
||||
|
||||
const items = [someItem, someOtherItem];
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
getComposite(items);
|
||||
}).toThrow(
|
||||
'Tried to get a composite but encountered non-unique ids: "some-id"',
|
||||
);
|
||||
});
|
||||
|
||||
it("given items with missing parent ids, when creating composite without handling for unknown parents, throws", () => {
|
||||
const someItem = {
|
||||
id: "some-id",
|
||||
parentId: undefined,
|
||||
};
|
||||
|
||||
const someItemWithMissingParentId = {
|
||||
id: "some-other-id",
|
||||
parentId: "some-missing-id",
|
||||
};
|
||||
|
||||
const items = [someItem, someItemWithMissingParentId];
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
getComposite(items);
|
||||
}).toThrow(
|
||||
`Tried to get a composite but encountered missing parent ids: "some-missing-id".
|
||||
|
||||
Available parent ids are:
|
||||
"some-id",
|
||||
"some-other-id"`,
|
||||
);
|
||||
});
|
||||
|
||||
describe("given items with missing parents, when creating composite with handling for missing parents", () => {
|
||||
let composite: Composite<any>;
|
||||
let handleMissingParentIdMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
const someItem = {
|
||||
id: "some-root-id",
|
||||
};
|
||||
|
||||
const someItemWithMissingParentId = {
|
||||
id: "some-orphan-id",
|
||||
// Note: the item corresponding to this id does not exist,
|
||||
// making this item have a "missing parent".
|
||||
parentId: "some-missing-id",
|
||||
};
|
||||
|
||||
const items = [someItem, someItemWithMissingParentId];
|
||||
|
||||
handleMissingParentIdMock = jest.fn();
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
handleMissingParentIds: handleMissingParentIdMock,
|
||||
});
|
||||
|
||||
composite = getComposite(items);
|
||||
});
|
||||
|
||||
it("creates composite without the orphan item, and without throwing", () => {
|
||||
const paths = getCompositePaths(composite);
|
||||
|
||||
expect(paths).toEqual([["some-root-id"]]);
|
||||
});
|
||||
|
||||
it("handles the missing parent ids", () => {
|
||||
expect(handleMissingParentIdMock).toHaveBeenCalledWith({
|
||||
missingParentIds: ["some-missing-id"],
|
||||
availableParentIds: ["some-root-id", "some-orphan-id"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("given items with same id and parent id, throws", () => {
|
||||
const someItem = {
|
||||
id: "some-id",
|
||||
parentId: "some-id",
|
||||
};
|
||||
|
||||
const someRoot = {
|
||||
id: "root",
|
||||
parentId: undefined,
|
||||
};
|
||||
|
||||
const items = [someItem, someRoot];
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
getComposite(items);
|
||||
}).toThrow(
|
||||
'Tried to get a composite, but found items with self as parent: "some-id"',
|
||||
);
|
||||
});
|
||||
|
||||
it("given undefined ids, throws", () => {
|
||||
const root = {
|
||||
parentId: undefined,
|
||||
id: "some-root",
|
||||
};
|
||||
|
||||
const someItem = {
|
||||
parentId: "some-root",
|
||||
id: undefined,
|
||||
};
|
||||
|
||||
const someOtherItem = {
|
||||
parentId: "some-root",
|
||||
id: undefined,
|
||||
};
|
||||
|
||||
const items = [root, someItem, someOtherItem];
|
||||
|
||||
const getComposite = getCompositeFor<any>({
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
getComposite(items);
|
||||
}).toThrow("Tried to get a composite but encountered 2 undefined ids");
|
||||
});
|
||||
|
||||
it("given transformed children, creates a composite with transformed children", () => {
|
||||
const someRootItem = {
|
||||
id: "some-root-id",
|
||||
orderNumber: 1,
|
||||
};
|
||||
|
||||
const someItem1 = {
|
||||
id: "some-id-1",
|
||||
parentId: "some-root-id",
|
||||
orderNumber: 1,
|
||||
};
|
||||
|
||||
const someItem2 = {
|
||||
id: "some-id-2",
|
||||
parentId: "some-root-id",
|
||||
orderNumber: 2,
|
||||
};
|
||||
|
||||
const someChildItem1 = {
|
||||
id: "some-child-id-1",
|
||||
parentId: "some-id-1",
|
||||
orderNumber: 1,
|
||||
};
|
||||
|
||||
const someChildItem2 = {
|
||||
id: "some-child-id-2",
|
||||
parentId: "some-id-1",
|
||||
orderNumber: 2,
|
||||
};
|
||||
|
||||
const items = [
|
||||
someRootItem,
|
||||
// Note: not in order yet.
|
||||
someItem2,
|
||||
someItem1,
|
||||
someChildItem2,
|
||||
someChildItem1,
|
||||
];
|
||||
|
||||
const getComposite = getCompositeFor<SomeItem>({
|
||||
getId: (x) => x.id,
|
||||
getParentId: (x) => x.parentId,
|
||||
transformChildren: (things) =>
|
||||
sortBy((thing) => thing.orderNumber, things),
|
||||
});
|
||||
|
||||
const composite = getComposite(items);
|
||||
|
||||
const orderedPaths = getCompositePaths(composite);
|
||||
|
||||
expect(orderedPaths).toEqual([
|
||||
["some-root-id"],
|
||||
["some-root-id", "some-id-1"],
|
||||
["some-root-id", "some-id-1", "some-child-id-1"],
|
||||
["some-root-id", "some-id-1", "some-child-id-2"],
|
||||
["some-root-id", "some-id-2"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import {
|
||||
countBy,
|
||||
filter,
|
||||
toPairs,
|
||||
nth,
|
||||
map,
|
||||
uniq,
|
||||
without,
|
||||
compact,
|
||||
identity,
|
||||
} from "lodash/fp";
|
||||
|
||||
export interface Composite<T> {
|
||||
id: string;
|
||||
parentId: string | undefined;
|
||||
value: T;
|
||||
children: Composite<T>[];
|
||||
}
|
||||
|
||||
interface Configuration<T> {
|
||||
rootId?: string;
|
||||
getId: (thing: T) => string;
|
||||
getParentId: (thing: T) => string | undefined;
|
||||
transformChildren?: (things: T[]) => T[];
|
||||
handleMissingParentIds?: (parentIdsForHandling: ParentIdsForHandling) => void;
|
||||
}
|
||||
|
||||
export const getCompositeFor = <T>({
|
||||
rootId = undefined,
|
||||
getId,
|
||||
getParentId,
|
||||
transformChildren = identity,
|
||||
handleMissingParentIds = throwMissingParentIds,
|
||||
}: Configuration<T>) => (source: T[]) => {
|
||||
const undefinedIds = pipeline(
|
||||
source,
|
||||
filter((x) => getId(x) === undefined),
|
||||
);
|
||||
|
||||
if (undefinedIds.length) {
|
||||
throw new Error(
|
||||
`Tried to get a composite but encountered ${undefinedIds.length} undefined ids`,
|
||||
);
|
||||
}
|
||||
|
||||
const selfReferencingIds = pipeline(
|
||||
source,
|
||||
filter((x) => getId(x) === getParentId(x)),
|
||||
map(getId),
|
||||
);
|
||||
|
||||
if (selfReferencingIds.length) {
|
||||
throw new Error(
|
||||
`Tried to get a composite, but found items with self as parent: "${selfReferencingIds.join(
|
||||
'", ',
|
||||
)}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const duplicateIds = pipeline(
|
||||
source,
|
||||
countBy(getId),
|
||||
toPairs,
|
||||
filter(([, count]) => count > 1),
|
||||
map(nth(0)),
|
||||
);
|
||||
|
||||
if (duplicateIds.length) {
|
||||
throw new Error(
|
||||
`Tried to get a composite but encountered non-unique ids: "${duplicateIds
|
||||
.map((x) => String(x))
|
||||
.join('", "')}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const allIds = pipeline(source, map(getId));
|
||||
|
||||
const allParentIds = pipeline(source, map(getParentId), uniq, compact);
|
||||
|
||||
const missingParentIds = without(allIds, allParentIds);
|
||||
|
||||
if (missingParentIds.length) {
|
||||
handleMissingParentIds({ missingParentIds, availableParentIds: allIds });
|
||||
}
|
||||
|
||||
const toComposite = (thing: T): Composite<T> => {
|
||||
const thingId = getId(thing);
|
||||
|
||||
return {
|
||||
id: thingId,
|
||||
parentId: getParentId(thing),
|
||||
value: thing,
|
||||
|
||||
children: pipeline(
|
||||
source,
|
||||
|
||||
filter((childThing) => {
|
||||
const parentId = getParentId(childThing);
|
||||
|
||||
return parentId === thingId;
|
||||
}),
|
||||
|
||||
transformChildren,
|
||||
|
||||
map(toComposite),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const isRootId = rootId
|
||||
? (thing: T) => getId(thing) === rootId
|
||||
: (thing: T) => getParentId(thing) === undefined;
|
||||
|
||||
const roots = source.filter(isRootId);
|
||||
|
||||
if (roots.length > 1) {
|
||||
throw new Error(
|
||||
`Tried to get a composite, but multiple roots where encountered: "${roots
|
||||
.map(getId)
|
||||
.join('", "')}"`,
|
||||
);
|
||||
}
|
||||
|
||||
return toComposite(roots[0]);
|
||||
};
|
||||
|
||||
interface ParentIdsForHandling {
|
||||
missingParentIds: string[];
|
||||
availableParentIds: string[];
|
||||
}
|
||||
|
||||
const throwMissingParentIds = ({
|
||||
missingParentIds,
|
||||
availableParentIds,
|
||||
}: ParentIdsForHandling) => {
|
||||
throw new Error(
|
||||
`Tried to get a composite but encountered missing parent ids: "${missingParentIds.join(
|
||||
'", "',
|
||||
)}".\n\nAvailable parent ids are:\n"${availableParentIds.join('",\n"')}"`,
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export interface ParentOfChildComposite<Id extends string = string> {
|
||||
id: Id;
|
||||
}
|
||||
|
||||
export interface ChildOfParentComposite<ParentId extends string = string> {
|
||||
parentId: ParentId;
|
||||
}
|
||||
|
||||
export type RootComposite<Id extends string = string> =
|
||||
& { parentId: undefined }
|
||||
& ParentOfChildComposite<Id>;
|
||||
Reference in new issue
Block a user