mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Namespace details tree view (#7080)
* Initial tests for <NamespaceTreeView /> Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Introduce <NamespaceTreeView/> Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Render namespace children Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Render a child subnamespace Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Remove unused lodash import Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Render subnamespace badge after name Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Testing rendering 2 levels deep Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Add tree view to namespace details Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Expand all nodes by default Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Add links to the tree items Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Initial label styling Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Label and group styling Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Remove fontSize attr from SvgIcon Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Styling subnamespace badge Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Expand and collapse tree nodes Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Testing clicking plus icon Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Restyling subnamespace badge Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Adding tooltip for subnamespace badge Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Linter fixes Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fix linter harder Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Replace CloseIcon with semi-transparent MinusIcon Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Styling TreeView inside scss module Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Move isSubnamespace method inside API Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Extract nodeId to avoid repeating Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Rename Icon components Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Clean up tests from boilderplate Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Subnamespace badge style fixes Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Linter fix Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Use font-size: x-small instead of rem units Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Move subnamespace badge show check to parent Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Use prevDefault util Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Refactor: move tree build logic to store Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Linter fixes Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Refactor hierarchicalNamespacesInjectable Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Add tests for getNamespaceTree() function Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Use Subnamespace badge in namespace list (#7132) Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> --------- Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
parent
5d21db9fc2
commit
0719293b11
@ -33,6 +33,20 @@ export class Namespace extends KubeObject<
|
|||||||
getStatus() {
|
getStatus() {
|
||||||
return this.status?.phase ?? "-";
|
return this.status?.phase ?? "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSubnamespace() {
|
||||||
|
return this.getAnnotations().find(annotation => annotation.includes("hnc.x-k8s.io/subnamespace-of"));
|
||||||
|
}
|
||||||
|
|
||||||
|
isChildOf(parentName: string) {
|
||||||
|
return 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> {
|
||||||
|
|||||||
@ -0,0 +1,937 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<NamespaceTreeView /> collapses item by clicking minus button 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="TreeView"
|
||||||
|
data-testid="namespace-tree-view"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="DrawerTitle title"
|
||||||
|
>
|
||||||
|
Tree View
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
aria-multiselectable="false"
|
||||||
|
class="MuiTreeView-root"
|
||||||
|
role="tree"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
aria-expanded="true"
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-levels-deep"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
levels-deep
|
||||||
|
|
||||||
|
</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"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-level-deep-child-a"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="opacity: 0.3;"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
level-deep-child-a
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
aria-expanded="false"
|
||||||
|
class="MuiTreeItem-root"
|
||||||
|
data-testid="namespace-level-deep-child-b"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="plus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 12.977h-4.923v4.896q0 .401-.281.682t-.682.281v0q-.375 0-.669-.281t-.294-.682v-4.896h-4.923q-.401 0-.682-.294t-.281-.669v0q0-.401.281-.682t.682-.281h4.923v-4.896q0-.401.294-.682t.669-.281v0q.401 0 .682.281t.281.682v4.896h4.923q.401 0 .682.281t.281.682v0q0 .375-.281.669t-.682.294z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
level-deep-child-b
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
class="MuiCollapse-root MuiTreeItem-group group"
|
||||||
|
role="group"
|
||||||
|
style="min-height: 0px; height: 0px; transition-duration: 300ms;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiCollapse-wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiCollapse-wrapperInner"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-level-deep-subchild-a"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="opacity: 0.3;"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
level-deep-subchild-a
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<NamespaceTreeView /> expands item by clicking plus button 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="TreeView"
|
||||||
|
data-testid="namespace-tree-view"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="DrawerTitle title"
|
||||||
|
>
|
||||||
|
Tree View
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
aria-multiselectable="false"
|
||||||
|
class="MuiTreeView-root"
|
||||||
|
role="tree"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
aria-expanded="true"
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-levels-deep"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
levels-deep
|
||||||
|
|
||||||
|
</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"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-level-deep-child-a"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="opacity: 0.3;"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
level-deep-child-a
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
aria-expanded="true"
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-level-deep-child-b"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
level-deep-child-b
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
class="MuiCollapse-root MuiTreeItem-group group"
|
||||||
|
role="group"
|
||||||
|
style="min-height: 0px; height: 0px; transition-duration: 300ms;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiCollapse-wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiCollapse-wrapperInner"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-level-deep-subchild-a"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="opacity: 0.3;"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
level-deep-subchild-a
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<NamespaceTreeView /> renders 2 levels deep 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="TreeView"
|
||||||
|
data-testid="namespace-tree-view"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="DrawerTitle title"
|
||||||
|
>
|
||||||
|
Tree View
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
aria-multiselectable="false"
|
||||||
|
class="MuiTreeView-root"
|
||||||
|
role="tree"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
aria-expanded="true"
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-levels-deep"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
levels-deep
|
||||||
|
|
||||||
|
</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"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-level-deep-child-a"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="opacity: 0.3;"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
level-deep-child-a
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
aria-expanded="true"
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-level-deep-child-b"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
level-deep-child-b
|
||||||
|
|
||||||
|
</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"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-level-deep-subchild-a"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="opacity: 0.3;"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
level-deep-subchild-a
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<NamespaceTreeView /> renders namespace with 2 children namespaces 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="TreeView"
|
||||||
|
data-testid="namespace-tree-view"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="DrawerTitle title"
|
||||||
|
>
|
||||||
|
Tree View
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
aria-multiselectable="false"
|
||||||
|
class="MuiTreeView-root"
|
||||||
|
role="tree"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
aria-expanded="true"
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-acme-org"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
acme-org
|
||||||
|
|
||||||
|
</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"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-team-a"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="opacity: 0.3;"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
team-a
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-team-b"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="opacity: 0.3;"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
team-b
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<NamespaceTreeView /> renders namespace with children namespaces and a subnamespace 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="TreeView"
|
||||||
|
data-testid="namespace-tree-view"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="DrawerTitle title"
|
||||||
|
>
|
||||||
|
Tree View
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
aria-multiselectable="false"
|
||||||
|
class="MuiTreeView-root"
|
||||||
|
role="tree"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
aria-expanded="true"
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-org-a"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
org-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"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-team-c"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="opacity: 0.3;"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
team-c
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="MuiTreeItem-root Mui-expanded"
|
||||||
|
data-testid="namespace-service-1"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="opacity: 0.3;"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
service-1
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="subnamespaceBadge"
|
||||||
|
data-testid="namespace-details-badge-for-service-1"
|
||||||
|
id="namespace-details-badge-for-service-1"
|
||||||
|
>
|
||||||
|
S
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<NamespaceTreeView /> renders one namespace without children 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="TreeView"
|
||||||
|
data-testid="namespace-tree-view"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="DrawerTitle title"
|
||||||
|
>
|
||||||
|
Tree View
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
aria-multiselectable="false"
|
||||||
|
class="MuiTreeView-root"
|
||||||
|
role="tree"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="MuiTreeItem-root"
|
||||||
|
data-testid="namespace-single-root"
|
||||||
|
role="treeitem"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTreeItem-iconContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="opacity: 0.3;"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
data-testid="minus-square"
|
||||||
|
focusable="false"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
single-root
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import namespaceStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
|
const hierarchicalNamespacesInjectable = getInjectable({
|
||||||
|
id: "hierarchical-namespaces",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const namespaceStore = di.inject(namespaceStoreInjectable);
|
||||||
|
|
||||||
|
return namespaceStore.items.filter(item => item.isControlledByHNC());
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default hierarchicalNamespacesInjectable;
|
||||||
@ -26,6 +26,9 @@ import limitRangeStoreInjectable from "../+config-limit-ranges/store.injectable"
|
|||||||
import resourceQuotaStoreInjectable from "../+config-resource-quotas/store.injectable";
|
import resourceQuotaStoreInjectable from "../+config-resource-quotas/store.injectable";
|
||||||
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 namespaceStoreInjectable from "./store.injectable";
|
||||||
|
import type { NamespaceStore } from "./store";
|
||||||
|
|
||||||
export interface NamespaceDetailsProps extends KubeObjectDetailsProps<Namespace> {
|
export interface NamespaceDetailsProps extends KubeObjectDetailsProps<Namespace> {
|
||||||
}
|
}
|
||||||
@ -35,6 +38,7 @@ interface Dependencies {
|
|||||||
getDetailsUrl: GetDetailsUrl;
|
getDetailsUrl: GetDetailsUrl;
|
||||||
resourceQuotaStore: ResourceQuotaStore;
|
resourceQuotaStore: ResourceQuotaStore;
|
||||||
limitRangeStore: LimitRangeStore;
|
limitRangeStore: LimitRangeStore;
|
||||||
|
namespaceStore: NamespaceStore;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,6 +107,10 @@ class NonInjectedNamespaceDetails extends React.Component<NamespaceDetailsProps
|
|||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
|
|
||||||
|
{namespace.isControlledByHNC() && (
|
||||||
|
<NamespaceTreeView tree={this.props.namespaceStore.getNamespaceTree(namespace)}/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -115,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),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,205 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
|
import { observable } from "mobx";
|
||||||
|
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||||
|
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import { Namespace } from "../../../common/k8s-api/endpoints";
|
||||||
|
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
|
||||||
|
import createClusterInjectable from "../../cluster/create-cluster.injectable";
|
||||||
|
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||||
|
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
|
||||||
|
import type { NamespaceStore } from "./store";
|
||||||
|
import namespaceStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
|
function createNamespace(name: string, labels?: Record<string, string>, annotations?: Record<string, string>): Namespace {
|
||||||
|
return new Namespace({
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Namespace",
|
||||||
|
metadata: {
|
||||||
|
name,
|
||||||
|
resourceVersion: "1",
|
||||||
|
selfLink: `/api/v1/namespaces/${name}`,
|
||||||
|
uid: `${name}`,
|
||||||
|
labels: {
|
||||||
|
...labels,
|
||||||
|
},
|
||||||
|
annotations: {
|
||||||
|
...annotations,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const singleRoot = createNamespace("single-root", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
});
|
||||||
|
|
||||||
|
const acmeGroup = createNamespace("acme-org", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgA = createNamespace("org-a", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
});
|
||||||
|
|
||||||
|
const teamA = createNamespace("team-a", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
"acme-org.tree.hnc.x-k8s.io/depth": "1",
|
||||||
|
"kubernetes.io/metadata.name": "team-a",
|
||||||
|
"team-a.tree.hnc.x-k8s.io/depth": "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
const teamB = createNamespace("team-b", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
"acme-org.tree.hnc.x-k8s.io/depth": "1",
|
||||||
|
"kubernetes.io/metadata.name": "team-b",
|
||||||
|
"team-b.tree.hnc.x-k8s.io/depth": "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
const teamC = createNamespace("team-c", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
"org-a.tree.hnc.x-k8s.io/depth": "1",
|
||||||
|
"kubernetes.io/metadata.name": "team-c",
|
||||||
|
"team-c.tree.hnc.x-k8s.io/depth": "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
const service1 = createNamespace("service-1", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
"org-a.tree.hnc.x-k8s.io/depth": "1",
|
||||||
|
"kubernetes.io/metadata.name": "team-c",
|
||||||
|
"service-1.tree.hnc.x-k8s.io/depth": "0",
|
||||||
|
}, {
|
||||||
|
"hnc.x-k8s.io/subnamespace-of": "org-a",
|
||||||
|
});
|
||||||
|
|
||||||
|
const levelsDeep = createNamespace("levels-deep", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
});
|
||||||
|
|
||||||
|
const levelDeepChildA = createNamespace("level-deep-child-a", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
"levels-deep.tree.hnc.x-k8s.io/depth": "1",
|
||||||
|
"level-deep-child-a.tree.hnc.x-k8s.io/depth": "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
const levelDeepChildB = createNamespace("level-deep-child-b", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
"levels-deep.tree.hnc.x-k8s.io/depth": "1",
|
||||||
|
"level-deep-child-b.tree.hnc.x-k8s.io/depth": "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
const levelDeepSubChildA = createNamespace("level-deep-subchild-a", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
"levels-deep.tree.hnc.x-k8s.io/depth": "2",
|
||||||
|
"level-deep-child-b.tree.hnc.x-k8s.io/depth": "1",
|
||||||
|
"level-deep-subchild-a.tree.hnc.x-k8s.io/depth": "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe("NamespaceStore", () => {
|
||||||
|
let di: DiContainer;
|
||||||
|
let namespaceStore: NamespaceStore;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
|
||||||
|
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
|
||||||
|
di.override(storesAndApisCanBeCreatedInjectable, () => true);
|
||||||
|
|
||||||
|
const createCluster = di.inject(createClusterInjectable);
|
||||||
|
|
||||||
|
di.override(hostedClusterInjectable, () => createCluster({
|
||||||
|
contextName: "some-context-name",
|
||||||
|
id: "some-cluster-id",
|
||||||
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
|
}, {
|
||||||
|
clusterServerUrl: "https://localhost:8080",
|
||||||
|
}));
|
||||||
|
|
||||||
|
namespaceStore = di.inject(namespaceStoreInjectable);
|
||||||
|
|
||||||
|
namespaceStore.items = observable.array([
|
||||||
|
acmeGroup,
|
||||||
|
orgA,
|
||||||
|
teamA,
|
||||||
|
teamB,
|
||||||
|
teamC,
|
||||||
|
service1,
|
||||||
|
levelsDeep,
|
||||||
|
levelDeepChildA,
|
||||||
|
levelDeepChildB,
|
||||||
|
levelDeepSubChildA,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns tree for single node", () => {
|
||||||
|
const tree = namespaceStore.getNamespaceTree(service1);
|
||||||
|
|
||||||
|
expect(tree).toEqual({
|
||||||
|
id: "service-1",
|
||||||
|
namespace: service1,
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns tree for namespace not listed in store", () => {
|
||||||
|
const tree = namespaceStore.getNamespaceTree(singleRoot);
|
||||||
|
|
||||||
|
expect(tree).toEqual({
|
||||||
|
id: "single-root",
|
||||||
|
namespace: singleRoot,
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("return tree for namespace with children", () => {
|
||||||
|
const tree = namespaceStore.getNamespaceTree(acmeGroup);
|
||||||
|
|
||||||
|
expect(tree).toEqual({
|
||||||
|
id: "acme-org",
|
||||||
|
namespace: acmeGroup,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "team-a",
|
||||||
|
namespace: teamA,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "team-b",
|
||||||
|
namespace: teamB,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("return tree for namespace with deep nested children", () => {
|
||||||
|
const tree = namespaceStore.getNamespaceTree(levelsDeep);
|
||||||
|
|
||||||
|
expect(tree).toEqual({
|
||||||
|
id: "levels-deep",
|
||||||
|
namespace: levelsDeep,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "level-deep-child-a",
|
||||||
|
namespace: levelDeepChildA,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "level-deep-child-b",
|
||||||
|
namespace: levelDeepChildB,
|
||||||
|
children: [{
|
||||||
|
id: "level-deep-subchild-a",
|
||||||
|
namespace: levelDeepSubChildA,
|
||||||
|
children: [],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
.TreeView {
|
||||||
|
.group {
|
||||||
|
margin-inline-start: var(--margin);
|
||||||
|
padding-inline-start: calc(var(--padding) * 2);
|
||||||
|
border-inline-start: 1px dashed var(--borderColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: 1.8;
|
||||||
|
cursor: default;
|
||||||
|
background-color: transparent!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,334 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
|
import { fireEvent } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import { Namespace } from "../../../common/k8s-api/endpoints";
|
||||||
|
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||||
|
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,
|
||||||
|
}));
|
||||||
|
|
||||||
|
function createNamespace(name: string, labels?: Record<string, string>, annotations?: Record<string, string>): Namespace {
|
||||||
|
return new Namespace({
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Namespace",
|
||||||
|
metadata: {
|
||||||
|
name,
|
||||||
|
resourceVersion: "1",
|
||||||
|
selfLink: `/api/v1/namespaces/${name}`,
|
||||||
|
uid: `${name}`,
|
||||||
|
labels: {
|
||||||
|
...labels,
|
||||||
|
},
|
||||||
|
annotations: {
|
||||||
|
...annotations,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const singleRoot = createNamespace("single-root", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
});
|
||||||
|
|
||||||
|
const acmeGroup = createNamespace("acme-org", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgA = createNamespace("org-a", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
});
|
||||||
|
|
||||||
|
const teamA = createNamespace("team-a", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
"acme-org.tree.hnc.x-k8s.io/depth": "1",
|
||||||
|
"kubernetes.io/metadata.name": "team-a",
|
||||||
|
"team-a.tree.hnc.x-k8s.io/depth": "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
const teamB = createNamespace("team-b", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
"acme-org.tree.hnc.x-k8s.io/depth": "1",
|
||||||
|
"kubernetes.io/metadata.name": "team-b",
|
||||||
|
"team-b.tree.hnc.x-k8s.io/depth": "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
const teamC = createNamespace("team-c", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
"org-a.tree.hnc.x-k8s.io/depth": "1",
|
||||||
|
"kubernetes.io/metadata.name": "team-c",
|
||||||
|
"team-c.tree.hnc.x-k8s.io/depth": "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
const service1 = createNamespace("service-1", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
"org-a.tree.hnc.x-k8s.io/depth": "1",
|
||||||
|
"kubernetes.io/metadata.name": "team-c",
|
||||||
|
"service-1.tree.hnc.x-k8s.io/depth": "0",
|
||||||
|
}, {
|
||||||
|
"hnc.x-k8s.io/subnamespace-of": "org-a",
|
||||||
|
});
|
||||||
|
|
||||||
|
const levelsDeep = createNamespace("levels-deep", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
});
|
||||||
|
|
||||||
|
const levelDeepChildA = createNamespace("level-deep-child-a", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
"levels-deep.tree.hnc.x-k8s.io/depth": "1",
|
||||||
|
"level-deep-child-a.tree.hnc.x-k8s.io/depth": "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
const levelDeepChildB = createNamespace("level-deep-child-b", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
"levels-deep.tree.hnc.x-k8s.io/depth": "1",
|
||||||
|
"level-deep-child-b.tree.hnc.x-k8s.io/depth": "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
const levelDeepSubChildA = createNamespace("level-deep-subchild-a", {
|
||||||
|
"hnc.x-k8s.io/included-namespace": "true",
|
||||||
|
"levels-deep.tree.hnc.x-k8s.io/depth": "2",
|
||||||
|
"level-deep-child-b.tree.hnc.x-k8s.io/depth": "1",
|
||||||
|
"level-deep-subchild-a.tree.hnc.x-k8s.io/depth": "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("<NamespaceTreeView />", () => {
|
||||||
|
let di: DiContainer;
|
||||||
|
let render: DiRender;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
di.override(hierarchicalNamespacesInjectable, () => [
|
||||||
|
acmeGroup,
|
||||||
|
orgA,
|
||||||
|
teamA,
|
||||||
|
teamB,
|
||||||
|
teamC,
|
||||||
|
service1,
|
||||||
|
levelsDeep,
|
||||||
|
levelDeepChildA,
|
||||||
|
levelDeepChildB,
|
||||||
|
levelDeepSubChildA,
|
||||||
|
]);
|
||||||
|
|
||||||
|
render = renderFor(di);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders one namespace without children", () => {
|
||||||
|
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 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 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 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")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not render an indicator badge for the true namespace", () => {
|
||||||
|
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']")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders 2 levels deep", () => {
|
||||||
|
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 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 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) {
|
||||||
|
fireEvent.click(minusButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(result.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("expands item by clicking plus button", () => {
|
||||||
|
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) {
|
||||||
|
fireEvent.click(minusButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
const plusButton = levelB.querySelector("[data-testid='plus-square']");
|
||||||
|
|
||||||
|
if (plusButton) {
|
||||||
|
fireEvent.click(plusButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(result.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styles from "./namespace-tree-view.module.scss";
|
||||||
|
|
||||||
|
import { SvgIcon } from "@material-ui/core";
|
||||||
|
import { TreeItem, TreeView } from "@material-ui/lab";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import React from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import type { Namespace } from "../../../common/k8s-api/endpoints";
|
||||||
|
import { DrawerTitle } from "../drawer";
|
||||||
|
import type { GetDetailsUrl } from "../kube-detail-params/get-details-url.injectable";
|
||||||
|
import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injectable";
|
||||||
|
import { SubnamespaceBadge } from "./subnamespace-badge";
|
||||||
|
import hierarchicalNamespacesInjectable from "./hierarchical-namespaces.injectable";
|
||||||
|
import { prevDefault } from "../../utils";
|
||||||
|
import type { NamespaceTree } from "./store";
|
||||||
|
|
||||||
|
interface NamespaceTreeViewProps {
|
||||||
|
tree: NamespaceTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
namespaces: Namespace[];
|
||||||
|
getDetailsUrl: GetDetailsUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 };
|
||||||
|
|
||||||
|
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()}`} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{Array.isArray(nodes.children) ? nodes.children.map((node) => renderTree(node)) : null}
|
||||||
|
</TreeItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleNode(id: string) {
|
||||||
|
if (expandedItems.includes(id)) {
|
||||||
|
setExpandedItems(expandedItems.filter(item => item !== id));
|
||||||
|
} else {
|
||||||
|
setExpandedItems([...expandedItems, id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div data-testid="namespace-tree-view" className={styles.TreeView}>
|
||||||
|
<DrawerTitle>Tree View</DrawerTitle>
|
||||||
|
<TreeView
|
||||||
|
defaultExpanded={[tree.id]}
|
||||||
|
defaultCollapseIcon={<MinusSquareIcon />}
|
||||||
|
defaultExpandIcon={<PlusSquareIcon />}
|
||||||
|
defaultEndIcon={(<div style={{ opacity: 0.3 }}><MinusSquareIcon /></div>)}
|
||||||
|
expanded={expandedItems}
|
||||||
|
>
|
||||||
|
{renderTree(tree)}
|
||||||
|
</TreeView>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MinusSquareIcon() {
|
||||||
|
return (
|
||||||
|
<SvgIcon style={{ width: 14, height: 14 }} data-testid="minus-square">
|
||||||
|
<path d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z" />
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PlusSquareIcon() {
|
||||||
|
return (
|
||||||
|
<SvgIcon style={{ width: 14, height: 14 }} data-testid="plus-square">
|
||||||
|
<path d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 12.977h-4.923v4.896q0 .401-.281.682t-.682.281v0q-.375 0-.669-.281t-.294-.682v-4.896h-4.923q-.401 0-.682-.294t-.281-.669v0q0-.401.281-.682t.682-.281h4.923v-4.896q0-.401.294-.682t.669-.281v0q.401 0 .682.281t.281.682v4.896h4.923q.401 0 .682.281t.281.682v0q0 .375-.281.669t-.682.294z" />
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NamespaceTreeView = withInjectables<Dependencies, NamespaceTreeViewProps>(NonInjectableNamespaceTreeView, {
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
namespaces: di.inject(hierarchicalNamespacesInjectable),
|
||||||
|
getDetailsUrl: di.inject(getDetailsUrlInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
});
|
||||||
@ -22,4 +22,8 @@
|
|||||||
@include namespaceStatus;
|
@include namespaceStatus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subnamespaceBadge {
|
||||||
|
margin-inline-start: var(--margin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import { withInjectables } from "@ogre-tools/injectable-react";
|
|||||||
import namespaceStoreInjectable from "./store.injectable";
|
import namespaceStoreInjectable from "./store.injectable";
|
||||||
import { KubeObjectAge } from "../kube-object/age";
|
import { KubeObjectAge } from "../kube-object/age";
|
||||||
import openAddNamepaceDialogInjectable from "./add-dialog/open.injectable";
|
import openAddNamepaceDialogInjectable from "./add-dialog/open.injectable";
|
||||||
|
import { SubnamespaceBadge } from "./subnamespace-badge";
|
||||||
|
|
||||||
enum columnId {
|
enum columnId {
|
||||||
name = "name",
|
name = "name",
|
||||||
@ -55,7 +56,12 @@ const NonInjectedNamespacesRoute = ({ namespaceStore, openAddNamespaceDialog }:
|
|||||||
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
|
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
|
||||||
]}
|
]}
|
||||||
renderTableContents={namespace => [
|
renderTableContents={namespace => [
|
||||||
namespace.getName(),
|
<>
|
||||||
|
{namespace.getName()}
|
||||||
|
{namespace.isSubnamespace() && (
|
||||||
|
<SubnamespaceBadge className="subnamespaceBadge" id={`namespace-list-badge-for-${namespace.getId()}`} />
|
||||||
|
)}
|
||||||
|
</>,
|
||||||
<KubeObjectStatusIcon key="icon" object={namespace} />,
|
<KubeObjectStatusIcon key="icon" object={namespace} />,
|
||||||
namespace.getLabels().map(label => (
|
namespace.getLabels().map(label => (
|
||||||
<Badge
|
<Badge
|
||||||
|
|||||||
@ -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 interface 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);
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
.subnamespaceBadge {
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 0px 4px;
|
||||||
|
border: 1px solid cadetblue;
|
||||||
|
color: cadetblue;
|
||||||
|
display: inline-flex;
|
||||||
|
font-size: x-small;
|
||||||
|
font-weight: bold;
|
||||||
|
height: 16px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import styles from "./subnamespace-badge.module.scss";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { Tooltip } from "../tooltip";
|
||||||
|
import { cssNames } from "../../utils";
|
||||||
|
|
||||||
|
interface SubnamespaceBadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SubnamespaceBadge({ id, className, ...other }: SubnamespaceBadgeProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className={cssNames(styles.subnamespaceBadge, className)}
|
||||||
|
data-testid={id}
|
||||||
|
id={id}
|
||||||
|
{...other}
|
||||||
|
>
|
||||||
|
S
|
||||||
|
</span>
|
||||||
|
<Tooltip targetId={id}>
|
||||||
|
Subnamespace
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user