1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Refactor: move tree build logic to store

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
Alex Andreev 2023-02-08 17:23:36 +03:00
parent 0cf649de69
commit 690bef9597
6 changed files with 240 additions and 235 deletions

View File

@ -34,9 +34,19 @@ export class Namespace extends KubeObject<
return this.status?.phase ?? "-"; return this.status?.phase ?? "-";
} }
isSubnamespace(){ isSubnamespace() {
return this.getAnnotations().find(annotation => annotation.includes("hnc.x-k8s.io/subnamespace-of")); 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<Namespace> { export class NamespaceApi extends KubeApi<Namespace> {

View File

@ -20,7 +20,7 @@ exports[`<NamespaceTreeView /> collapses item by clicking minus button 1`] = `
<li <li
aria-expanded="true" aria-expanded="true"
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-levels-deep-1" data-testid="namespace-levels-deep"
role="treeitem" role="treeitem"
tabindex="0" tabindex="0"
> >
@ -47,6 +47,7 @@ exports[`<NamespaceTreeView /> collapses item by clicking minus button 1`] = `
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1" class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
> >
levels-deep levels-deep
</div> </div>
</div> </div>
<ul <ul
@ -62,7 +63,7 @@ exports[`<NamespaceTreeView /> collapses item by clicking minus button 1`] = `
> >
<li <li
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-level-deep-child-a-1" data-testid="namespace-level-deep-child-a"
role="treeitem" role="treeitem"
tabindex="-1" tabindex="-1"
> >
@ -96,24 +97,11 @@ exports[`<NamespaceTreeView /> collapses item by clicking minus button 1`] = `
</div> </div>
</div> </div>
<ul
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
role="group"
style="min-height: 0px;"
>
<div
class="MuiCollapse-wrapper"
>
<div
class="MuiCollapse-wrapperInner"
/>
</div>
</ul>
</li> </li>
<li <li
aria-expanded="false" aria-expanded="false"
class="MuiTreeItem-root" class="MuiTreeItem-root"
data-testid="namespace-level-deep-child-b-1" data-testid="namespace-level-deep-child-b"
role="treeitem" role="treeitem"
tabindex="-1" tabindex="-1"
> >
@ -156,7 +144,7 @@ exports[`<NamespaceTreeView /> collapses item by clicking minus button 1`] = `
> >
<li <li
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-level-deep-subchild-a-1" data-testid="namespace-level-deep-subchild-a"
role="treeitem" role="treeitem"
tabindex="-1" tabindex="-1"
> >
@ -190,19 +178,6 @@ exports[`<NamespaceTreeView /> collapses item by clicking minus button 1`] = `
</div> </div>
</div> </div>
<ul
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
role="group"
style="min-height: 0px;"
>
<div
class="MuiCollapse-wrapper"
>
<div
class="MuiCollapse-wrapperInner"
/>
</div>
</ul>
</li> </li>
</div> </div>
</div> </div>
@ -238,7 +213,7 @@ exports[`<NamespaceTreeView /> expands item by clicking plus button 1`] = `
<li <li
aria-expanded="true" aria-expanded="true"
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-levels-deep-1" data-testid="namespace-levels-deep"
role="treeitem" role="treeitem"
tabindex="0" tabindex="0"
> >
@ -265,6 +240,7 @@ exports[`<NamespaceTreeView /> expands item by clicking plus button 1`] = `
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1" class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
> >
levels-deep levels-deep
</div> </div>
</div> </div>
<ul <ul
@ -280,7 +256,7 @@ exports[`<NamespaceTreeView /> expands item by clicking plus button 1`] = `
> >
<li <li
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-level-deep-child-a-1" data-testid="namespace-level-deep-child-a"
role="treeitem" role="treeitem"
tabindex="-1" tabindex="-1"
> >
@ -314,24 +290,11 @@ exports[`<NamespaceTreeView /> expands item by clicking plus button 1`] = `
</div> </div>
</div> </div>
<ul
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
role="group"
style="min-height: 0px;"
>
<div
class="MuiCollapse-wrapper"
>
<div
class="MuiCollapse-wrapperInner"
/>
</div>
</ul>
</li> </li>
<li <li
aria-expanded="true" aria-expanded="true"
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-level-deep-child-b-1" data-testid="namespace-level-deep-child-b"
role="treeitem" role="treeitem"
tabindex="-1" tabindex="-1"
> >
@ -374,7 +337,7 @@ exports[`<NamespaceTreeView /> expands item by clicking plus button 1`] = `
> >
<li <li
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-level-deep-subchild-a-1" data-testid="namespace-level-deep-subchild-a"
role="treeitem" role="treeitem"
tabindex="-1" tabindex="-1"
> >
@ -408,19 +371,6 @@ exports[`<NamespaceTreeView /> expands item by clicking plus button 1`] = `
</div> </div>
</div> </div>
<ul
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
role="group"
style="min-height: 0px;"
>
<div
class="MuiCollapse-wrapper"
>
<div
class="MuiCollapse-wrapperInner"
/>
</div>
</ul>
</li> </li>
</div> </div>
</div> </div>
@ -456,7 +406,7 @@ exports[`<NamespaceTreeView /> renders 2 levels deep 1`] = `
<li <li
aria-expanded="true" aria-expanded="true"
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-levels-deep-1" data-testid="namespace-levels-deep"
role="treeitem" role="treeitem"
tabindex="0" tabindex="0"
> >
@ -483,6 +433,7 @@ exports[`<NamespaceTreeView /> renders 2 levels deep 1`] = `
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1" class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
> >
levels-deep levels-deep
</div> </div>
</div> </div>
<ul <ul
@ -498,7 +449,7 @@ exports[`<NamespaceTreeView /> renders 2 levels deep 1`] = `
> >
<li <li
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-level-deep-child-a-1" data-testid="namespace-level-deep-child-a"
role="treeitem" role="treeitem"
tabindex="-1" tabindex="-1"
> >
@ -532,24 +483,11 @@ exports[`<NamespaceTreeView /> renders 2 levels deep 1`] = `
</div> </div>
</div> </div>
<ul
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
role="group"
style="min-height: 0px;"
>
<div
class="MuiCollapse-wrapper"
>
<div
class="MuiCollapse-wrapperInner"
/>
</div>
</ul>
</li> </li>
<li <li
aria-expanded="true" aria-expanded="true"
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-level-deep-child-b-1" data-testid="namespace-level-deep-child-b"
role="treeitem" role="treeitem"
tabindex="-1" tabindex="-1"
> >
@ -592,7 +530,7 @@ exports[`<NamespaceTreeView /> renders 2 levels deep 1`] = `
> >
<li <li
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-level-deep-subchild-a-1" data-testid="namespace-level-deep-subchild-a"
role="treeitem" role="treeitem"
tabindex="-1" tabindex="-1"
> >
@ -626,19 +564,6 @@ exports[`<NamespaceTreeView /> renders 2 levels deep 1`] = `
</div> </div>
</div> </div>
<ul
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
role="group"
style="min-height: 0px;"
>
<div
class="MuiCollapse-wrapper"
>
<div
class="MuiCollapse-wrapperInner"
/>
</div>
</ul>
</li> </li>
</div> </div>
</div> </div>
@ -674,7 +599,7 @@ exports[`<NamespaceTreeView /> renders namespace with 2 children namespaces 1`]
<li <li
aria-expanded="true" aria-expanded="true"
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-acme-org-1" data-testid="namespace-acme-org"
role="treeitem" role="treeitem"
tabindex="0" tabindex="0"
> >
@ -701,6 +626,7 @@ exports[`<NamespaceTreeView /> renders namespace with 2 children namespaces 1`]
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1" class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
> >
acme-org acme-org
</div> </div>
</div> </div>
<ul <ul
@ -716,7 +642,7 @@ exports[`<NamespaceTreeView /> renders namespace with 2 children namespaces 1`]
> >
<li <li
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-team-a-1" data-testid="namespace-team-a"
role="treeitem" role="treeitem"
tabindex="-1" tabindex="-1"
> >
@ -750,23 +676,10 @@ exports[`<NamespaceTreeView /> renders namespace with 2 children namespaces 1`]
</div> </div>
</div> </div>
<ul
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
role="group"
style="min-height: 0px;"
>
<div
class="MuiCollapse-wrapper"
>
<div
class="MuiCollapse-wrapperInner"
/>
</div>
</ul>
</li> </li>
<li <li
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-team-b-1" data-testid="namespace-team-b"
role="treeitem" role="treeitem"
tabindex="-1" tabindex="-1"
> >
@ -800,19 +713,6 @@ exports[`<NamespaceTreeView /> renders namespace with 2 children namespaces 1`]
</div> </div>
</div> </div>
<ul
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
role="group"
style="min-height: 0px;"
>
<div
class="MuiCollapse-wrapper"
>
<div
class="MuiCollapse-wrapperInner"
/>
</div>
</ul>
</li> </li>
</div> </div>
</div> </div>
@ -844,7 +744,7 @@ exports[`<NamespaceTreeView /> renders namespace with children namespaces and a
<li <li
aria-expanded="true" aria-expanded="true"
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-org-a-1" data-testid="namespace-org-a"
role="treeitem" role="treeitem"
tabindex="0" tabindex="0"
> >
@ -871,6 +771,7 @@ exports[`<NamespaceTreeView /> renders namespace with children namespaces and a
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1" class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
> >
org-a org-a
</div> </div>
</div> </div>
<ul <ul
@ -886,7 +787,7 @@ exports[`<NamespaceTreeView /> renders namespace with children namespaces and a
> >
<li <li
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-team-c-1" data-testid="namespace-team-c"
role="treeitem" role="treeitem"
tabindex="-1" tabindex="-1"
> >
@ -920,23 +821,10 @@ exports[`<NamespaceTreeView /> renders namespace with children namespaces and a
</div> </div>
</div> </div>
<ul
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
role="group"
style="min-height: 0px;"
>
<div
class="MuiCollapse-wrapper"
>
<div
class="MuiCollapse-wrapperInner"
/>
</div>
</ul>
</li> </li>
<li <li
class="MuiTreeItem-root Mui-expanded" class="MuiTreeItem-root Mui-expanded"
data-testid="namespace-service-1-1" data-testid="namespace-service-1"
role="treeitem" role="treeitem"
tabindex="-1" tabindex="-1"
> >
@ -970,26 +858,13 @@ exports[`<NamespaceTreeView /> renders namespace with children namespaces and a
<span <span
class="subnamespaceBadge" class="subnamespaceBadge"
data-testid="namespace-details-badge-for-service-1-1" data-testid="namespace-details-badge-for-service-1"
id="namespace-details-badge-for-service-1-1" id="namespace-details-badge-for-service-1"
> >
S S
</span> </span>
</div> </div>
</div> </div>
<ul
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
role="group"
style="min-height: 0px;"
>
<div
class="MuiCollapse-wrapper"
>
<div
class="MuiCollapse-wrapperInner"
/>
</div>
</ul>
</li> </li>
</div> </div>
</div> </div>
@ -1001,12 +876,6 @@ exports[`<NamespaceTreeView /> renders namespace with children namespaces and a
</body> </body>
`; `;
exports[`<NamespaceTreeView /> renders null with regular namespace 1`] = `
<body>
<div />
</body>
`;
exports[`<NamespaceTreeView /> renders one namespace without children 1`] = ` exports[`<NamespaceTreeView /> renders one namespace without children 1`] = `
<body> <body>
<div> <div>
@ -1026,7 +895,7 @@ exports[`<NamespaceTreeView /> renders one namespace without children 1`] = `
> >
<li <li
class="MuiTreeItem-root" class="MuiTreeItem-root"
data-testid="namespace-single-root-1" data-testid="namespace-single-root"
role="treeitem" role="treeitem"
tabindex="0" tabindex="0"
> >
@ -1057,6 +926,7 @@ exports[`<NamespaceTreeView /> renders one namespace without children 1`] = `
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1" class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
> >
single-root single-root
</div> </div>
</div> </div>
</li> </li>

View File

@ -27,6 +27,8 @@ import resourceQuotaStoreInjectable from "../+config-resource-quotas/store.injec
import type { Logger } from "../../../common/logger"; import type { Logger } from "../../../common/logger";
import loggerInjectable from "../../../common/logger.injectable"; import loggerInjectable from "../../../common/logger.injectable";
import { NamespaceTreeView } from "./namespace-tree-view"; import { NamespaceTreeView } from "./namespace-tree-view";
import namespaceStoreInjectable from "./store.injectable";
import type { NamespaceStore } from "./store";
export interface NamespaceDetailsProps extends KubeObjectDetailsProps<Namespace> { export interface NamespaceDetailsProps extends KubeObjectDetailsProps<Namespace> {
} }
@ -36,6 +38,7 @@ interface Dependencies {
getDetailsUrl: GetDetailsUrl; getDetailsUrl: GetDetailsUrl;
resourceQuotaStore: ResourceQuotaStore; resourceQuotaStore: ResourceQuotaStore;
limitRangeStore: LimitRangeStore; limitRangeStore: LimitRangeStore;
namespaceStore: NamespaceStore;
logger: Logger; logger: Logger;
} }
@ -105,7 +108,9 @@ class NonInjectedNamespaceDetails extends React.Component<NamespaceDetailsProps
))} ))}
</DrawerItem> </DrawerItem>
<NamespaceTreeView root={namespace}/> {namespace.isControlledByHNC() && (
<NamespaceTreeView tree={this.props.namespaceStore.getNamespaceTree(namespace)}/>
)}
</div> </div>
); );
} }
@ -118,6 +123,7 @@ export const NamespaceDetails = withInjectables<Dependencies, NamespaceDetailsPr
getDetailsUrl: di.inject(getDetailsUrlInjectable), getDetailsUrl: di.inject(getDetailsUrlInjectable),
limitRangeStore: di.inject(limitRangeStoreInjectable), limitRangeStore: di.inject(limitRangeStoreInjectable),
resourceQuotaStore: di.inject(resourceQuotaStoreInjectable), resourceQuotaStore: di.inject(resourceQuotaStoreInjectable),
namespaceStore: di.inject(namespaceStoreInjectable),
logger: di.inject(loggerInjectable), logger: di.inject(loggerInjectable),
}), }),
}); });

