-
Notifications
You must be signed in to change notification settings - Fork 925
feat(site): implement notification ui #14175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
fc282e7
997e0d3
73f8471
33c9ab0
bbc8cbb
7227a42
788de88
1262f7b
e449e3d
0154b94
aedf159
1a2efae
a1f363c
7412eb9
1ff0973
2acde04
7cc7bdb
4956409
1c62242
a020619
0efc40d
1aedb92
786b005
2ecbe5f
ec7ab40
c986e51
e037423
341f550
d97dd82
4399cd6
d403eed
fb02aec
013ccff
7858a5a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,4 @@ | ||
import type { Interpolation, Theme } from "@emotion/react"; | ||
import AlertTitle from "@mui/material/AlertTitle"; | ||
import Button from "@mui/material/Button"; | ||
import Card from "@mui/material/Card"; | ||
import Divider from "@mui/material/Divider"; | ||
import List from "@mui/material/List"; | ||
|
@@ -11,25 +9,29 @@ import ToggleButtonGroup from "@mui/material/ToggleButtonGroup"; | |
import Tooltip from "@mui/material/Tooltip"; | ||
import { Fragment, type FC } from "react"; | ||
import { useMutation, useQueries, useQueryClient } from "react-query"; | ||
import { useSearchParams } from "react-router-dom"; | ||
import { | ||
notificationDispatchMethods, | ||
selectTemplatesByGroup, | ||
systemNotificationTemplates, | ||
updateNotificationTemplateMethod, | ||
} from "api/queries/notifications"; | ||
import { Alert, AlertDetail } from "components/Alert/Alert"; | ||
import type { NotificationsConfig } from "api/typesGenerated"; | ||
import { Alert } from "components/Alert/Alert"; | ||
import { displaySuccess } from "components/GlobalSnackbar/utils"; | ||
import { Loader } from "components/Loader/Loader"; | ||
import { Stack } from "components/Stack/Stack"; | ||
import { useClipboard } from "hooks"; | ||
import { TabLink, Tabs, TabsList } from "components/Tabs/Tabs"; | ||
import { | ||
castNotificationMethod, | ||
methodIcons, | ||
methodLabels, | ||
type NotificationMethod, | ||
} from "modules/notifications/utils"; | ||
import { Section } from "pages/UserSettingsPage/Section"; | ||
import { deploymentGroupHasParent } from "utils/deployOptions"; | ||
import { useDeploySettings } from "../DeploySettingsLayout"; | ||
import OptionsTable from "../OptionsTable"; | ||
|
||
type MethodToggleGroupProps = { | ||
templateId: string; | ||
|
@@ -89,6 +91,7 @@ const MethodToggleGroup: FC<MethodToggleGroupProps> = ({ | |
}; | ||
|
||
export const NotificationsPage: FC = () => { | ||
const [searchParams] = useSearchParams(); | ||
const { deploymentValues } = useDeploySettings(); | ||
const [templatesByGroup, dispatchMethods] = useQueries({ | ||
queries: [ | ||
|
@@ -99,104 +102,120 @@ export const NotificationsPage: FC = () => { | |
notificationDispatchMethods(), | ||
], | ||
}); | ||
const ready = templatesByGroup.data && dispatchMethods.data; | ||
|
||
const isUsingWebhook = dispatchMethods.data?.available.includes("webhook"); | ||
const webhookEndpoint = | ||
deploymentValues?.config.notifications?.webhook.endpoint; | ||
const ready = | ||
templatesByGroup.data && dispatchMethods.data && deploymentValues; | ||
const tab = searchParams.get("tab") || "events"; | ||
|
||
return ( | ||
BrunoQuaresma marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<Section | ||
title="Notifications" | ||
description="Control delivery methods for notifications. Settings applied to this deployment." | ||
layout="fluid" | ||
> | ||
{ready ? ( | ||
<Stack spacing={3}> | ||
{isUsingWebhook && | ||
(webhookEndpoint ? ( | ||
<WebhookInfo endpoint={webhookEndpoint} /> | ||
) : ( | ||
<Alert severity="warning"> | ||
Webhook method is enabled, but the endpoint is not configured. | ||
</Alert> | ||
))} | ||
{Object.entries(templatesByGroup.data).map(([group, templates]) => ( | ||
<Card | ||
key={group} | ||
variant="outlined" | ||
css={{ background: "transparent", width: "100%" }} | ||
> | ||
<List> | ||
<ListItem css={styles.listHeader}> | ||
<ListItemText css={styles.listItemText} primary={group} /> | ||
</ListItem> | ||
<Tabs active={tab}> | ||
<TabsList> | ||
<TabLink to="?tab=events" value="events"> | ||
Events | ||
</TabLink> | ||
<TabLink to="?tab=settings" value="settings"> | ||
Settings | ||
</TabLink> | ||
</TabsList> | ||
</Tabs> | ||
|
||
{templates.map((tpl) => { | ||
const value = castNotificationMethod( | ||
tpl.method || dispatchMethods.data.default, | ||
); | ||
const options = dispatchMethods.data.available.map( | ||
castNotificationMethod, | ||
); | ||
|
||
return ( | ||
<Fragment key={tpl.id}> | ||
<ListItem> | ||
<ListItemText | ||
css={styles.listItemText} | ||
primary={tpl.name} | ||
/> | ||
<MethodToggleGroup | ||
templateId={tpl.id} | ||
options={options} | ||
value={value} | ||
/> | ||
</ListItem> | ||
<Divider css={styles.divider} /> | ||
</Fragment> | ||
); | ||
})} | ||
</List> | ||
</Card> | ||
))} | ||
</Stack> | ||
) : ( | ||
<Loader /> | ||
)} | ||
<div css={styles.content}> | ||
{ready ? ( | ||
tab === "events" ? ( | ||
<EventsView | ||
defaultMethod={castNotificationMethod( | ||
dispatchMethods.data.default, | ||
)} | ||
availableMethods={dispatchMethods.data.available.map( | ||
castNotificationMethod, | ||
)} | ||
notificationsConfig={deploymentValues.config.notifications} | ||
templatesByGroup={templatesByGroup.data} | ||
/> | ||
) : ( | ||
<OptionsTable | ||
options={deploymentValues?.options.filter((o) => | ||
deploymentGroupHasParent(o.group, "Notifications"), | ||
)} | ||
/> | ||
) | ||
) : ( | ||
<Loader /> | ||
)} | ||
</div> | ||
</Section> | ||
); | ||
}; | ||
|
||
export default NotificationsPage; | ||
|
||
type WebhookInfoProps = { | ||
endpoint: string; | ||
type EventsViewProps = { | ||
defaultMethod: NotificationMethod; | ||
availableMethods: NotificationMethod[]; | ||
notificationsConfig?: NotificationsConfig; | ||
templatesByGroup: ReturnType<typeof selectTemplatesByGroup>; | ||
}; | ||
|
||
const WebhookInfo = ({ endpoint }: WebhookInfoProps) => { | ||
const clipboard = useClipboard({ textToCopy: endpoint }); | ||
const EventsView: FC<EventsViewProps> = ({ | ||
defaultMethod, | ||
availableMethods, | ||
notificationsConfig, | ||
templatesByGroup, | ||
}) => { | ||
const isUsingWebhook = availableMethods.includes("webhook"); | ||
const webhookEndpoint = notificationsConfig?.webhook.endpoint; | ||
|
||
return ( | ||
<Alert | ||
severity="info" | ||
actions={ | ||
<Button | ||
variant="text" | ||
onClick={clipboard.copyToClipboard} | ||
disabled={clipboard.showCopiedSuccess} | ||
<Stack spacing={3}> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the tables could use a little more spacing between them. They have the borders, so there's no real risk of them getting confused for each other, but they feel a little tense right now |
||
{isUsingWebhook && !webhookEndpoint && ( | ||
<Alert severity="warning"> | ||
Webhook method is enabled, but the endpoint is not configured. | ||
</Alert> | ||
)} | ||
BrunoQuaresma marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{Object.entries(templatesByGroup).map(([group, templates]) => ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this have a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Going to implement this in the |
||
<Card | ||
key={group} | ||
variant="outlined" | ||
css={{ background: "transparent", width: "100%" }} | ||
> | ||
{clipboard.showCopiedSuccess ? "Copied!" : "Copy"} | ||
</Button> | ||
} | ||
> | ||
<AlertTitle>Webhook Endpoint</AlertTitle> | ||
<AlertDetail>{endpoint}</AlertDetail> | ||
</Alert> | ||
<List> | ||
<ListItem css={styles.listHeader}> | ||
<ListItemText css={styles.listItemText} primary={group} /> | ||
</ListItem> | ||
|
||
{templates.map((tpl) => { | ||
const value = castNotificationMethod(tpl.method || defaultMethod); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just making sure: this is basically a stopgap, since the more specific types aren't being auto-generated just yet, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes! |
||
|
||
return ( | ||
<Fragment key={tpl.id}> | ||
<ListItem> | ||
<ListItemText | ||
css={styles.listItemText} | ||
primary={tpl.name} | ||
/> | ||
<MethodToggleGroup | ||
templateId={tpl.id} | ||
options={availableMethods} | ||
value={value} | ||
/> | ||
</ListItem> | ||
<Divider css={styles.divider} /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a big deal, but could this changed so that we check the array index and conditionally choose not to render out the divider if it's the last one? I know we have the |
||
</Fragment> | ||
); | ||
})} | ||
</List> | ||
</Card> | ||
))} | ||
</Stack> | ||
); | ||
}; | ||
|
||
export default NotificationsPage; | ||
|
||
const styles = { | ||
content: { paddingTop: 24 }, | ||
listHeader: (theme) => ({ | ||
background: theme.palette.background.paper, | ||
borderBottom: `1px solid ${theme.palette.divider}`, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think this might be a good use case for
useSearchParamsKey
? It'd have to be changed a little bit to account for the||
instead of the??
, thoughUh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it would make too much difference since I'm using regukar links to update the search params. I'm using
||
instead of??
to grab empty strings as well.