Merge pull request #29301 from billyatroadie/add-topics-to-notification-settings
Adds ability for user to turn on/off notifications for specific topics within an origin.
This commit is contained in:
@@ -207,6 +207,7 @@ export function useNotificationsApi<T>(
|
||||
// @public (undocumented)
|
||||
export const UserNotificationSettingsCard: (props: {
|
||||
originNames?: Record<string, string>;
|
||||
topicNames?: Record<string, string>;
|
||||
}) => JSX_2.Element;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import MuiTableCell from '@material-ui/core/TableCell';
|
||||
|
||||
export const NoBorderTableCell = withStyles({
|
||||
root: {
|
||||
borderBottom: 'none',
|
||||
},
|
||||
})(MuiTableCell);
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
ChannelSetting,
|
||||
isNotificationsEnabledFor,
|
||||
NotificationSettings,
|
||||
OriginSetting,
|
||||
} from '@backstage/plugin-notifications-common';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Switch from '@material-ui/core/Switch';
|
||||
import TableRow from '@material-ui/core/TableRow';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
|
||||
import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp';
|
||||
import { NoBorderTableCell } from './NoBorderTableCell';
|
||||
import { useNotificationFormat } from './UserNotificationSettingsCard';
|
||||
|
||||
export const OriginRow = (props: {
|
||||
channel: ChannelSetting;
|
||||
origin: OriginSetting;
|
||||
settings: NotificationSettings;
|
||||
handleChange: (
|
||||
channel: string,
|
||||
origin: string,
|
||||
topic: string | null,
|
||||
enabled: boolean,
|
||||
) => void;
|
||||
open: boolean;
|
||||
handleRowToggle: (originId: string) => void;
|
||||
}) => {
|
||||
const { channel, origin, settings, handleChange, open, handleRowToggle } =
|
||||
props;
|
||||
const { formatOriginName } = useNotificationFormat();
|
||||
return (
|
||||
<TableRow>
|
||||
<NoBorderTableCell>
|
||||
{origin.topics && origin.topics.length > 0 && (
|
||||
<Tooltip
|
||||
title={`Show Topics for the ${formatOriginName(origin.id)} origin`}
|
||||
>
|
||||
<IconButton
|
||||
aria-label="expand row"
|
||||
size="small"
|
||||
onClick={() => handleRowToggle(origin.id)}
|
||||
>
|
||||
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</NoBorderTableCell>
|
||||
<NoBorderTableCell>{formatOriginName(origin.id)}</NoBorderTableCell>
|
||||
<NoBorderTableCell>all</NoBorderTableCell>
|
||||
{settings.channels.map(ch => (
|
||||
<NoBorderTableCell key={ch.id} align="center">
|
||||
<Tooltip
|
||||
title={`Enable or disable ${channel.id.toLocaleLowerCase(
|
||||
'en-US',
|
||||
)} notifications from ${formatOriginName(origin.id)}`}
|
||||
>
|
||||
<Switch
|
||||
checked={isNotificationsEnabledFor(
|
||||
settings,
|
||||
ch.id,
|
||||
origin.id,
|
||||
null,
|
||||
)}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
handleChange(ch.id, origin.id, null, event.target.checked);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</NoBorderTableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
isNotificationsEnabledFor,
|
||||
NotificationSettings,
|
||||
OriginSetting,
|
||||
TopicSetting,
|
||||
} from '@backstage/plugin-notifications-common';
|
||||
import TableRow from '@material-ui/core/TableRow';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import Switch from '@material-ui/core/Switch';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { NoBorderTableCell } from './NoBorderTableCell';
|
||||
import { useNotificationFormat } from './UserNotificationSettingsCard';
|
||||
|
||||
const TopicTableRow = withStyles({
|
||||
root: {
|
||||
paddingLeft: '4px',
|
||||
},
|
||||
})(TableRow);
|
||||
|
||||
export const TopicRow = (props: {
|
||||
topic: TopicSetting;
|
||||
origin: OriginSetting;
|
||||
settings: NotificationSettings;
|
||||
handleChange: (
|
||||
channel: string,
|
||||
origin: string,
|
||||
topic: string | null,
|
||||
enabled: boolean,
|
||||
) => void;
|
||||
}) => {
|
||||
const { topic, origin, settings, handleChange } = props;
|
||||
const { formatOriginName, formatTopicName } = useNotificationFormat();
|
||||
return (
|
||||
<TopicTableRow>
|
||||
<NoBorderTableCell />
|
||||
<NoBorderTableCell />
|
||||
<NoBorderTableCell>{formatTopicName(topic.id)}</NoBorderTableCell>
|
||||
{settings.channels.map(ch => (
|
||||
<NoBorderTableCell key={`${ch.id}-${topic}`} align="center">
|
||||
<Tooltip
|
||||
title={`Enable or disable ${ch.id.toLocaleLowerCase(
|
||||
'en-US',
|
||||
)} notifications for the ${formatTopicName(
|
||||
topic.id,
|
||||
)} topic from ${formatOriginName(origin.id)}`}
|
||||
>
|
||||
<Switch
|
||||
checked={isNotificationsEnabledFor(
|
||||
settings,
|
||||
ch.id,
|
||||
origin.id,
|
||||
topic.id,
|
||||
)}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
handleChange(ch.id, origin.id, topic.id, event.target.checked);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</NoBorderTableCell>
|
||||
))}
|
||||
</TopicTableRow>
|
||||
);
|
||||
};
|
||||
+67
-6
@@ -14,17 +14,74 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { createContext, useState, useContext, useEffect } from 'react';
|
||||
import { ErrorPanel, InfoCard, Progress } from '@backstage/core-components';
|
||||
import { useNotificationsApi } from '../../hooks';
|
||||
import { NotificationSettings } from '@backstage/plugin-notifications-common';
|
||||
import { notificationsApiRef } from '../../api';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { UserNotificationSettingsPanel } from './UserNotificationSettingsPanel';
|
||||
import { capitalize } from 'lodash';
|
||||
|
||||
type FormatContextType = {
|
||||
formatOriginName: (id: string) => string;
|
||||
formatTopicName: (id: string) => string;
|
||||
};
|
||||
|
||||
const NotificationFormatContext = createContext<FormatContextType | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export const useNotificationFormat = () => {
|
||||
const context = useContext(NotificationFormatContext);
|
||||
if (!context)
|
||||
throw new Error(
|
||||
'useNotificationFormat must be used within a NotificationFormatProvider',
|
||||
);
|
||||
return context;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
originMap: Record<string, string> | undefined;
|
||||
topicMap: Record<string, string> | undefined;
|
||||
};
|
||||
|
||||
export const NotificationFormatProvider = ({
|
||||
children,
|
||||
originMap,
|
||||
topicMap,
|
||||
}: Props) => {
|
||||
const formatName = (
|
||||
id: string,
|
||||
nameMap: Record<string, string> | undefined,
|
||||
) => {
|
||||
if (nameMap && id in nameMap) {
|
||||
return nameMap[id];
|
||||
}
|
||||
return capitalize(id.replaceAll(/[-_:]/g, ' '));
|
||||
};
|
||||
|
||||
const formatOriginName = (originId: string) => {
|
||||
return formatName(originId, originMap);
|
||||
};
|
||||
|
||||
const formatTopicName = (topicId: string) => {
|
||||
return formatName(topicId, topicMap);
|
||||
};
|
||||
return (
|
||||
<NotificationFormatContext.Provider
|
||||
value={{ formatOriginName, formatTopicName }}
|
||||
>
|
||||
{children}
|
||||
</NotificationFormatContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const UserNotificationSettingsCard = (props: {
|
||||
originNames?: Record<string, string>;
|
||||
topicNames?: Record<string, string>;
|
||||
}) => {
|
||||
const [settings, setNotificationSettings] = useState<
|
||||
NotificationSettings | undefined
|
||||
@@ -52,11 +109,15 @@ export const UserNotificationSettingsCard = (props: {
|
||||
{loading && <Progress />}
|
||||
{error && <ErrorPanel title="Failed to load settings" error={error} />}
|
||||
{settings && (
|
||||
<UserNotificationSettingsPanel
|
||||
settings={settings}
|
||||
onChange={onUpdate}
|
||||
originNames={props.originNames}
|
||||
/>
|
||||
<NotificationFormatProvider
|
||||
originMap={props.originNames}
|
||||
topicMap={props.topicNames}
|
||||
>
|
||||
<UserNotificationSettingsPanel
|
||||
settings={settings}
|
||||
onChange={onUpdate}
|
||||
/>
|
||||
</NotificationFormatProvider>
|
||||
)}
|
||||
</InfoCard>
|
||||
);
|
||||
|
||||
+72
-52
@@ -14,11 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ChangeEvent } from 'react';
|
||||
import {
|
||||
isNotificationsEnabledFor,
|
||||
NotificationSettings,
|
||||
} from '@backstage/plugin-notifications-common';
|
||||
import { useState } from 'react';
|
||||
import { NotificationSettings } from '@backstage/plugin-notifications-common';
|
||||
import Table from '@material-ui/core/Table';
|
||||
import MuiTableCell from '@material-ui/core/TableCell';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
@@ -26,9 +23,8 @@ import TableHead from '@material-ui/core/TableHead';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import TableBody from '@material-ui/core/TableBody';
|
||||
import TableRow from '@material-ui/core/TableRow';
|
||||
import Switch from '@material-ui/core/Switch';
|
||||
import { capitalize } from 'lodash';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import { TopicRow } from './TopicRow';
|
||||
import { OriginRow } from './OriginRow';
|
||||
|
||||
const TableCell = withStyles({
|
||||
root: {
|
||||
@@ -40,19 +36,26 @@ export const UserNotificationSettingsPanel = (props: {
|
||||
settings: NotificationSettings;
|
||||
onChange: (settings: NotificationSettings) => void;
|
||||
originNames?: Record<string, string>;
|
||||
topicNames?: Record<string, string>;
|
||||
}) => {
|
||||
const { settings, onChange } = props;
|
||||
const allOrigins = [
|
||||
...new Set(
|
||||
settings.channels.flatMap(channel =>
|
||||
channel.origins.map(origin => origin.id),
|
||||
),
|
||||
),
|
||||
];
|
||||
const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set());
|
||||
|
||||
const handleRowToggle = (originId: string) => {
|
||||
setExpandedRows(prevState => {
|
||||
const newExpandedRows = new Set(prevState);
|
||||
if (newExpandedRows.has(originId)) {
|
||||
newExpandedRows.delete(originId);
|
||||
} else {
|
||||
newExpandedRows.add(originId);
|
||||
}
|
||||
return newExpandedRows;
|
||||
});
|
||||
};
|
||||
const handleChange = (
|
||||
channelId: string,
|
||||
originId: string,
|
||||
topicId: string | null,
|
||||
enabled: boolean,
|
||||
) => {
|
||||
const updatedSettings = {
|
||||
@@ -66,9 +69,30 @@ export const UserNotificationSettingsPanel = (props: {
|
||||
if (origin.id !== originId) {
|
||||
return origin;
|
||||
}
|
||||
|
||||
if (topicId === null) {
|
||||
return {
|
||||
...origin,
|
||||
enabled,
|
||||
topics:
|
||||
origin.topics?.map(topic => {
|
||||
return { ...topic, enabled };
|
||||
}) ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...origin,
|
||||
enabled,
|
||||
topics:
|
||||
origin.topics?.map(topic => {
|
||||
if (topic.id === topicId) {
|
||||
return {
|
||||
...topic,
|
||||
enabled: origin.enabled ? enabled : origin.enabled,
|
||||
};
|
||||
}
|
||||
return topic;
|
||||
}) ?? [],
|
||||
};
|
||||
}),
|
||||
};
|
||||
@@ -77,14 +101,7 @@ export const UserNotificationSettingsPanel = (props: {
|
||||
onChange(updatedSettings);
|
||||
};
|
||||
|
||||
const formatOriginName = (originId: string) => {
|
||||
if (props.originNames && originId in props.originNames) {
|
||||
return props.originNames[originId];
|
||||
}
|
||||
return capitalize(originId.replaceAll(/[_:]/g, ' '));
|
||||
};
|
||||
|
||||
if (settings.channels.length === 0 || allOrigins.length === 0) {
|
||||
if (settings.channels.length === 0) {
|
||||
return (
|
||||
<Typography variant="body1">
|
||||
No notification settings available, check back later
|
||||
@@ -96,44 +113,47 @@ export const UserNotificationSettingsPanel = (props: {
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell />
|
||||
<TableCell>
|
||||
<Typography variant="subtitle1">Origin</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="subtitle1">Topic</Typography>
|
||||
</TableCell>
|
||||
{settings.channels.map(channel => (
|
||||
<TableCell>
|
||||
<Typography variant="subtitle1">{channel.id}</Typography>
|
||||
<TableCell key={channel.id}>
|
||||
<Typography variant="subtitle1" align="center">
|
||||
{channel.id}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{allOrigins.map(origin => (
|
||||
<TableRow>
|
||||
<TableCell>{formatOriginName(origin)}</TableCell>
|
||||
{settings.channels.map(channel => (
|
||||
<TableCell>
|
||||
<Tooltip
|
||||
title={`Enable or disable ${channel.id.toLocaleLowerCase(
|
||||
'en-US',
|
||||
)} notifications from ${formatOriginName(
|
||||
origin,
|
||||
).toLocaleLowerCase('en-US')}`}
|
||||
>
|
||||
<Switch
|
||||
checked={isNotificationsEnabledFor(
|
||||
settings,
|
||||
channel.id,
|
||||
origin,
|
||||
)}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
handleChange(channel.id, origin, event.target.checked);
|
||||
}}
|
||||
{settings.channels.map(channel =>
|
||||
channel.origins.flatMap(origin => [
|
||||
<OriginRow
|
||||
key={origin.id}
|
||||
channel={channel}
|
||||
origin={origin}
|
||||
settings={settings}
|
||||
open={expandedRows.has(origin.id)}
|
||||
handleChange={handleChange}
|
||||
handleRowToggle={handleRowToggle}
|
||||
/>,
|
||||
...(expandedRows.has(origin.id)
|
||||
? origin.topics?.map(topic => (
|
||||
<TopicRow
|
||||
key={`${origin.id}-${topic.id}`}
|
||||
topic={topic}
|
||||
origin={origin}
|
||||
settings={settings}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
)) || []
|
||||
: []),
|
||||
]),
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user