View File

@ -11,6 +11,7 @@ import type { DiRender } from "../test-utils/renderFor";
import { renderFor } from "../test-utils/renderFor"; import { renderFor } from "../test-utils/renderFor";
import hierarchicalNamespacesInjectable from "./hierarchical-namespaces.injectable"; import hierarchicalNamespacesInjectable from "./hierarchical-namespaces.injectable";
import { NamespaceTreeView } from "./namespace-tree-view"; import { NamespaceTreeView } from "./namespace-tree-view";
import type { NamespaceTree } from "./store";
jest.mock("react-router-dom", () => ({ jest.mock("react-router-dom", () => ({
Link: ({ children }: { children: React.ReactNode }) => children, Link: ({ children }: { children: React.ReactNode }) => children,
@ -24,7 +25,7 @@ function createNamespace(name: string, labels?: Record<string, string>, annotati
name, name,
resourceVersion: "1", resourceVersion: "1",
selfLink: `/api/v1/namespaces/${name}`, selfLink: `/api/v1/namespaces/${name}`,
uid: `${name}-1`, uid: `${name}`,
labels: { labels: {
...labels, ...labels,
}, },
@ -123,59 +124,169 @@ describe("<NamespaceTreeView />", () => {
render = renderFor(di); render = renderFor(di);
}); });
it("renders null with regular namespace", () => {
const result = render(<NamespaceTreeView root={createNamespace("tree-1")} />);
expect(result.baseElement).toMatchSnapshot();
});
it("renders one namespace without children", () => { it("renders one namespace without children", () => {
const result = render(<NamespaceTreeView root={singleRoot} />); const tree: NamespaceTree = {
id: "single-root",
namespace: singleRoot
}
const result = render(<NamespaceTreeView tree={tree} />);
expect(result.baseElement).toMatchSnapshot(); expect(result.baseElement).toMatchSnapshot();
}); });
it("renders namespace with 2 children namespaces", () => { it("renders namespace with 2 children namespaces", () => {
const result = render(<NamespaceTreeView root={acmeGroup} />); const tree: NamespaceTree = {
id: "acme-org",
namespace: acmeGroup,
children: [
{
id: "team-a",
namespace: teamA
},
{
id: "team-b",
namespace: teamB
}
]
}
const result = render(<NamespaceTreeView tree={tree} />);
expect(result.baseElement).toMatchSnapshot(); expect(result.baseElement).toMatchSnapshot();
}); });
it("renders namespace with children namespaces and a subnamespace", () => { it("renders namespace with children namespaces and a subnamespace", () => {
const result = render(<NamespaceTreeView root={orgA} />); const tree: NamespaceTree = {
id: "org-a",
namespace: orgA,
children: [
{
id: "team-c",
namespace: teamC
},
{
id: "service-1",
namespace: service1
}
]
}
const result = render(<NamespaceTreeView tree={tree} />);
expect(result.baseElement).toMatchSnapshot(); expect(result.baseElement).toMatchSnapshot();
}); });
it("renders an indicator badge for the subnamespace", () => { it("renders an indicator badge for the subnamespace", () => {
const result = render(<NamespaceTreeView root={orgA} />); const tree: NamespaceTree = {
id: "org-a",
namespace: orgA,
children: [
{
id: "team-c",
namespace: teamC
},
{
id: "service-1",
namespace: service1
}
]
}
const result = render(<NamespaceTreeView tree={tree} />);
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", () => { it("does not render an indicator badge for the true namespace", () => {
const result = render(<NamespaceTreeView root={orgA} />); const tree: NamespaceTree = {
const trueNamespace = result.getByTestId("namespace-team-c-1"); id: "org-a",
namespace: orgA,
children: [
{
id: "team-c",
namespace: teamC
},
{
id: "service-1",
namespace: service1
}
]
}
const result = render(<NamespaceTreeView tree={tree} />);
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", () => { it("renders 2 levels deep", () => {
const result = render(<NamespaceTreeView root={levelsDeep} />); 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(<NamespaceTreeView tree={tree} />);
expect(result.baseElement).toMatchSnapshot(); expect(result.baseElement).toMatchSnapshot();
}); });
it("expands children items by default", () => { it("expands children items by default", () => {
const result = render(<NamespaceTreeView root={levelsDeep} />); const tree: NamespaceTree = {
const deepest = result.getByTestId("namespace-level-deep-child-b-1"); 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(<NamespaceTreeView tree={tree} />);
const deepest = result.getByTestId("namespace-level-deep-child-b");
expect(deepest).toHaveAttribute("aria-expanded", "true"); expect(deepest).toHaveAttribute("aria-expanded", "true");
}); });
it("collapses item by clicking minus button", () => { it("collapses item by clicking minus button", () => {
const result = render(<NamespaceTreeView root={levelsDeep} />); const tree: NamespaceTree = {
const levelB = result.getByTestId("namespace-level-deep-child-b-1"); 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(<NamespaceTreeView tree={tree} />);
const levelB = result.getByTestId("namespace-level-deep-child-b");
const minusButton = levelB.querySelector("[data-testid='minus-square']"); const minusButton = levelB.querySelector("[data-testid='minus-square']");
if (minusButton) { if (minusButton) {
@ -186,8 +297,26 @@ describe("<NamespaceTreeView />", () => {
}); });
it("expands item by clicking plus button", () => { it("expands item by clicking plus button", () => {
const result = render(<NamespaceTreeView root={levelsDeep} />); const tree: NamespaceTree = {
const levelB = result.getByTestId("namespace-level-deep-child-b-1"); 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(<NamespaceTreeView tree={tree} />);
const levelB = result.getByTestId("namespace-level-deep-child-b");
const minusButton = levelB.querySelector("[data-testid='minus-square']"); const minusButton = levelB.querySelector("[data-testid='minus-square']");
if (minusButton) { if (minusButton) {

View File

@ -17,9 +17,10 @@ import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injec
import { SubnamespaceBadge } from "./subnamespace-badge"; import { SubnamespaceBadge } from "./subnamespace-badge";
import hierarchicalNamespacesInjectable from "./hierarchical-namespaces.injectable"; import hierarchicalNamespacesInjectable from "./hierarchical-namespaces.injectable";
import { prevDefault } from "../../utils"; import { prevDefault } from "../../utils";
import type { NamespaceTree } from "./store";
interface NamespaceTreeViewProps { interface NamespaceTreeViewProps {
root: Namespace; tree: NamespaceTree;
} }
interface Dependencies { interface Dependencies {
@ -27,48 +28,33 @@ interface Dependencies {
getDetailsUrl: GetDetailsUrl; getDetailsUrl: GetDetailsUrl;
} }
function isNamespaceControlledByHNC(namespace: Namespace) { function NonInjectableNamespaceTreeView({ tree, namespaces, getDetailsUrl }: Dependencies & NamespaceTreeViewProps) {
const hierarchicalNamesaceControllerLabel = "hnc.x-k8s.io/included-namespace=true"; const [expandedItems, setExpandedItems] = React.useState<string[]>(namespaces.map(ns => ns.getId()));
return namespace.getLabels().find(label => label === hierarchicalNamesaceControllerLabel);
}
function NonInjectableNamespaceTreeView({ root, namespaces, getDetailsUrl }: Dependencies & NamespaceTreeViewProps) {
const [expandedItems, setExpandedItems] = React.useState<string[]>(namespaces.map(ns => `namespace-${ns.getId()}`));
const classes = { group: styles.group, label: styles.label }; const classes = { group: styles.group, label: styles.label };
const nodeId = `namespace-${root.getId()}`;
function renderChildren(parent: Namespace) { function renderTree(nodes: NamespaceTree) {
const children = namespaces.filter(ns => return (
ns.getLabels().find(label => label === `${parent.getName()}.tree.hnc.x-k8s.io/depth=1`), <TreeItem
); key={nodes.id}
nodeId={nodes.id}
return children.map(child => { data-testid={`namespace-${nodes.id}`}
const childId = `namespace-${child.getId()}`; classes={classes}
onIconClick={prevDefault(() => toggleNode(nodes.id))}
return ( label={(
<TreeItem <>
key={childId} <Link key={nodes.namespace.getId()} to={getDetailsUrl(nodes.namespace.selfLink)}>
nodeId={childId} {nodes.namespace.getName()}
data-testid={childId} </Link>
classes={classes} {" "}
onIconClick={prevDefault(() => toggleNode(childId))} {nodes.namespace.isSubnamespace() && (
label={( <SubnamespaceBadge id={`namespace-details-badge-for-${nodes.namespace.getId()}`} />
<> )}
<Link key={child.getId()} to={getDetailsUrl(child.selfLink)}> </>
{child.getName()} )}
</Link>
{" "}
{child.isSubnamespace() && (
<SubnamespaceBadge id={`namespace-details-badge-for-${child.getId()}`} />
)}
</>
)}
> >
{renderChildren(child)} {Array.isArray(nodes.children) ? nodes.children.map((node) => renderTree(node)) : null}
</TreeItem> </TreeItem>
); )
});
} }
function toggleNode(id: string) { function toggleNode(id: string) {
@ -79,29 +65,17 @@ function NonInjectableNamespaceTreeView({ root, namespaces, getDetailsUrl }: Dep
} }
} }
if (!isNamespaceControlledByHNC(root)) {
return null;
}
return ( return (
<div data-testid="namespace-tree-view" className={styles.TreeView}> <div data-testid="namespace-tree-view" className={styles.TreeView}>
<DrawerTitle>Tree View</DrawerTitle> <DrawerTitle>Tree View</DrawerTitle>
<TreeView <TreeView
defaultExpanded={[nodeId]} defaultExpanded={[tree.id]}
defaultCollapseIcon={<MinusSquareIcon />} defaultCollapseIcon={<MinusSquareIcon />}
defaultExpandIcon={<PlusSquareIcon />} defaultExpandIcon={<PlusSquareIcon />}
defaultEndIcon={(<div style={{ opacity: 0.3 }}><MinusSquareIcon /></div>)} defaultEndIcon={(<div style={{ opacity: 0.3 }}><MinusSquareIcon /></div>)}
expanded={expandedItems} expanded={expandedItems}
> >
<TreeItem {renderTree(tree)}
nodeId={nodeId}
label={root.getName()}
data-testid={nodeId}
classes={classes}
onIconClick={prevDefault(() => toggleNode(nodeId))}
>
{renderChildren(root)}
</TreeItem>
</TreeView> </TreeView>
</div> </div>
); );

View File

@ -12,6 +12,12 @@ import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import type { NamespaceApi } from "../../../common/k8s-api/endpoints/namespace.api"; import type { NamespaceApi } from "../../../common/k8s-api/endpoints/namespace.api";
import { Namespace } 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 { interface Dependencies extends KubeObjectStoreDependencies {
readonly storage: StorageLayer<string[] | undefined>; readonly storage: StorageLayer<string[] | undefined>;
readonly clusterConfiguredAccessibleNamespaces: IComputedValue<string[]>; readonly clusterConfiguredAccessibleNamespaces: IComputedValue<string[]>;
@ -202,6 +208,16 @@ export class NamespaceStore extends KubeObjectStore<Namespace, NamespaceApi> {
this.selectAll(); 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 @action
async remove(item: Namespace) { async remove(item: Namespace) {
await super.remove(item); await super.remove(item);