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:
parent
0cf649de69
commit
690bef9597
@ -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<Namespace> {
|
||||
|
||||
@ -20,7 +20,7 @@ exports[`<NamespaceTreeView /> collapses item by clicking minus button 1`] = `
|
||||
<li
|
||||
aria-expanded="true"
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-levels-deep-1"
|
||||
data-testid="namespace-levels-deep"
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
>
|
||||
@ -47,6 +47,7 @@ exports[`<NamespaceTreeView /> collapses item by clicking minus button 1`] = `
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
levels-deep
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
@ -62,7 +63,7 @@ exports[`<NamespaceTreeView /> collapses item by clicking minus button 1`] = `
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-child-a-1"
|
||||
data-testid="namespace-level-deep-child-a"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
@ -96,24 +97,11 @@ exports[`<NamespaceTreeView /> collapses item by clicking minus button 1`] = `
|
||||
|
||||
</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
|
||||
aria-expanded="false"
|
||||
class="MuiTreeItem-root"
|
||||
data-testid="namespace-level-deep-child-b-1"
|
||||
data-testid="namespace-level-deep-child-b"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
@ -156,7 +144,7 @@ exports[`<NamespaceTreeView /> collapses item by clicking minus button 1`] = `
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-subchild-a-1"
|
||||
data-testid="namespace-level-deep-subchild-a"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
@ -190,19 +178,6 @@ exports[`<NamespaceTreeView /> collapses item by clicking minus button 1`] = `
|
||||
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
@ -238,7 +213,7 @@ exports[`<NamespaceTreeView /> expands item by clicking plus button 1`] = `
|
||||
<li
|
||||
aria-expanded="true"
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-levels-deep-1"
|
||||
data-testid="namespace-levels-deep"
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
>
|
||||
@ -265,6 +240,7 @@ exports[`<NamespaceTreeView /> expands item by clicking plus button 1`] = `
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
levels-deep
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
@ -280,7 +256,7 @@ exports[`<NamespaceTreeView /> expands item by clicking plus button 1`] = `
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-child-a-1"
|
||||
data-testid="namespace-level-deep-child-a"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
@ -314,24 +290,11 @@ exports[`<NamespaceTreeView /> expands item by clicking plus button 1`] = `
|
||||
|
||||
</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
|
||||
aria-expanded="true"
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-child-b-1"
|
||||
data-testid="namespace-level-deep-child-b"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
@ -374,7 +337,7 @@ exports[`<NamespaceTreeView /> expands item by clicking plus button 1`] = `
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-subchild-a-1"
|
||||
data-testid="namespace-level-deep-subchild-a"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
@ -408,19 +371,6 @@ exports[`<NamespaceTreeView /> expands item by clicking plus button 1`] = `
|
||||
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
@ -456,7 +406,7 @@ exports[`<NamespaceTreeView /> renders 2 levels deep 1`] = `
|
||||
<li
|
||||
aria-expanded="true"
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-levels-deep-1"
|
||||
data-testid="namespace-levels-deep"
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
>
|
||||
@ -483,6 +433,7 @@ exports[`<NamespaceTreeView /> renders 2 levels deep 1`] = `
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
levels-deep
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
@ -498,7 +449,7 @@ exports[`<NamespaceTreeView /> renders 2 levels deep 1`] = `
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-child-a-1"
|
||||
data-testid="namespace-level-deep-child-a"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
@ -532,24 +483,11 @@ exports[`<NamespaceTreeView /> renders 2 levels deep 1`] = `
|
||||
|
||||
</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
|
||||
aria-expanded="true"
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-child-b-1"
|
||||
data-testid="namespace-level-deep-child-b"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
@ -592,7 +530,7 @@ exports[`<NamespaceTreeView /> renders 2 levels deep 1`] = `
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-subchild-a-1"
|
||||
data-testid="namespace-level-deep-subchild-a"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
@ -626,19 +564,6 @@ exports[`<NamespaceTreeView /> renders 2 levels deep 1`] = `
|
||||
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
@ -674,7 +599,7 @@ exports[`<NamespaceTreeView /> renders namespace with 2 children namespaces 1`]
|
||||
<li
|
||||
aria-expanded="true"
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-acme-org-1"
|
||||
data-testid="namespace-acme-org"
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
>
|
||||
@ -701,6 +626,7 @@ exports[`<NamespaceTreeView /> renders namespace with 2 children namespaces 1`]
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
acme-org
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
@ -716,7 +642,7 @@ exports[`<NamespaceTreeView /> renders namespace with 2 children namespaces 1`]
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-team-a-1"
|
||||
data-testid="namespace-team-a"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
@ -750,23 +676,10 @@ exports[`<NamespaceTreeView /> renders namespace with 2 children namespaces 1`]
|
||||
|
||||
</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
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-team-b-1"
|
||||
data-testid="namespace-team-b"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
@ -800,19 +713,6 @@ exports[`<NamespaceTreeView /> renders namespace with 2 children namespaces 1`]
|
||||
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
@ -844,7 +744,7 @@ exports[`<NamespaceTreeView /> renders namespace with children namespaces and a
|
||||
<li
|
||||
aria-expanded="true"
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-org-a-1"
|
||||
data-testid="namespace-org-a"
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
>
|
||||
@ -871,6 +771,7 @@ exports[`<NamespaceTreeView /> renders namespace with children namespaces and a
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
org-a
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
@ -886,7 +787,7 @@ exports[`<NamespaceTreeView /> renders namespace with children namespaces and a
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-team-c-1"
|
||||
data-testid="namespace-team-c"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
@ -920,23 +821,10 @@ exports[`<NamespaceTreeView /> renders namespace with children namespaces and a
|
||||
|
||||
</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
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-service-1-1"
|
||||
data-testid="namespace-service-1"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
@ -970,26 +858,13 @@ exports[`<NamespaceTreeView /> renders namespace with children namespaces and a
|
||||
|
||||
<span
|
||||
class="subnamespaceBadge"
|
||||
data-testid="namespace-details-badge-for-service-1-1"
|
||||
id="namespace-details-badge-for-service-1-1"
|
||||
data-testid="namespace-details-badge-for-service-1"
|
||||
id="namespace-details-badge-for-service-1"
|
||||
>
|
||||
S
|
||||
</span>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
@ -1001,12 +876,6 @@ exports[`<NamespaceTreeView /> renders namespace with children namespaces and a
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`<NamespaceTreeView /> renders null with regular namespace 1`] = `
|
||||
<body>
|
||||
<div />
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`<NamespaceTreeView /> renders one namespace without children 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
@ -1026,7 +895,7 @@ exports[`<NamespaceTreeView /> renders one namespace without children 1`] = `
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root"
|
||||
data-testid="namespace-single-root-1"
|
||||
data-testid="namespace-single-root"
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
>
|
||||
@ -1057,6 +926,7 @@ exports[`<NamespaceTreeView /> renders one namespace without children 1`] = `
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
single-root
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@ -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<Namespace> {
|
||||
}
|
||||
@ -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<NamespaceDetailsProps
|
||||
))}
|
||||
</DrawerItem>
|
||||
|
||||
<NamespaceTreeView root={namespace}/>
|
||||
{namespace.isControlledByHNC() && (
|
||||
<NamespaceTreeView tree={this.props.namespaceStore.getNamespaceTree(namespace)}/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -118,6 +123,7 @@ export const NamespaceDetails = withInjectables<Dependencies, NamespaceDetailsPr
|
||||
getDetailsUrl: di.inject(getDetailsUrlInjectable),
|
||||
limitRangeStore: di.inject(limitRangeStoreInjectable),
|
||||
resourceQuotaStore: di.inject(resourceQuotaStoreInjectable),
|
||||
namespaceStore: di.inject(namespaceStoreInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -11,6 +11,7 @@ import type { DiRender } from "../test-utils/renderFor";
|
||||
import { renderFor } from "../test-utils/renderFor";
|
||||
import hierarchicalNamespacesInjectable from "./hierarchical-namespaces.injectable";
|
||||
import { NamespaceTreeView } from "./namespace-tree-view";
|
||||
import type { NamespaceTree } from "./store";
|
||||
|
||||
jest.mock("react-router-dom", () => ({
|
||||
Link: ({ children }: { children: React.ReactNode }) => children,
|
||||
@ -24,7 +25,7 @@ function createNamespace(name: string, labels?: Record<string, string>, annotati
|
||||
name,
|
||||
resourceVersion: "1",
|
||||
selfLink: `/api/v1/namespaces/${name}`,
|
||||
uid: `${name}-1`,
|
||||
uid: `${name}`,
|
||||
labels: {
|
||||
...labels,
|
||||
},
|
||||
@ -123,59 +124,169 @@ describe("<NamespaceTreeView />", () => {
|
||||
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", () => {
|
||||
const result = render(<NamespaceTreeView root={singleRoot} />);
|
||||
const tree: NamespaceTree = {
|
||||
id: "single-root",
|
||||
namespace: singleRoot
|
||||
}
|
||||
|
||||
const result = render(<NamespaceTreeView tree={tree} />);
|
||||
|
||||
expect(result.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
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", () => {
|
||||
const result = render(<NamespaceTreeView root={orgA} />);
|
||||
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(<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", () => {
|
||||
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();
|
||||
});
|
||||
|
||||
it("expands children items by default", () => {
|
||||
const result = render(<NamespaceTreeView root={levelsDeep} />);
|
||||
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(<NamespaceTreeView tree={tree} />);
|
||||
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(<NamespaceTreeView root={levelsDeep} />);
|
||||
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(<NamespaceTreeView tree={tree} />);
|
||||
const levelB = result.getByTestId("namespace-level-deep-child-b");
|
||||
const minusButton = levelB.querySelector("[data-testid='minus-square']");
|
||||
|
||||
if (minusButton) {
|
||||
@ -186,8 +297,26 @@ describe("<NamespaceTreeView />", () => {
|
||||
});
|
||||
|
||||
it("expands item by clicking plus button", () => {
|
||||
const result = render(<NamespaceTreeView root={levelsDeep} />);
|
||||
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(<NamespaceTreeView tree={tree} />);
|
||||
const levelB = result.getByTestId("namespace-level-deep-child-b");
|
||||
const minusButton = levelB.querySelector("[data-testid='minus-square']");
|
||||
|
||||
if (minusButton) {
|
||||
|
||||
@ -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<string[]>(namespaces.map(ns => `namespace-${ns.getId()}`));
|
||||
function NonInjectableNamespaceTreeView({ tree, namespaces, getDetailsUrl }: Dependencies & NamespaceTreeViewProps) {
|
||||
const [expandedItems, setExpandedItems] = React.useState<string[]>(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 (
|
||||
<TreeItem
|
||||
key={childId}
|
||||
nodeId={childId}
|
||||
data-testid={childId}
|
||||
classes={classes}
|
||||
onIconClick={prevDefault(() => toggleNode(childId))}
|
||||
label={(
|
||||
<>
|
||||
<Link key={child.getId()} to={getDetailsUrl(child.selfLink)}>
|
||||
{child.getName()}
|
||||
</Link>
|
||||
{" "}
|
||||
{child.isSubnamespace() && (
|
||||
<SubnamespaceBadge id={`namespace-details-badge-for-${child.getId()}`} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
function renderTree(nodes: NamespaceTree) {
|
||||
return (
|
||||
<TreeItem
|
||||
key={nodes.id}
|
||||
nodeId={nodes.id}
|
||||
data-testid={`namespace-${nodes.id}`}
|
||||
classes={classes}
|
||||
onIconClick={prevDefault(() => toggleNode(nodes.id))}
|
||||
label={(
|
||||
<>
|
||||
<Link key={nodes.namespace.getId()} to={getDetailsUrl(nodes.namespace.selfLink)}>
|
||||
{nodes.namespace.getName()}
|
||||
</Link>
|
||||
{" "}
|
||||
{nodes.namespace.isSubnamespace() && (
|
||||
<SubnamespaceBadge id={`namespace-details-badge-for-${nodes.namespace.getId()}`} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
>
|
||||
{renderChildren(child)}
|
||||
</TreeItem>
|
||||
);
|
||||
});
|
||||
{Array.isArray(nodes.children) ? nodes.children.map((node) => renderTree(node)) : null}
|
||||
</TreeItem>
|
||||
)
|
||||
}
|
||||
|
||||
function toggleNode(id: string) {
|
||||
@ -79,29 +65,17 @@ function NonInjectableNamespaceTreeView({ root, namespaces, getDetailsUrl }: Dep
|
||||
}
|
||||
}
|
||||
|
||||
if (!isNamespaceControlledByHNC(root)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="namespace-tree-view" className={styles.TreeView}>
|
||||
<DrawerTitle>Tree View</DrawerTitle>
|
||||
<TreeView
|
||||
defaultExpanded={[nodeId]}
|
||||
defaultExpanded={[tree.id]}
|
||||
defaultCollapseIcon={<MinusSquareIcon />}
|
||||
defaultExpandIcon={<PlusSquareIcon />}
|
||||
defaultEndIcon={(<div style={{ opacity: 0.3 }}><MinusSquareIcon /></div>)}
|
||||
expanded={expandedItems}
|
||||
>
|
||||
<TreeItem
|
||||
nodeId={nodeId}
|
||||
label={root.getName()}
|
||||
data-testid={nodeId}
|
||||
classes={classes}
|
||||
onIconClick={prevDefault(() => toggleNode(nodeId))}
|
||||
>
|
||||
{renderChildren(root)}
|
||||
</TreeItem>
|
||||
{renderTree(tree)}
|
||||
</TreeView>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -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<string[] | undefined>;
|
||||
readonly clusterConfiguredAccessibleNamespaces: IComputedValue<string[]>;
|
||||
@ -202,6 +208,16 @@ export class NamespaceStore extends KubeObjectStore<Namespace, NamespaceApi> {
|
||||
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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user