Skip to content

Commit 78b8264

Browse files
authored
feat(site): add deployment menu to navbar (#13401)
1 parent c7233ec commit 78b8264

File tree

8 files changed

+251
-112
lines changed

8 files changed

+251
-112
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { css, type Interpolation, type Theme, useTheme } from "@emotion/react";
2+
import Button from "@mui/material/Button";
3+
import MenuItem from "@mui/material/MenuItem";
4+
import type { FC } from "react";
5+
import { NavLink } from "react-router-dom";
6+
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
7+
import {
8+
Popover,
9+
PopoverContent,
10+
PopoverTrigger,
11+
usePopover,
12+
} from "components/Popover/Popover";
13+
import { USERS_LINK } from "modules/navigation";
14+
15+
interface DeploymentDropdownProps {
16+
canViewAuditLog: boolean;
17+
canViewDeployment: boolean;
18+
canViewAllUsers: boolean;
19+
canViewHealth: boolean;
20+
}
21+
22+
export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
23+
canViewAuditLog,
24+
canViewDeployment,
25+
canViewAllUsers,
26+
canViewHealth,
27+
}) => {
28+
const theme = useTheme();
29+
30+
if (
31+
!canViewAuditLog &&
32+
!canViewDeployment &&
33+
!canViewAllUsers &&
34+
!canViewHealth
35+
) {
36+
return null;
37+
}
38+
39+
return (
40+
<Popover>
41+
<PopoverTrigger>
42+
<Button
43+
size="small"
44+
endIcon={
45+
<DropdownArrow
46+
color={theme.experimental.l2.fill.solid}
47+
close={false}
48+
margin={false}
49+
/>
50+
}
51+
>
52+
Deployment
53+
</Button>
54+
</PopoverTrigger>
55+
56+
<PopoverContent
57+
horizontal="right"
58+
css={{
59+
".MuiPaper-root": {
60+
minWidth: "auto",
61+
width: 180,
62+
boxShadow: theme.shadows[6],
63+
},
64+
}}
65+
>
66+
<DeploymentDropdownContent
67+
canViewAuditLog={canViewAuditLog}
68+
canViewDeployment={canViewDeployment}
69+
canViewAllUsers={canViewAllUsers}
70+
canViewHealth={canViewHealth}
71+
/>
72+
</PopoverContent>
73+
</Popover>
74+
);
75+
};
76+
77+
const DeploymentDropdownContent: FC<DeploymentDropdownProps> = ({
78+
canViewAuditLog,
79+
canViewDeployment,
80+
canViewAllUsers,
81+
canViewHealth,
82+
}) => {
83+
const popover = usePopover();
84+
85+
const onPopoverClose = () => popover.setIsOpen(false);
86+
87+
return (
88+
<nav>
89+
{canViewDeployment && (
90+
<MenuItem
91+
component={NavLink}
92+
to="/deployment/general"
93+
css={styles.menuItem}
94+
onClick={onPopoverClose}
95+
>
96+
Settings
97+
</MenuItem>
98+
)}
99+
{canViewAllUsers && (
100+
<MenuItem
101+
component={NavLink}
102+
to={USERS_LINK}
103+
css={styles.menuItem}
104+
onClick={onPopoverClose}
105+
>
106+
Users
107+
</MenuItem>
108+
)}
109+
{canViewAuditLog && (
110+
<MenuItem
111+
component={NavLink}
112+
to="/audit"
113+
css={styles.menuItem}
114+
onClick={onPopoverClose}
115+
>
116+
Auditing
117+
</MenuItem>
118+
)}
119+
{canViewHealth && (
120+
<MenuItem
121+
component={NavLink}
122+
to="/health"
123+
css={styles.menuItem}
124+
onClick={onPopoverClose}
125+
>
126+
Healthcheck
127+
</MenuItem>
128+
)}
129+
</nav>
130+
);
131+
};
132+
133+
const styles = {
134+
menuItem: (theme) => css`
135+
text-decoration: none;
136+
color: inherit;
137+
gap: 20px;
138+
padding: 8px 20px;
139+
font-size: 14px;
140+
141+
&:hover {
142+
background-color: ${theme.palette.action.hover};
143+
transition: background-color 0.3s ease;
144+
}
145+
`,
146+
menuItemIcon: (theme) => ({
147+
color: theme.palette.text.secondary,
148+
width: 20,
149+
height: 20,
150+
}),
151+
} satisfies Record<string, Interpolation<Theme>>;

