diff --git a/packages/core/src/common/k8s-api/endpoints/namespace.api.ts b/packages/core/src/common/k8s-api/endpoints/namespace.api.ts
index 456cd8bc6c..c27a233d45 100644
--- a/packages/core/src/common/k8s-api/endpoints/namespace.api.ts
+++ b/packages/core/src/common/k8s-api/endpoints/namespace.api.ts
@@ -34,9 +34,19 @@ export class Namespace extends KubeObject<
return this.status?.phase ?? "-";
}
- isSubnamespace(){
+ isSubnamespace() {
return this.getAnnotations().find(annotation => annotation.includes("hnc.x-k8s.io/subnamespace-of"));
}
+
+ isChildOf(parentName: string) {
+ this.getLabels().find(label => label === `${parentName}.tree.hnc.x-k8s.io/depth=1`);
+ }
+
+ isControlledByHNC() {
+ const hierarchicalNamesaceControllerLabel = "hnc.x-k8s.io/included-namespace=true";
+
+ return this.getLabels().find(label => label === hierarchicalNamesaceControllerLabel);
+ }
}
export class NamespaceApi extends KubeApi {
diff --git a/packages/core/src/renderer/components/+namespaces/__snapshots__/namespace-tree-view.test.tsx.snap b/packages/core/src/renderer/components/+namespaces/__snapshots__/namespace-tree-view.test.tsx.snap
index e5d026b5b3..206c68cd27 100644
--- a/packages/core/src/renderer/components/+namespaces/__snapshots__/namespace-tree-view.test.tsx.snap
+++ b/packages/core/src/renderer/components/+namespaces/__snapshots__/namespace-tree-view.test.tsx.snap
@@ -20,7 +20,7 @@ exports[` collapses item by clicking minus button 1`] = `
@@ -47,6 +47,7 @@ exports[` collapses item by clicking minus button 1`] = `
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
>
levels-deep
+
collapses item by clicking minus button 1`] = `
>
@@ -96,24 +97,11 @@ exports[` collapses item by clicking minus button 1`] = `
-
@@ -156,7 +144,7 @@ exports[` collapses item by clicking minus button 1`] = `
>
@@ -190,19 +178,6 @@ exports[` collapses item by clicking minus button 1`] = `
-
@@ -238,7 +213,7 @@ exports[` expands item by clicking plus button 1`] = `
@@ -265,6 +240,7 @@ exports[` expands item by clicking plus button 1`] = `
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
>
levels-deep
+
expands item by clicking plus button 1`] = `
>
@@ -314,24 +290,11 @@ exports[` expands item by clicking plus button 1`] = `
-
@@ -374,7 +337,7 @@ exports[` expands item by clicking plus button 1`] = `
>
@@ -408,19 +371,6 @@ exports[` expands item by clicking plus button 1`] = `
-
@@ -456,7 +406,7 @@ exports[` renders 2 levels deep 1`] = `
@@ -483,6 +433,7 @@ exports[` renders 2 levels deep 1`] = `
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
>
levels-deep
+
renders 2 levels deep 1`] = `
>
@@ -532,24 +483,11 @@ exports[` renders 2 levels deep 1`] = `
-
@@ -592,7 +530,7 @@ exports[` renders 2 levels deep 1`] = `
>
@@ -626,19 +564,6 @@ exports[` renders 2 levels deep 1`] = `
-
@@ -674,7 +599,7 @@ exports[` renders namespace with 2 children namespaces 1`]
@@ -701,6 +626,7 @@ exports[` renders namespace with 2 children namespaces 1`]
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
>
acme-org
+
renders namespace with 2 children namespaces 1`]
>
@@ -750,23 +676,10 @@ exports[` renders namespace with 2 children namespaces 1`]
-
@@ -800,19 +713,6 @@ exports[` renders namespace with 2 children namespaces 1`]
-
@@ -844,7 +744,7 @@ exports[` renders namespace with children namespaces and a
@@ -871,6 +771,7 @@ exports[` renders namespace with children namespaces and a
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
>
org-a
+
renders namespace with children namespaces and a
>
@@ -920,23 +821,10 @@ exports[` renders namespace with children namespaces and a
-
@@ -970,26 +858,13 @@ exports[` renders namespace with children namespaces and a
S
-
@@ -1001,12 +876,6 @@ exports[` renders namespace with children namespaces and a
-
-
@@ -1026,7 +895,7 @@ exports[` renders one namespace without children 1`] = `
>
@@ -1057,6 +926,7 @@ exports[` renders one namespace without children 1`] = `
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
>
single-root
+
diff --git a/packages/core/src/renderer/components/+namespaces/namespace-details.tsx b/packages/core/src/renderer/components/+namespaces/namespace-details.tsx
index b66ab0e772..d38cab94ba 100644
--- a/packages/core/src/renderer/components/+namespaces/namespace-details.tsx
+++ b/packages/core/src/renderer/components/+namespaces/namespace-details.tsx
@@ -27,6 +27,8 @@ import resourceQuotaStoreInjectable from "../+config-resource-quotas/store.injec
import type { Logger } from "../../../common/logger";
import loggerInjectable from "../../../common/logger.injectable";
import { NamespaceTreeView } from "./namespace-tree-view";
+import namespaceStoreInjectable from "./store.injectable";
+import type { NamespaceStore } from "./store";
export interface NamespaceDetailsProps extends KubeObjectDetailsProps {
}
@@ -36,6 +38,7 @@ interface Dependencies {
getDetailsUrl: GetDetailsUrl;
resourceQuotaStore: ResourceQuotaStore;
limitRangeStore: LimitRangeStore;
+ namespaceStore: NamespaceStore;
logger: Logger;
}
@@ -105,7 +108,9 @@ class NonInjectedNamespaceDetails extends React.Component
-
+ {namespace.isControlledByHNC() && (
+
+ )}
);
}
@@ -118,6 +123,7 @@ export const NamespaceDetails = withInjectables ({
Link: ({ children }: { children: React.ReactNode }) => children,
@@ -24,7 +25,7 @@ function createNamespace(name: string, labels?: Record, annotati
name,
resourceVersion: "1",
selfLink: `/api/v1/namespaces/${name}`,
- uid: `${name}-1`,
+ uid: `${name}`,
labels: {
...labels,
},
@@ -123,59 +124,169 @@ describe(" ", () => {
render = renderFor(di);
});
- it("renders null with regular namespace", () => {
- const result = render( );
-
- expect(result.baseElement).toMatchSnapshot();
- });
-
it("renders one namespace without children", () => {
- const result = render( );
+ const tree: NamespaceTree = {
+ id: "single-root",
+ namespace: singleRoot
+ }
+
+ const result = render( );
expect(result.baseElement).toMatchSnapshot();
});
it("renders namespace with 2 children namespaces", () => {
- const result = render( );
+ const tree: NamespaceTree = {
+ id: "acme-org",
+ namespace: acmeGroup,
+ children: [
+ {
+ id: "team-a",
+ namespace: teamA
+ },
+ {
+ id: "team-b",
+ namespace: teamB
+ }
+ ]
+ }
+
+ const result = render( );
expect(result.baseElement).toMatchSnapshot();
});
it("renders namespace with children namespaces and a subnamespace", () => {
- const result = render( );
+ const tree: NamespaceTree = {
+ id: "org-a",
+ namespace: orgA,
+ children: [
+ {
+ id: "team-c",
+ namespace: teamC
+ },
+ {
+ id: "service-1",
+ namespace: service1
+ }
+ ]
+ }
+ const result = render( );
expect(result.baseElement).toMatchSnapshot();
});
it("renders an indicator badge for the subnamespace", () => {
- const result = render( );
+ const tree: NamespaceTree = {
+ id: "org-a",
+ namespace: orgA,
+ children: [
+ {
+ id: "team-c",
+ namespace: teamC
+ },
+ {
+ id: "service-1",
+ namespace: service1
+ }
+ ]
+ }
+ const result = render( );
- expect(result.getByTestId("namespace-details-badge-for-service-1-1")).toBeInTheDocument();
+ expect(result.getByTestId("namespace-details-badge-for-service-1")).toBeInTheDocument();
});
it("does not render an indicator badge for the true namespace", () => {
- const result = render( );
- const trueNamespace = result.getByTestId("namespace-team-c-1");
+ const tree: NamespaceTree = {
+ id: "org-a",
+ namespace: orgA,
+ children: [
+ {
+ id: "team-c",
+ namespace: teamC
+ },
+ {
+ id: "service-1",
+ namespace: service1
+ }
+ ]
+ }
+ const result = render( );
+ const trueNamespace = result.getByTestId("namespace-team-c");
- expect(trueNamespace.querySelector("[data-testid='namespace-details-badge-for-team-c-1']")).toBeNull();
+ expect(trueNamespace.querySelector("[data-testid='namespace-details-badge-for-team-c']")).toBeNull();
});
it("renders 2 levels deep", () => {
- const result = render( );
+ const tree: NamespaceTree = {
+ id: "levels-deep",
+ namespace: levelsDeep,
+ children: [
+ {
+ id: "level-deep-child-a",
+ namespace: levelDeepChildA
+ },
+ {
+ id: "level-deep-child-b",
+ namespace: levelDeepChildB,
+ children: [{
+ id: "level-deep-subchild-a",
+ namespace: levelDeepSubChildA
+ }]
+ }
+ ]
+ }
+ const result = render( );
expect(result.baseElement).toMatchSnapshot();
});
it("expands children items by default", () => {
- const result = render( );
- const deepest = result.getByTestId("namespace-level-deep-child-b-1");
+ const tree: NamespaceTree = {
+ id: "levels-deep",
+ namespace: levelsDeep,
+ children: [
+ {
+ id: "level-deep-child-a",
+ namespace: levelDeepChildA
+ },
+ {
+ id: "level-deep-child-b",
+ namespace: levelDeepChildB,
+ children: [{
+ id: "level-deep-subchild-a",
+ namespace: levelDeepSubChildA
+ }]
+ }
+ ]
+ }
+ const result = render( );
+ const deepest = result.getByTestId("namespace-level-deep-child-b");
expect(deepest).toHaveAttribute("aria-expanded", "true");
});
it("collapses item by clicking minus button", () => {
- const result = render( );
- const levelB = result.getByTestId("namespace-level-deep-child-b-1");
+ const tree: NamespaceTree = {
+ id: "levels-deep",
+ namespace: levelsDeep,
+ children: [
+ {
+ id: "level-deep-child-a",
+ namespace: levelDeepChildA
+ },
+ {
+ id: "level-deep-child-b",
+ namespace: levelDeepChildB,
+ children: [{
+ id: "level-deep-subchild-a",
+ namespace: levelDeepSubChildA
+ }]
+ }
+ ]
+ }
+ const result = render( );
+ const levelB = result.getByTestId("namespace-level-deep-child-b");
const minusButton = levelB.querySelector("[data-testid='minus-square']");
if (minusButton) {
@@ -186,8 +297,26 @@ describe(" ", () => {
});
it("expands item by clicking plus button", () => {
- const result = render( );
- const levelB = result.getByTestId("namespace-level-deep-child-b-1");
+ const tree: NamespaceTree = {
+ id: "levels-deep",
+ namespace: levelsDeep,
+ children: [
+ {
+ id: "level-deep-child-a",
+ namespace: levelDeepChildA
+ },
+ {
+ id: "level-deep-child-b",
+ namespace: levelDeepChildB,
+ children: [{
+ id: "level-deep-subchild-a",
+ namespace: levelDeepSubChildA
+ }]
+ }
+ ]
+ }
+ const result = render( );
+ const levelB = result.getByTestId("namespace-level-deep-child-b");
const minusButton = levelB.querySelector("[data-testid='minus-square']");
if (minusButton) {
diff --git a/packages/core/src/renderer/components/+namespaces/namespace-tree-view.tsx b/packages/core/src/renderer/components/+namespaces/namespace-tree-view.tsx
index f479bb91be..57f6589ba2 100644
--- a/packages/core/src/renderer/components/+namespaces/namespace-tree-view.tsx
+++ b/packages/core/src/renderer/components/+namespaces/namespace-tree-view.tsx
@@ -17,9 +17,10 @@ import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injec
import { SubnamespaceBadge } from "./subnamespace-badge";
import hierarchicalNamespacesInjectable from "./hierarchical-namespaces.injectable";
import { prevDefault } from "../../utils";
+import type { NamespaceTree } from "./store";
interface NamespaceTreeViewProps {
- root: Namespace;
+ tree: NamespaceTree;
}
interface Dependencies {
@@ -27,48 +28,33 @@ interface Dependencies {
getDetailsUrl: GetDetailsUrl;
}
-function isNamespaceControlledByHNC(namespace: Namespace) {
- const hierarchicalNamesaceControllerLabel = "hnc.x-k8s.io/included-namespace=true";
-
- return namespace.getLabels().find(label => label === hierarchicalNamesaceControllerLabel);
-}
-
-function NonInjectableNamespaceTreeView({ root, namespaces, getDetailsUrl }: Dependencies & NamespaceTreeViewProps) {
- const [expandedItems, setExpandedItems] = React.useState(namespaces.map(ns => `namespace-${ns.getId()}`));
+function NonInjectableNamespaceTreeView({ tree, namespaces, getDetailsUrl }: Dependencies & NamespaceTreeViewProps) {
+ const [expandedItems, setExpandedItems] = React.useState(namespaces.map(ns => ns.getId()));
const classes = { group: styles.group, label: styles.label };
- const nodeId = `namespace-${root.getId()}`;
- function renderChildren(parent: Namespace) {
- const children = namespaces.filter(ns =>
- ns.getLabels().find(label => label === `${parent.getName()}.tree.hnc.x-k8s.io/depth=1`),
- );
-
- return children.map(child => {
- const childId = `namespace-${child.getId()}`;
-
- return (
- toggleNode(childId))}
- label={(
- <>
-
- {child.getName()}
-
- {" "}
- {child.isSubnamespace() && (
-
- )}
- >
- )}
+ function renderTree(nodes: NamespaceTree) {
+ return (
+ toggleNode(nodes.id))}
+ label={(
+ <>
+
+ {nodes.namespace.getName()}
+
+ {" "}
+ {nodes.namespace.isSubnamespace() && (
+
+ )}
+ >
+ )}
>
- {renderChildren(child)}
-
- );
- });
+ {Array.isArray(nodes.children) ? nodes.children.map((node) => renderTree(node)) : null}
+
+ )
}
function toggleNode(id: string) {
@@ -79,29 +65,17 @@ function NonInjectableNamespaceTreeView({ root, namespaces, getDetailsUrl }: Dep
}
}
- if (!isNamespaceControlledByHNC(root)) {
- return null;
- }
-
return (
Tree View
}
defaultExpandIcon={
}
defaultEndIcon={(
)}
expanded={expandedItems}
>
-
toggleNode(nodeId))}
- >
- {renderChildren(root)}
-
+ {renderTree(tree)}
);
diff --git a/packages/core/src/renderer/components/+namespaces/store.ts b/packages/core/src/renderer/components/+namespaces/store.ts
index 78f836e807..fee3e3fcd1 100644
--- a/packages/core/src/renderer/components/+namespaces/store.ts
+++ b/packages/core/src/renderer/components/+namespaces/store.ts
@@ -12,6 +12,12 @@ import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import type { NamespaceApi } from "../../../common/k8s-api/endpoints/namespace.api";
import { Namespace } from "../../../common/k8s-api/endpoints/namespace.api";
+export type NamespaceTree = {
+ id: string,
+ namespace: Namespace,
+ children?: NamespaceTree[]
+}
+
interface Dependencies extends KubeObjectStoreDependencies {
readonly storage: StorageLayer;
readonly clusterConfiguredAccessibleNamespaces: IComputedValue;
@@ -202,6 +208,16 @@ export class NamespaceStore extends KubeObjectStore {
this.selectAll();
}
+ getNamespaceTree(root: Namespace): NamespaceTree {
+ const children = this.items.filter(namespace => namespace.isChildOf(root.getName()));
+
+ return {
+ id: root.getId(),
+ namespace: root,
+ children: children.map(this.getNamespaceTree)
+ }
+ }
+
@action
async remove(item: Namespace) {
await super.remove(item);