Skip to content

Commit 9e1f179

Browse files
authored
FLPATH-2747 | [FE] New navigation structure (#1719)
1 parent 97c55ed commit 9e1f179

9 files changed

Lines changed: 303 additions & 12 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
plugins:
2+
- package: ./plugins/redhat-resource-optimization/dist/red-hat-developer-hub-backstage-plugin-redhat-resource-optimization
3+
disabled: false
4+
pluginConfig:
5+
dynamicPlugins:
6+
frontend:
7+
red-hat-developer-hub.backstage-plugin-redhat-resource-optimization:
8+
appIcons:
9+
- name: costManagementIcon
10+
importName: ResourceOptimizationIcon
11+
dynamicRoutes:
12+
- path: /resource-optimization
13+
importName: ResourceOptimizationPage
14+
- path: /resource-optimization/openshift
15+
importName: OpenShiftPage
16+
menuItems:
17+
cost-management:
18+
text: Cost management
19+
icon: costManagementIcon
20+
children:
21+
openshift:
22+
text: OpenShift
23+
route: /resource-optimization/openshift
24+
optimizations:
25+
text: Optimizations
26+
route: /resource-optimization

workspaces/redhat-resource-optimization/packages/app/src/components/Root/Root.tsx

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import React, { PropsWithChildren } from 'react';
17+
import React, { PropsWithChildren, useState } from 'react';
1818
import { makeStyles } from '@material-ui/core';
1919
import HomeIcon from '@material-ui/icons/Home';
2020
import ExtensionIcon from '@material-ui/icons/Extension';
@@ -40,11 +40,16 @@ import {
4040
} from '@backstage/core-components';
4141
import MenuIcon from '@material-ui/icons/Menu';
4242
import SearchIcon from '@material-ui/icons/Search';
43-
import { ResourceOptimizationIconOutlined } from '@red-hat-developer-hub/plugin-redhat-resource-optimization';
43+
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
44+
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
45+
import { AnalyticsIconOutlined } from '@red-hat-developer-hub/plugin-redhat-resource-optimization';
4446
import { OrchestratorIcon } from '@red-hat-developer-hub/backstage-plugin-orchestrator';
4547
import { useRhdhTheme } from '../../hooks/useRhdhTheme';
4648
import { Administration } from '@backstage-community/plugin-rbac';
4749

50+
// Empty icon component to satisfy SidebarItem's required icon prop
51+
const EmptyIcon = () => null;
52+
4853
const useSidebarLogoStyles = makeStyles({
4954
root: {
5055
width: sidebarConfig.drawerWidthClosed,
@@ -76,6 +81,52 @@ const Logo = (props: { isOpen?: boolean }) => {
7681
return logo;
7782
};
7883

84+
const CollapsibleSubmenu = ({
85+
icon,
86+
text,
87+
children,
88+
}: {
89+
icon: React.ReactElement;
90+
text: string;
91+
children: React.ReactNode;
92+
}) => {
93+
const [isOpen, setIsOpen] = useState(true);
94+
const { isOpen: sidebarOpen } = useSidebarOpenState();
95+
96+
return (
97+
<>
98+
<div
99+
onClick={() => setIsOpen(!isOpen)}
100+
onKeyDown={e => {
101+
if (e.key === 'Enter' || e.key === ' ') {
102+
e.preventDefault();
103+
setIsOpen(!isOpen);
104+
}
105+
}}
106+
role="button"
107+
tabIndex={0}
108+
style={{
109+
display: 'flex',
110+
alignItems: 'center',
111+
cursor: 'pointer',
112+
padding: '12px 16px',
113+
userSelect: 'none',
114+
}}
115+
>
116+
{icon}
117+
{sidebarOpen && <span style={{ marginLeft: 12, flex: 1 }}>{text}</span>}
118+
{sidebarOpen &&
119+
(isOpen ? (
120+
<ExpandMoreIcon style={{ fontSize: '20px' }} />
121+
) : (
122+
<ChevronRightIcon style={{ fontSize: '20px' }} />
123+
))}
124+
</div>
125+
{isOpen && <div style={{ marginLeft: 40 }}>{children}</div>}
126+
</>
127+
);
128+
};
129+
79130
const SidebarLogo = () => {
80131
const classes = useSidebarLogoStyles();
81132
const { isOpen } = useSidebarOpenState();
@@ -105,11 +156,23 @@ export const Root = ({ children }: PropsWithChildren<{}>) => (
105156
<SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
106157
{/* End global nav */}
107158
<SidebarDivider />
108-
<SidebarItem
109-
icon={ResourceOptimizationIconOutlined}
110-
to="/redhat-resource-optimization"
111-
text="Optimizations"
112-
/>
159+
160+
<CollapsibleSubmenu
161+
icon={<AnalyticsIconOutlined />}
162+
text="Cost management"
163+
>
164+
<SidebarItem
165+
icon={EmptyIcon}
166+
to="/redhat-resource-optimization/ocp"
167+
text="OpenShift"
168+
/>
169+
<SidebarItem
170+
icon={EmptyIcon}
171+
to="/redhat-resource-optimization"
172+
text="Optimizations"
173+
/>
174+
</CollapsibleSubmenu>
175+
113176
<SidebarItem
114177
icon={OrchestratorIcon}
115178
to="orchestrator"

workspaces/redhat-resource-optimization/plugins/redhat-resource-optimization/src/Router.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
import { ErrorPage } from '@backstage/core-components';
1818
import React from 'react';
1919
import { Routes, Route } from 'react-router-dom';
20-
import { optimizationsBreakdownRouteRef } from './routes';
2120
import { OptimizationsPage } from './pages/optimizations/OptimizationsPage';
2221
import { OptimizationsBreakdownPage } from './pages/optimizations-breakdown/OptimizationsBreakdownPage';
22+
import { OpenShiftPage } from './pages/openshift/OpenShiftPage';
2323
import { usePatternFlyTheme } from './hooks/usePatternFlyTheme';
2424

2525
/** @public */
@@ -30,10 +30,8 @@ export function Router() {
3030
return (
3131
<Routes>
3232
<Route path="/" element={<OptimizationsPage />} />
33-
<Route
34-
path={optimizationsBreakdownRouteRef.path}
35-
element={<OptimizationsBreakdownPage />}
36-
/>
33+
<Route path="/ocp" element={<OpenShiftPage />} />
34+
<Route path="/:id/*" element={<OptimizationsBreakdownPage />} />
3735
<Route
3836
path="*"
3937
element={<ErrorPage status="404" statusMessage="Page not found" />}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import React from 'react';
18+
import { renderInTestApp } from '@backstage/test-utils';
19+
import { AnalyticsIcon } from './AnalyticsIcon';
20+
import { AnalyticsIconOutlined } from './AnalyticsIconOutlined';
21+
import { AnalyticsIconFilled } from './AnalyticsIconFilled';
22+
23+
describe('AnalyticsIcon', () => {
24+
it('should render the outlined version by default', async () => {
25+
const view = await renderInTestApp(<AnalyticsIcon />);
26+
27+
expect(view.asFragment()).toMatchInlineSnapshot(`
28+
<DocumentFragment>
29+
<svg
30+
aria-hidden="true"
31+
class="MuiSvgIcon-root"
32+
focusable="false"
33+
viewBox="0 -960 960 960"
34+
>
35+
<path
36+
d="M280-280h80v-200h-80v200Zm320 0h80v-400h-80v400Zm-160 0h80v-120h-80v120Zm0-200h80v-80h-80v80ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z"
37+
/>
38+
</svg>
39+
</DocumentFragment>
40+
`);
41+
});
42+
43+
it('should render the outlined version when passed the outlined variant', async () => {
44+
const view = await renderInTestApp(<AnalyticsIconOutlined />);
45+
46+
expect(view.asFragment()).toMatchInlineSnapshot(`
47+
<DocumentFragment>
48+
<svg
49+
aria-hidden="true"
50+
class="MuiSvgIcon-root"
51+
focusable="false"
52+
variant="outlined"
53+
viewBox="0 -960 960 960"
54+
>
55+
<path
56+
d="M280-280h80v-200h-80v200Zm320 0h80v-400h-80v400Zm-160 0h80v-120h-80v120Zm0-200h80v-80h-80v80ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z"
57+
/>
58+
</svg>
59+
</DocumentFragment>
60+
`);
61+
});
62+
63+
it('should render the filled version when passed the filled variant', async () => {
64+
const view = await renderInTestApp(<AnalyticsIconFilled />);
65+
66+
expect(view.asFragment()).toMatchInlineSnapshot(`
67+
<DocumentFragment>
68+
<svg
69+
aria-hidden="true"
70+
class="MuiSvgIcon-root"
71+
focusable="false"
72+
variant="filled"
73+
viewBox="0 -960 960 960"
74+
>
75+
<path
76+
d="M280-280h80v-200h-80v200Zm320 0h80v-400h-80v400Zm-160 0h80v-120h-80v120Zm0-200h80v-80h-80v80ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Z"
77+
/>
78+
</svg>
79+
</DocumentFragment>
80+
`);
81+
});
82+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import React from 'react';
18+
import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
19+
20+
/**
21+
* The Analytics icon.
22+
*
23+
* @public
24+
*/
25+
export const AnalyticsIcon = (
26+
props: SvgIconProps & { variant?: 'outlined' | 'filled' },
27+
) => {
28+
const variant = props.variant ?? 'outlined';
29+
const path =
30+
variant === 'outlined' ? (
31+
<path d="M280-280h80v-200h-80v200Zm320 0h80v-400h-80v400Zm-160 0h80v-120h-80v120Zm0-200h80v-80h-80v80ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z" />
32+
) : (
33+
<path d="M280-280h80v-200h-80v200Zm320 0h80v-400h-80v400Zm-160 0h80v-120h-80v120Zm0-200h80v-80h-80v80ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Z" />
34+
);
35+
36+
return (
37+
<SvgIcon {...props} viewBox="0 -960 960 960">
38+
{path}
39+
</SvgIcon>
40+
);
41+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import React from 'react';
18+
import type { SvgIconProps } from '@material-ui/core/SvgIcon';
19+
import { AnalyticsIcon } from './AnalyticsIcon';
20+
21+
/**
22+
* The filled variant of the Analytics icon.
23+
*
24+
* @public
25+
*/
26+
export const AnalyticsIconFilled = (props: SvgIconProps) => {
27+
return <AnalyticsIcon variant="filled" {...props} />;
28+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import React from 'react';
18+
import type { SvgIconProps } from '@material-ui/core/SvgIcon';
19+
import { AnalyticsIcon } from './AnalyticsIcon';
20+
21+
/**
22+
* The outlined variant of the Analytics icon.
23+
*
24+
* @public
25+
*/
26+
export const AnalyticsIconOutlined = (props: SvgIconProps) => {
27+
return <AnalyticsIcon variant="outlined" {...props} />;
28+
};

workspaces/redhat-resource-optimization/plugins/redhat-resource-optimization/src/components/icon/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@
1717
export { ResourceOptimizationIcon } from './ResourceOptimizationIcon';
1818
export { ResourceOptimizationIconOutlined } from './ResourceOptimizationIconOutlined';
1919
export { ResourceOptimizationIconFilled } from './ResourceOptimizationIconFilled';
20+
export { AnalyticsIcon } from './AnalyticsIcon';
21+
export { AnalyticsIconOutlined } from './AnalyticsIconOutlined';
22+
export { AnalyticsIconFilled } from './AnalyticsIconFilled';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import React from 'react';
18+
19+
/** @public */
20+
export function OpenShiftPage() {
21+
return <div>OpenShiftPage</div>;
22+
}

0 commit comments

Comments
 (0)