site/src/modules/dashboard/Navbar/Navbar.test.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { render, screen, waitFor } from "@testing-library/react";
2+
import userEvent from "@testing-library/user-event";
23
import { HttpResponse, http } from "msw";
34
import { App } from "App";
45
import {
@@ -21,6 +22,8 @@ describe("Navbar", () => {
2122
}),
2223
);
2324
render(<App />);
25+
const deploymentMenu = await screen.findByText("Deployment");
26+
await userEvent.click(deploymentMenu);
2427
await waitFor(
2528
() => {
2629
const link = screen.getByText(Language.audit);
@@ -34,6 +37,8 @@ describe("Navbar", () => {
3437
// by default, user is an Admin with permission to see the audit log,
3538
// but is unlicensed so not entitled to see the audit log
3639
render(<App />);
40+
const deploymentMenu = await screen.findByText("Deployment");
41+
await userEvent.click(deploymentMenu);
3742
await waitFor(
3843
() => {
3944
const link = screen.queryByText(Language.audit);
@@ -59,7 +64,7 @@ describe("Navbar", () => {
5964
render(<App />);
6065
await waitFor(
6166
() => {
62-
const link = screen.queryByText(Language.audit);
67+
const link = screen.queryByText("Deployment");
6368
expect(link).toBe(null);
6469
},
6570
{ timeout: 2000 },

site/src/modules/dashboard/Navbar/NavbarView.stories.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ const meta: Meta<typeof NavbarView> = {
1010
component: NavbarView,
1111
args: {
1212
user: MockUser,
13+
canViewAuditLog: true,
14+
canViewDeployment: true,
15+
canViewAllUsers: true,
16+
canViewHealth: true,
1317
},
1418
decorators: [withDashboardProvider],
1519
};
@@ -25,6 +29,7 @@ export const ForMember: Story = {
2529
canViewAuditLog: false,
2630
canViewDeployment: false,
2731
canViewAllUsers: false,
32+
canViewHealth: false,
2833
},
2934
};
3035

site/src/modules/dashboard/Navbar/NavbarView.test.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { screen } from "@testing-library/react";
2+
import userEvent from "@testing-library/user-event";
23
import type { ProxyContextValue } from "contexts/ProxyContext";
34
import { MockPrimaryWorkspaceProxy, MockUser } from "testHelpers/entities";
45
import { renderWithAuth } from "testHelpers/renderHelpers";
@@ -65,6 +66,8 @@ describe("NavbarView", () => {
6566
canViewHealth
6667
/>,
6768
);
69+
const deploymentMenu = await screen.findByText("Deployment");
70+
await userEvent.click(deploymentMenu);
6871
const userLink = await screen.findByText(navLanguage.users);
6972
expect((userLink as HTMLAnchorElement).href).toContain("/users");
7073
});
@@ -81,6 +84,8 @@ describe("NavbarView", () => {
8184
canViewHealth
8285
/>,
8386
);
87+
const deploymentMenu = await screen.findByText("Deployment");
88+
await userEvent.click(deploymentMenu);
8489
const auditLink = await screen.findByText(navLanguage.audit);
8590
expect((auditLink as HTMLAnchorElement).href).toContain("/audit");
8691
});
@@ -97,8 +102,12 @@ describe("NavbarView", () => {
97102
canViewHealth
98103
/>,
99104
);
100-
const auditLink = await screen.findByText(navLanguage.deployment);
101-
expect((auditLink as HTMLAnchorElement).href).toContain(
105+
const deploymentMenu = await screen.findByText("Deployment");
106+
await userEvent.click(deploymentMenu);
107+
const deploymentSettingsLink = await screen.findByText(
108+
navLanguage.deployment,
109+
);
110+
expect((deploymentSettingsLink as HTMLAnchorElement).href).toContain(
102111
"/deployment/general",
103112
);
104113
});

site/src/modules/dashboard/Navbar/NavbarView.tsx

Lines changed: 15 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Menu from "@mui/material/Menu";
99
import MenuItem from "@mui/material/MenuItem";
1010
import Skeleton from "@mui/material/Skeleton";
1111
import { visuallyHidden } from "@mui/utils";
12-
import { type FC, type ReactNode, useRef, useState } from "react";
12+
import { type FC, useRef, useState } from "react";
1313
import { NavLink, useLocation, useNavigate } from "react-router-dom";
1414
import type * as TypesGen from "api/typesGenerated";
1515
import { Abbr } from "components/Abbr/Abbr";
@@ -20,12 +20,9 @@ import { Latency } from "components/Latency/Latency";
2020
import { useAuthenticated } from "contexts/auth/RequireAuth";
2121
import type { ProxyContextValue } from "contexts/ProxyContext";
2222
import { BUTTON_SM_HEIGHT, navHeight } from "theme/constants";
23+
import { DeploymentDropdown } from "./DeploymentDropdown";
2324
import { UserDropdown } from "./UserDropdown/UserDropdown";
2425

25-
export const USERS_LINK = `/users?filter=${encodeURIComponent(
26-
"status:active",
27-
)}`;
28-
2926
export interface NavbarViewProps {
3027
logo_url?: string;
3128
user?: TypesGen.User;
@@ -43,26 +40,15 @@ export const Language = {
4340
workspaces: "Workspaces",
4441
templates: "Templates",
4542
users: "Users",
46-
audit: "Audit",
47-
deployment: "Deployment",
43+
audit: "Auditing",
44+
deployment: "Settings",
4845
};
4946

5047
interface NavItemsProps {
51-
children?: ReactNode;
5248
className?: string;
53-
canViewAuditLog: boolean;
54-
canViewDeployment: boolean;
55-
canViewAllUsers: boolean;
56-
canViewHealth: boolean;
5749
}
5850

59-
const NavItems: FC<NavItemsProps> = ({
60-
className,
61-
canViewAuditLog,
62-
canViewDeployment,
63-
canViewAllUsers,
64-
canViewHealth,
65-
}) => {
51+
const NavItems: FC<NavItemsProps> = ({ className }) => {
6652
const location = useLocation();
6753
const theme = useTheme();
6854

@@ -83,26 +69,6 @@ const NavItems: FC<NavItemsProps> = ({
8369
<NavLink css={styles.link} to="/templates">
8470
{Language.templates}
8571
</NavLink>
86-
{canViewAllUsers && (
87-
<NavLink css={styles.link} to={USERS_LINK}>
88-
{Language.users}
89-
</NavLink>
90-
)}
91-
{canViewAuditLog && (
92-
<NavLink css={styles.link} to="/audit">
93-
{Language.audit}
94-
</NavLink>
95-
)}
96-
{canViewDeployment && (
97-
<NavLink css={styles.link} to="/deployment/general">
98-
{Language.deployment}
99-
</NavLink>
100-
)}
101-
{canViewHealth && (
102-
<NavLink css={styles.link} to="/health">
103-
Health
104-
</NavLink>
105-
)}
10672
</nav>
10773
);
10874
};
@@ -157,12 +123,7 @@ export const NavbarView: FC<NavbarViewProps> = ({
157123
)}
158124
</div>
159125
</div>
160-
<NavItems
161-
canViewAuditLog={canViewAuditLog}
162-
canViewDeployment={canViewDeployment}
163-
canViewAllUsers={canViewAllUsers}
164-
canViewHealth={canViewHealth}
165-
/>
126+
<NavItems />
166127
</div>
167128
</Drawer>
168129

@@ -174,18 +135,20 @@ export const NavbarView: FC<NavbarViewProps> = ({
174135
)}
175136
</NavLink>
176137

177-
<NavItems
178-
css={styles.desktopNavItems}
179-
canViewAuditLog={canViewAuditLog}
180-
canViewDeployment={canViewDeployment}
181-
canViewAllUsers={canViewAllUsers}
182-
canViewHealth={canViewHealth}
183-
/>
138+
<NavItems css={styles.desktopNavItems} />
184139

185140
<div css={styles.navMenus}>
186141
{proxyContextValue && (
187142
<ProxyMenu proxyContextValue={proxyContextValue} />
188143
)}
144+
145+
<DeploymentDropdown
146+
canViewAuditLog={canViewAuditLog}
147+
canViewDeployment={canViewDeployment}
148+
canViewAllUsers={canViewAllUsers}
149+
canViewHealth={canViewHealth}
150+
/>
151+
189152
{user && (
190153
<UserDropdown
191154
user={user}
@@ -260,7 +223,6 @@ const ProxyMenu: FC<ProxyMenuProps> = ({ proxyContextValue }) => {
260223
size="small"
261224
endIcon={<KeyboardArrowDownOutlined />}
262225
css={{
263-
borderRadius: "999px",
264226
"& .MuiSvgIcon-root": { fontSize: 14 },
265227
}}
266228
>

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy