rm NODE_OPTIONS;
Adds topicNames prop Adds collapsible row for topics Refactor getNotificationSettings for readability Signed-off-by: Billy Stalnaker <bstalnaker@roadie.com>
This commit is contained in:
+1
-1
@@ -33,7 +33,7 @@
|
||||
"build:plugins-report": "node ./scripts/build-plugins-report",
|
||||
"clean": "backstage-cli repo clean",
|
||||
"create-plugin": "echo \"use 'yarn new' instead\"",
|
||||
"dev": "NODE_OPTIONS=--no-node-snapshot yarn workspaces foreach -A --include example-backend --include example-app --parallel --jobs unlimited -v -i run start",
|
||||
"dev": "yarn workspaces foreach -A --include example-backend --include example-app --parallel --jobs unlimited -v -i run start",
|
||||
"dev:next": "yarn workspaces foreach -A --include example-backend --include example-app-next --parallel --jobs unlimited -v -i run start",
|
||||
"docker-build": "yarn tsc && yarn workspace example-backend build && yarn workspace example-backend build-image",
|
||||
"fix": "backstage-cli repo fix --publish",
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { SignalsService } from '@backstage/plugin-signals-node';
|
||||
import {
|
||||
ChannelSetting,
|
||||
isNotificationsEnabledFor,
|
||||
NewNotificationSignal,
|
||||
Notification,
|
||||
@@ -45,6 +46,7 @@ import {
|
||||
NotificationSettings,
|
||||
notificationSeverities,
|
||||
NotificationStatus,
|
||||
OriginSetting,
|
||||
} from '@backstage/plugin-notifications-common';
|
||||
import { parseEntityOrderFieldParams } from './parseEntityOrderFieldParams';
|
||||
import { getUsersForEntityRef } from './getUsersForEntityRef';
|
||||
@@ -89,52 +91,68 @@ export async function createRouter(
|
||||
return info.userEntityRef;
|
||||
};
|
||||
|
||||
const getTopicSettings = (
|
||||
topic: any,
|
||||
existingOrigin: OriginSetting | undefined,
|
||||
defaultEnabled: boolean,
|
||||
) => {
|
||||
const existingTopic = existingOrigin?.topics?.find(
|
||||
t => t.id === topic.topic,
|
||||
);
|
||||
return {
|
||||
id: topic.topic,
|
||||
enabled: existingTopic ? existingTopic.enabled : defaultEnabled,
|
||||
};
|
||||
};
|
||||
|
||||
const getOriginSettings = (
|
||||
originId: string,
|
||||
existingChannel: ChannelSetting | undefined,
|
||||
topics: { origin: string; topic: string }[],
|
||||
) => {
|
||||
const existingOrigin = existingChannel?.origins.find(
|
||||
o => o.id === originId,
|
||||
);
|
||||
const defaultEnabled = existingOrigin ? existingOrigin.enabled : true;
|
||||
return {
|
||||
id: originId,
|
||||
enabled: defaultEnabled,
|
||||
topics: topics
|
||||
.filter(t => t.origin === originId)
|
||||
.map(t => getTopicSettings(t, existingOrigin, defaultEnabled)),
|
||||
};
|
||||
};
|
||||
|
||||
const getNotificationChannels = () => {
|
||||
return [WEB_NOTIFICATION_CHANNEL, ...processors.map(p => p.getName())];
|
||||
};
|
||||
|
||||
const getChannelSettings = (
|
||||
channelId: string,
|
||||
settings: NotificationSettings,
|
||||
origins: string[],
|
||||
topics: { origin: string; topic: string }[],
|
||||
) => {
|
||||
const existingChannel = settings.channels.find(c => c.id === channelId);
|
||||
return {
|
||||
id: channelId,
|
||||
origins: origins.map(originId =>
|
||||
getOriginSettings(originId, existingChannel, topics),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const getNotificationSettings = async (user: string) => {
|
||||
const { origins } = await store.getUserNotificationOrigins({ user });
|
||||
const { topics } = await store.getUserNotificationTopics({ user });
|
||||
const settings = await store.getNotificationSettings({ user });
|
||||
const channels = getNotificationChannels();
|
||||
|
||||
const response: NotificationSettings = {
|
||||
channels: channels.map(channelId => {
|
||||
return {
|
||||
id: channelId,
|
||||
origins: origins.map(originId => {
|
||||
const existingChannel = settings.channels.find(
|
||||
c => c.id === channelId,
|
||||
);
|
||||
const existingOrigin = existingChannel?.origins.find(
|
||||
o => o.id === originId,
|
||||
);
|
||||
const defaultEnabled = existingOrigin
|
||||
? existingOrigin.enabled
|
||||
: true;
|
||||
return {
|
||||
id: originId,
|
||||
enabled: defaultEnabled,
|
||||
topics: topics
|
||||
.filter(t => t.origin === originId)
|
||||
.map(t => {
|
||||
const existingTopic =
|
||||
existingOrigin?.topics &&
|
||||
existingOrigin.topics.find(topic => topic.id === t.topic);
|
||||
return {
|
||||
id: t.topic,
|
||||
enabled: existingTopic
|
||||
? existingTopic.enabled
|
||||
: defaultEnabled,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}),
|
||||
return {
|
||||
channels: channels.map(channelId =>
|
||||
getChannelSettings(channelId, settings, origins, topics),
|
||||
),
|
||||
};
|
||||
return response;
|
||||
};
|
||||
|
||||
const isNotificationsEnabled = async (opts: {
|
||||
|
||||
@@ -129,16 +129,31 @@ export type NotificationProcessorFilters = {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type NotificationSettings = {
|
||||
channels: {
|
||||
id: string;
|
||||
origins: {
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
topics?: {
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
}[];
|
||||
}[];
|
||||
}[];
|
||||
export type TopicSetting = {
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type OriginSetting = {
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
topics?: TopicSetting[];
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type ChannelSetting = {
|
||||
id: string;
|
||||
origins: OriginSetting[];
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type NotificationSettings = {
|
||||
channels: ChannelSetting[];
|
||||
};
|
||||
|
||||
+2
@@ -25,6 +25,7 @@ import { UserNotificationSettingsPanel } from './UserNotificationSettingsPanel';
|
||||
/** @public */
|
||||
export const UserNotificationSettingsCard = (props: {
|
||||
originNames?: Record<string, string>;
|
||||
topicNames?: Record<string, string>;
|
||||
}) => {
|
||||
const [settings, setNotificationSettings] = React.useState<
|
||||
NotificationSettings | undefined
|
||||
@@ -56,6 +57,7 @@ export const UserNotificationSettingsCard = (props: {
|
||||
settings={settings}
|
||||
onChange={onUpdate}
|
||||
originNames={props.originNames}
|
||||
topicNames={props.topicNames}
|
||||
/>
|
||||
)}
|
||||
</InfoCard>
|
||||
|
||||
+99
-54
@@ -14,15 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
isNotificationsEnabledFor,
|
||||
NotificationSettings,
|
||||
} from '@backstage/plugin-notifications-common';
|
||||
import ChevronRight from '@material-ui/icons/ChevronRight';
|
||||
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
|
||||
import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp';
|
||||
import Table from '@material-ui/core/Table';
|
||||
import MuiTableCell from '@material-ui/core/TableCell';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import TableHead from '@material-ui/core/TableHead';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import TableBody from '@material-ui/core/TableBody';
|
||||
@@ -37,13 +39,6 @@ const TableCell = withStyles({
|
||||
},
|
||||
})(MuiTableCell);
|
||||
|
||||
const CenterContentsTableCell = withStyles({
|
||||
root: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
})(MuiTableCell);
|
||||
|
||||
const TopicTableRow = withStyles({
|
||||
root: {
|
||||
paddingLeft: '4px',
|
||||
@@ -54,8 +49,22 @@ export const UserNotificationSettingsPanel = (props: {
|
||||
settings: NotificationSettings;
|
||||
onChange: (settings: NotificationSettings) => void;
|
||||
originNames?: Record<string, string>;
|
||||
topicNames?: Record<string, string>;
|
||||
}) => {
|
||||
const { settings, onChange } = props;
|
||||
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,
|
||||
@@ -104,12 +113,22 @@ export const UserNotificationSettingsPanel = (props: {
|
||||
};
|
||||
onChange(updatedSettings);
|
||||
};
|
||||
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) => {
|
||||
if (props.originNames && originId in props.originNames) {
|
||||
return props.originNames[originId];
|
||||
}
|
||||
return capitalize(originId.replaceAll(/[_:]/g, ' '));
|
||||
return formatName(originId, props.originNames);
|
||||
};
|
||||
|
||||
const formatTopicName = (topicId: string) => {
|
||||
return formatName(topicId, props.topicNames);
|
||||
};
|
||||
|
||||
if (settings.channels.length === 0) {
|
||||
@@ -124,12 +143,15 @@ export const UserNotificationSettingsPanel = (props: {
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<CenterContentsTableCell>
|
||||
<Typography variant="subtitle1">Origin</Typography> <ChevronRight />{' '}
|
||||
<TableCell />
|
||||
<TableCell>
|
||||
<Typography variant="subtitle1">Origin</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="subtitle1">Topic</Typography>
|
||||
</CenterContentsTableCell>
|
||||
</TableCell>
|
||||
{settings.channels.map(channel => (
|
||||
<TableCell>
|
||||
<TableCell key={channel.id}>
|
||||
<Typography variant="subtitle1" align="center">
|
||||
{channel.id}
|
||||
</Typography>
|
||||
@@ -141,7 +163,29 @@ export const UserNotificationSettingsPanel = (props: {
|
||||
{settings.channels.map(channel =>
|
||||
channel.origins.flatMap(origin => [
|
||||
<TableRow key={`${channel.id}-${origin.id}`}>
|
||||
<TableCell>
|
||||
{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)}
|
||||
>
|
||||
{expandedRows.has(origin.id) ? (
|
||||
<KeyboardArrowUpIcon />
|
||||
) : (
|
||||
<KeyboardArrowDownIcon />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>{formatOriginName(origin.id)}</TableCell>
|
||||
<TableCell>*</TableCell>
|
||||
{settings.channels.map(ch => (
|
||||
<TableCell key={ch.id} align="center">
|
||||
<Tooltip
|
||||
@@ -171,44 +215,45 @@ export const UserNotificationSettingsPanel = (props: {
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>,
|
||||
...(origin.topics?.map(topic => (
|
||||
<TopicTableRow key={`${origin.id}-${topic.id}`}>
|
||||
<CenterContentsTableCell>
|
||||
<ChevronRight />
|
||||
{topic.id}
|
||||
</CenterContentsTableCell>
|
||||
{settings.channels.map(ch => (
|
||||
<TableCell key={`${ch.id}-${topic.id}`} align="center">
|
||||
<Tooltip
|
||||
title={`Enable or disable ${channel.id.toLocaleLowerCase(
|
||||
'en-US',
|
||||
)} notifications for the ${
|
||||
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,
|
||||
...(expandedRows.has(origin.id)
|
||||
? origin.topics?.map(topic => (
|
||||
<TopicTableRow key={`${origin.id}-${topic.id}`}>
|
||||
<TableCell />
|
||||
<TableCell />
|
||||
<TableCell>{formatTopicName(topic.id)}</TableCell>
|
||||
{settings.channels.map(ch => (
|
||||
<TableCell key={`${ch.id}-${topic.id}`} align="center">
|
||||
<Tooltip
|
||||
title={`Enable or disable ${channel.id.toLocaleLowerCase(
|
||||
'en-US',
|
||||
)} notifications for the ${formatTopicName(
|
||||
topic.id,
|
||||
event.target.checked,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
))}
|
||||
</TopicTableRow>
|
||||
)) || []),
|
||||
)} 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>
|
||||
</TableCell>
|
||||
))}
|
||||
</TopicTableRow>
|
||||
)) || []
|
||||
: []),
|
||||
]),
|
||||
)}
|
||||
</TableBody>
|
||||
|
||||
Reference in New Issue
Block a user