From 3e252af626771a74b16c1965c9d6a3ef7bcfef3f Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Fri, 8 May 2026 11:07:22 -0400 Subject: [PATCH 01/70] Rollup subscriptions by prefix for tabular display --- .../admin/Settings/PrefixAlerts/types.ts | 20 ++++- .../useAlertSubscriptionsStore.ts | 16 ++++ src/components/tables/PrefixAlerts/Rows.tsx | 73 +++++++++---------- src/components/tables/PrefixAlerts/index.tsx | 68 +++++++++++------ src/components/tables/PrefixAlerts/shared.ts | 4 - src/components/tables/PrefixAlerts/types.ts | 6 +- .../tables/cells/prefixAlerts/EditButton.tsx | 11 +-- src/utils/notification-utils.ts | 23 ++++++ 8 files changed, 139 insertions(+), 82 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/types.ts b/src/components/admin/Settings/PrefixAlerts/types.ts index 268e6595f8..8910f61f28 100644 --- a/src/components/admin/Settings/PrefixAlerts/types.ts +++ b/src/components/admin/Settings/PrefixAlerts/types.ts @@ -35,9 +35,8 @@ export interface DialogActionProps { } export interface EditButtonProps extends TableCellProps { - alertTypes: ReducedAlertSubscription['alertTypes']; - email: string; prefix: string; + subscriptionMetadata: SubscriptionMetadata; } export interface EmailDictionary { @@ -48,6 +47,23 @@ export interface EmailListFieldProps { staticEmail?: string; } +interface GlobalSetting { + [property: string]: boolean | number | string; +} + +interface GlobalSettingDictionary { + [alertType: string]: GlobalSetting; +} + export interface PrefixFieldProps { staticPrefix?: string; } + +export interface SubscriptionMetadata { + settings: GlobalSettingDictionary; + subscriptions: ReducedAlertSubscription[]; +} + +export interface SubscriptionMetadataDictionary { + [prefix: string]: SubscriptionMetadata; +} diff --git a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts index 79a7889e92..1878df0452 100644 --- a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts +++ b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts @@ -1,5 +1,6 @@ import type { PostgrestError } from '@supabase/postgrest-js'; import type { ReducedAlertSubscription } from 'src/api/types'; +import type { SubscriptionMetadataDictionary } from 'src/components/admin/Settings/PrefixAlerts/types'; import type { AlertTypeInfo } from 'src/gql-types/graphql'; import type { CombinedError } from 'urql'; @@ -8,6 +9,7 @@ import { devtools } from 'zustand/middleware'; import produce from 'immer'; +import { bundleSubscriptionsByPrefix } from 'src/utils/notification-utils'; import { devtoolsOptions } from 'src/utils/store-utils'; interface AlertSubscriptionState { @@ -20,6 +22,7 @@ interface AlertSubscriptionState { ReducedAlertSubscription, 'alertTypes' | 'catalogPrefix' | 'email' >; + subscriptionMetadata: SubscriptionMetadataDictionary; resetState: () => void; setAlertTypes: (values: AlertTypeInfo[], initialize?: boolean) => void; setEmailErrorsExist: ( @@ -31,6 +34,7 @@ interface AlertSubscriptionState { setSaveErrors: (value: AlertSubscriptionState['saveErrors']) => void; setSubscribedEmail: (value: string) => void; setSubscribedPrefix: (value: string, errors: string | null) => void; + setSubscriptionMetadata: (value: ReducedAlertSubscription[]) => void; } const getInitialState = (): Pick< @@ -41,6 +45,7 @@ const getInitialState = (): Pick< | 'prefixErrorsExist' | 'saveErrors' | 'subscription' + | 'subscriptionMetadata' > => ({ alertTypes: [], emailErrorsExist: false, @@ -48,6 +53,7 @@ const getInitialState = (): Pick< prefixErrorsExist: false, saveErrors: [], subscription: { alertTypes: [], catalogPrefix: '', email: '' }, + subscriptionMetadata: {}, }); const name = 'estuary.alert-subscriptions-store'; @@ -119,6 +125,16 @@ const useAlertSubscriptionsStore = create()( false, 'subscribed prefix set' ), + + setSubscriptionMetadata: (value) => + set( + produce((state: AlertSubscriptionState) => { + state.subscriptionMetadata = + bundleSubscriptionsByPrefix(value); + }), + false, + 'subscription metadata set' + ), }; }, devtoolsOptions(name)) ); diff --git a/src/components/tables/PrefixAlerts/Rows.tsx b/src/components/tables/PrefixAlerts/Rows.tsx index 38e0c368a3..f7d809faab 100644 --- a/src/components/tables/PrefixAlerts/Rows.tsx +++ b/src/components/tables/PrefixAlerts/Rows.tsx @@ -1,61 +1,56 @@ -import type { ChipDisplay } from 'src/components/shared/ChipList/types'; import type { RowProps, RowsProps, } from 'src/components/tables/PrefixAlerts/types'; -import { useMemo } from 'react'; - import { TableCell, TableRow } from '@mui/material'; import ChipListCell from 'src/components/tables/cells/ChipList'; import AlertEditButton from 'src/components/tables/cells/prefixAlerts/EditButton'; -import { sortByAlertType } from 'src/utils/misc-utils'; -function Row({ alertTypeDefs, row }: RowProps) { - const evaluatedAlertTypes: ChipDisplay[] = useMemo( - () => - row.alertTypes - .map((alertType) => - alertTypeDefs.find((def) => def.alertType === alertType) - ) - .filter((def) => typeof def !== 'undefined') - .sort((first, second) => - sortByAlertType( - { - isSystemAlert: first.isSystem, - value: first.displayName, - }, - { - isSystemAlert: second.isSystem, - value: second.displayName, - }, - 'asc' - ) - ) - .map(({ displayName, isSystem }) => ({ - display: displayName, - diminishedText: isSystem, - })), - [alertTypeDefs, row.alertTypes] - ); +function Row({ row }: RowProps) { + const { subscriptions } = row; + + // const evaluatedAlertTypes: ChipDisplay[] = useMemo( + // () => + // row.alertTypes + // .map((alertType) => + // alertTypeDefs.find((def) => def.alertType === alertType) + // ) + // .filter((def) => typeof def !== 'undefined') + // .sort((first, second) => + // sortByAlertType( + // { + // isSystemAlert: first.isSystem, + // value: first.displayName, + // }, + // { + // isSystemAlert: second.isSystem, + // value: second.displayName, + // }, + // 'asc' + // ) + // ) + // .map(({ displayName, isSystem }) => ({ + // display: displayName, + // diminishedText: isSystem, + // })), + // [alertTypeDefs, row.alertTypes] + // ); return ( - {row.catalogPrefix} + {subscriptions[0].catalogPrefix} email)} /> - {row.email} - ); @@ -67,7 +62,7 @@ function Rows({ alertTypeDefs, data }: RowsProps) { {data.map((datum, index) => ( ))} diff --git a/src/components/tables/PrefixAlerts/index.tsx b/src/components/tables/PrefixAlerts/index.tsx index a825ff4960..2c4b1efc37 100644 --- a/src/components/tables/PrefixAlerts/index.tsx +++ b/src/components/tables/PrefixAlerts/index.tsx @@ -1,4 +1,4 @@ -import type { ReducedAlertSubscriptionQueryResponse } from 'src/api/types'; +import type { SubscriptionMetadataDictionary } from 'src/components/admin/Settings/PrefixAlerts/types'; import type { AlertTypeInfo } from 'src/gql-types/graphql'; import type { TableState } from 'src/types'; @@ -6,6 +6,8 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import { Box, Stack, Table, TableContainer } from '@mui/material'; +import { useShallow } from 'zustand/react/shallow'; + import { debounce } from 'lodash'; import { useUnmount } from 'react-use'; @@ -23,34 +25,45 @@ import TableFilter from 'src/components/tables/PrefixAlerts/TableFilter'; import { useGetAlertSubscriptions } from 'src/context/AlertSubscriptions'; import { useGetAlertTypes } from 'src/context/AlertType'; import { TableStatuses } from 'src/types'; +import { bundleSubscriptionsByPrefix } from 'src/utils/notification-utils'; function PrefixAlertTable() { const [{ data, error, fetching }] = useGetAlertSubscriptions(); const [alertTypeResponse] = useGetAlertTypes(); - const setInitializationError = useAlertSubscriptionsStore( - (state) => state.setInitializationError - ); + const [setInitializationError, setSubscriptionMetadata] = + useAlertSubscriptionsStore( + useShallow((state) => [ + state.setInitializationError, + state.setSubscriptionMetadata, + ]) + ); const [searchQuery, setSearchQuery] = useState(''); const [tableState, setTableState] = useState({ status: TableStatuses.LOADING, }); - const processedData: ReducedAlertSubscriptionQueryResponse['alertSubscriptions'] = - useMemo(() => { - if (!data) { - return []; - } - - return searchQuery - ? data.alertSubscriptions.filter( - ({ catalogPrefix, email }) => - catalogPrefix.includes(searchQuery) || - email.includes(searchQuery) - ) - : data.alertSubscriptions; - }, [data, searchQuery]); + const processedData: SubscriptionMetadataDictionary = useMemo(() => { + if (!data) { + return {}; + } + + const evaluatedData = searchQuery + ? data.alertSubscriptions.filter( + ({ catalogPrefix, email }) => + catalogPrefix.includes(searchQuery) || + email.includes(searchQuery) + ) + : data.alertSubscriptions; + + return bundleSubscriptionsByPrefix(evaluatedData); + }, [data, searchQuery]); + + const processedDataExists = useMemo( + () => Object.keys(processedData).length > 0, + [processedData] + ); const alertTypeDefs: AlertTypeInfo[] = useMemo( () => @@ -71,13 +84,21 @@ function PrefixAlertTable() { useEffect(() => { if (!fetching && !alertTypeResponse.fetching) { setInitializationError(error); + setSubscriptionMetadata(data?.alertSubscriptions ?? []); } - }, [alertTypeResponse.fetching, error, fetching, setInitializationError]); + }, [ + alertTypeResponse.fetching, + data?.alertSubscriptions, + error, + fetching, + setInitializationError, + setSubscriptionMetadata, + ]); useEffect(() => { if (fetching || alertTypeResponse.fetching) { setTableState({ status: TableStatuses.LOADING }); - } else if (processedData.length > 0) { + } else if (processedDataExists) { displayLoadingState.current?.cancel(); setTableState({ @@ -94,8 +115,8 @@ function PrefixAlertTable() { } }, [ alertTypeResponse.fetching, + processedDataExists, fetching, - processedData.length, searchQuery, ]); @@ -144,11 +165,10 @@ function PrefixAlertTable() { disableDoclink: true, }} rows={ - processedData.length > 0 && - alertTypeDefs.length > 0 ? ( + processedDataExists && alertTypeDefs.length > 0 ? ( ) : null } diff --git a/src/components/tables/PrefixAlerts/shared.ts b/src/components/tables/PrefixAlerts/shared.ts index ecba51906e..b02cbbd9e1 100644 --- a/src/components/tables/PrefixAlerts/shared.ts +++ b/src/components/tables/PrefixAlerts/shared.ts @@ -15,10 +15,6 @@ export const columns: TableColumns[] = [ field: 'catalog_prefix', headerIntlKey: 'entityTable.data.catalogPrefix', }, - { - field: null, - headerIntlKey: 'entityTable.data.alertTypes', - }, { field: null, headerIntlKey: 'alerts.config.table.label.alertMethod', diff --git a/src/components/tables/PrefixAlerts/types.ts b/src/components/tables/PrefixAlerts/types.ts index 0cc3ffa643..f9335c6771 100644 --- a/src/components/tables/PrefixAlerts/types.ts +++ b/src/components/tables/PrefixAlerts/types.ts @@ -1,15 +1,15 @@ import type { Dispatch, SetStateAction } from 'react'; -import type { ReducedAlertSubscription } from 'src/api/types'; +import type { SubscriptionMetadata } from 'src/components/admin/Settings/PrefixAlerts/types'; import type { AlertTypeInfo } from 'src/gql-types/graphql'; export interface RowProps { alertTypeDefs: AlertTypeInfo[]; - row: ReducedAlertSubscription; + row: SubscriptionMetadata; } export interface RowsProps { alertTypeDefs: AlertTypeInfo[]; - data: ReducedAlertSubscription[]; + data: SubscriptionMetadata[]; } export interface TableFilterProps { diff --git a/src/components/tables/cells/prefixAlerts/EditButton.tsx b/src/components/tables/cells/prefixAlerts/EditButton.tsx index c44462397f..a040722cc4 100644 --- a/src/components/tables/cells/prefixAlerts/EditButton.tsx +++ b/src/components/tables/cells/prefixAlerts/EditButton.tsx @@ -7,22 +7,16 @@ import { Button, TableCell } from '@mui/material'; import { useIntl } from 'react-intl'; import AlertSubscriptionDialog from 'src/components/admin/Settings/PrefixAlerts/Dialog'; -import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; function AlertEditButton({ - alertTypes, - email, prefix, + subscriptionMetadata, ...props }: EditButtonProps) { const intl = useIntl(); const [open, setOpen] = useState(false); - const setSubscribedEmail = useAlertSubscriptionsStore( - (state) => state.setSubscribedEmail - ); - return ( + + ); }; diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx index 4398d7ff47..850677cefb 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx @@ -9,11 +9,7 @@ const Details = ({ subscription }: SubscriberAccordionProps) => { const { email } = subscription; return ( - + diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Summary.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Summary.tsx index ae1e87d6c2..54f4b0ddf7 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Summary.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Summary.tsx @@ -15,6 +15,7 @@ import { import { NavArrowDown, NavArrowRight } from 'iconoir-react'; +import DeleteButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton'; import ChipList from 'src/components/shared/ChipList'; import { useGetAlertTypes } from 'src/context/AlertType'; import { sortByAlertType } from 'src/utils/misc-utils'; @@ -79,21 +80,31 @@ const Summary = ({ expanded, subscription }: SubscriberAccordionProps) => { }, }} > - - - {expanded ? ( - - ) : ( - - )} + + + + {expanded ? ( + + ) : ( + + )} + + + {email} + + - - {email} - + {expanded ? null : ( diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/index.tsx index 1ea4913204..ddf2d4d008 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/index.tsx @@ -1,4 +1,4 @@ -import type { SubscriberInfoProps } from 'src/components/admin/Settings/PrefixAlerts/types'; +import type { SubscriberAccordionProps } from 'src/components/admin/Settings/PrefixAlerts/types'; import { useState } from 'react'; @@ -8,7 +8,9 @@ import Details from 'src/components/admin/Settings/PrefixAlerts/Dialog/Subscribe import Summary from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Summary'; import { defaultOutline, defaultOutlineColor_hovered } from 'src/context/Theme'; -const SubscriberInfo = ({ subscription }: SubscriberInfoProps) => { +const SubscriberInfo = ({ + subscription, +}: Omit) => { const theme = useTheme(); const [expanded, setExpanded] = useState(false); @@ -30,7 +32,7 @@ const SubscriberInfo = ({ subscription }: SubscriberInfoProps) => { }, }} > - +
diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx index 4a90a2c025..8352884155 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx @@ -12,7 +12,6 @@ import { import { useIntl } from 'react-intl'; import { useUnmount } from 'react-use'; -import DeleteButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton'; import PrefixField from 'src/components/admin/Settings/PrefixAlerts/Dialog/PrefixField'; import SaveButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/SaveButton'; import ServerErrors from 'src/components/admin/Settings/PrefixAlerts/Dialog/ServerErrors'; @@ -67,17 +66,7 @@ const AlertSubscriptionDialog = ({ - - {enableDeletion ? ( - closeDialog()} /> - ) : null} - + + ); +}; + +export default AddButton; diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx similarity index 68% rename from src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection.tsx rename to src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx index bcba61a973..4585d6f9e3 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx @@ -7,6 +7,7 @@ import { Stack, Typography } from '@mui/material'; import { useIntl } from 'react-intl'; import SubscriberInfo from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo'; +import AddButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/AddButton'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; import { hasOwnProperty } from 'src/utils/misc-utils'; @@ -30,12 +31,25 @@ const SubscriberSection = () => { return ( - - {intl.formatMessage( - { id: 'alerts.config.dialog.label.subscribers' }, - { count: targetSubscriptionMetadata.subscriptions.length } - )} - + + + {intl.formatMessage( + { id: 'alerts.config.dialog.label.subscribers' }, + { + count: targetSubscriptionMetadata.subscriptions + .length, + } + )} + + + + {targetSubscriptionMetadata.subscriptions .filter(({ deleted }) => !deleted) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx index 0158dc9b42..5373ae4869 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx @@ -17,7 +17,7 @@ import { useUnmount } from 'react-use'; import PrefixField from 'src/components/admin/Settings/PrefixAlerts/Dialog/PrefixField'; import SaveButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/SaveButton'; import ServerErrors from 'src/components/admin/Settings/PrefixAlerts/Dialog/ServerErrors'; -import SubscriberSection from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection'; +import SubscriberSection from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; import MessageWithLink from 'src/components/content/MessageWithLink'; import DialogTitleWithClose from 'src/components/shared/Dialog/TitleWithClose'; diff --git a/src/lang/en-US/Alerts.ts b/src/lang/en-US/Alerts.ts index db41a8a92d..efb986af9f 100644 --- a/src/lang/en-US/Alerts.ts +++ b/src/lang/en-US/Alerts.ts @@ -4,6 +4,7 @@ export const Alerts: Record = { 'alerts.config.header': `Organization Notifications`, 'alerts.config.cta.addAlertMethod': `Configure Notifications`, + 'alerts.config.dialog.cta.addSubscriber': `Add Recipient`, 'alerts.config.dialog.emailSelector.inputError': `Email is incorrectly formatted.`, 'alerts.config.dialog.error.generic': `An issue was encountered {operation} an alert subscription.`, 'alerts.config.dialog.error.term.delete': `deleting`, From 103269dc20a4f2f242f2bc056248e5372800e961 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Thu, 14 May 2026 15:20:14 -0400 Subject: [PATCH 15/70] Enable the creation of a templated subscription in dialog --- .../PrefixAlerts/Dialog/AlertTypeField.tsx | 25 ++---- .../Dialog/SubscriberSection/AddButton.tsx | 13 ++- .../Dialog/SubscriberSection/index.tsx | 27 ++++++- .../useAlertSubscriptionsStore.ts | 79 ++++++++++++++++++- 4 files changed, 119 insertions(+), 25 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/AlertTypeField.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/AlertTypeField.tsx index c48532722e..4aa8ab17bc 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/AlertTypeField.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/AlertTypeField.tsx @@ -1,35 +1,24 @@ import type { AlertTypeFieldProps } from 'src/components/admin/Settings/PrefixAlerts/types'; -import type { AlertTypeInfo } from 'src/gql-types/graphql'; - -import { useEffect } from 'react'; import { Skeleton } from '@mui/material'; import AlertTypeSelector from 'src/components/admin/Settings/PrefixAlerts/Dialog/AlertTypeSelector'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; -import { useGetAlertTypes } from 'src/context/AlertType'; - -const DEFAULT_OPTIONS: AlertTypeInfo[] = []; const AlertTypeField = ({ subscription }: AlertTypeFieldProps) => { - const [{ fetching, data, error }] = useGetAlertTypes(); - - const setServerError = useAlertSubscriptionsStore( - (state) => state.setSaveErrors + const alertTypeOptions = useAlertSubscriptionsStore( + (state) => state.alertTypeOptions + ); + const alertTypeOptionsFetching = useAlertSubscriptionsStore( + (state) => state.alertTypeOptionsFetching ); - useEffect(() => { - if (error) { - setServerError([error]); - } - }, [error, setServerError]); - - return fetching || !data ? ( + return alertTypeOptionsFetching ? ( ) : ( ); }; diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/AddButton.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/AddButton.tsx index 5aaf6903c9..fb35f0c520 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/AddButton.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/AddButton.tsx @@ -2,11 +2,22 @@ import { Button } from '@mui/material'; import { useIntl } from 'react-intl'; +import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; + const AddButton = () => { const intl = useIntl(); + const addTemplatedSubscription = useAlertSubscriptionsStore( + (state) => state.addTemplatedSubscription + ); + return ( - + ); +}; + +export default DeleteButton; diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx index 5373ae4869..987cbc964c 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx @@ -14,6 +14,7 @@ import { import { useIntl } from 'react-intl'; import { useUnmount } from 'react-use'; +import DeleteButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton'; import PrefixField from 'src/components/admin/Settings/PrefixAlerts/Dialog/PrefixField'; import SaveButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/SaveButton'; import ServerErrors from 'src/components/admin/Settings/PrefixAlerts/Dialog/ServerErrors'; @@ -76,7 +77,13 @@ const AlertSubscriptionDialog = ({ - + + closeDialog()} /> + ); }; diff --git a/src/lang/en-US/Alerts.ts b/src/lang/en-US/Alerts.ts index efb986af9f..dbdb360b78 100644 --- a/src/lang/en-US/Alerts.ts +++ b/src/lang/en-US/Alerts.ts @@ -5,6 +5,7 @@ export const Alerts: Record = { 'alerts.config.cta.addAlertMethod': `Configure Notifications`, 'alerts.config.dialog.cta.addSubscriber': `Add Recipient`, + 'alerts.config.dialog.cta.deleteAll': `Delete Prefix Alerts`, 'alerts.config.dialog.emailSelector.inputError': `Email is incorrectly formatted.`, 'alerts.config.dialog.error.generic': `An issue was encountered {operation} an alert subscription.`, 'alerts.config.dialog.error.term.delete': `deleting`, From 9279d56705e6522007d1733f1a13a41e14cf07da Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Fri, 29 May 2026 12:01:17 -0400 Subject: [PATCH 30/70] Filter out deleted subscriptions in metadata memo in SubscriberSection --- .../Dialog/SubscriberSection/index.tsx | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx index f5ca56282f..a0d63940e4 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx @@ -31,13 +31,17 @@ const SubscriberSection = () => { (state) => state.initializeAlertTypeOptions ); - const targetSubscriptionMetadata: SubscriptionMetadata = useMemo( - () => - hasOwnProperty(mutableSubscriptionMetadata, catalogPrefix) - ? mutableSubscriptionMetadata[catalogPrefix] - : { settings: {}, subscriptions: [] }, - [catalogPrefix, mutableSubscriptionMetadata] - ); + const targetSubscriptionMetadata: SubscriptionMetadata = useMemo(() => { + if (hasOwnProperty(mutableSubscriptionMetadata, catalogPrefix)) { + return { + ...mutableSubscriptionMetadata[catalogPrefix], + subscriptions: mutableSubscriptionMetadata[ + catalogPrefix + ].subscriptions.filter(({ deleted }) => !deleted), + }; + } + return { settings: {}, subscriptions: [] }; + }, [catalogPrefix, mutableSubscriptionMetadata]); useEffect(() => { if (error) { @@ -62,9 +66,8 @@ const SubscriberSection = () => { {intl.formatMessage( { id: 'alerts.config.dialog.label.subscribers' }, { - count: targetSubscriptionMetadata.subscriptions.filter( - ({ deleted }) => !deleted - ).length, + count: targetSubscriptionMetadata.subscriptions + .length, } )} @@ -72,14 +75,14 @@ const SubscriberSection = () => { - {targetSubscriptionMetadata.subscriptions - .filter(({ deleted }) => !deleted) - .map((subscription, index) => ( + {targetSubscriptionMetadata.subscriptions.map( + (subscription, index) => ( - ))} + ) + )} ); }; From 506c9014c0f283fc97dcb009141a46b06a095bbc Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Fri, 29 May 2026 13:18:11 -0400 Subject: [PATCH 31/70] Thicken accordion border for templated subscriptions --- .../PrefixAlerts/Dialog/SubscriberInfo/Summary.tsx | 11 ++++++----- .../PrefixAlerts/Dialog/SubscriberInfo/index.tsx | 9 +++++++++ src/components/admin/Settings/PrefixAlerts/types.ts | 5 +++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Summary.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Summary.tsx index caac9039dd..fdcaa2e9ef 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Summary.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Summary.tsx @@ -1,4 +1,4 @@ -import type { SubscriberAccordionProps } from 'src/components/admin/Settings/PrefixAlerts/types'; +import type { SubscriberAccordionSummaryProps } from 'src/components/admin/Settings/PrefixAlerts/types'; import type { ChipDisplay } from 'src/components/shared/ChipList/types'; import type { AlertTypeInfo } from 'src/gql-types/graphql'; @@ -16,17 +16,18 @@ import { import { NavArrowDown, NavArrowRight, WarningCircle } from 'iconoir-react'; import { useIntl } from 'react-intl'; -import { useEvaluateSubscriptionIneligibility } from 'src/components/admin/Settings/PrefixAlerts/useEvaluateSubscriptionIneligibility'; import ChipList from 'src/components/shared/ChipList'; import { useGetAlertTypes } from 'src/context/AlertType'; import { sortByAlertType } from 'src/utils/misc-utils'; -const Summary = ({ expanded, subscription }: SubscriberAccordionProps) => { +const Summary = ({ + duplicateSubscriptionEmails, + expanded, + subscription, +}: SubscriberAccordionSummaryProps) => { const theme = useTheme(); const intl = useIntl(); - const { duplicateSubscriptionEmails } = - useEvaluateSubscriptionIneligibility(); const [alertTypeResponse] = useGetAlertTypes(); const alertTypeDefs: AlertTypeInfo[] = useMemo( diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/index.tsx index 899c44f055..3a8ae44092 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/index.tsx @@ -6,6 +6,7 @@ import DeleteButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/Subs import Details from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details'; import Summary from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Summary'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; +import { useEvaluateSubscriptionIneligibility } from 'src/components/admin/Settings/PrefixAlerts/useEvaluateSubscriptionIneligibility'; import { defaultOutline, defaultOutlineColor_hovered } from 'src/context/Theme'; const SubscriberInfo = ({ @@ -17,6 +18,9 @@ const SubscriberInfo = ({ (state) => state.toggleSubscriptionViewingStatus ); + const { duplicateSubscriptionEmails, emptyEmailDetected } = + useEvaluateSubscriptionIneligibility(); + return ( diff --git a/src/components/admin/Settings/PrefixAlerts/types.ts b/src/components/admin/Settings/PrefixAlerts/types.ts index 5dde41c42f..88ee630636 100644 --- a/src/components/admin/Settings/PrefixAlerts/types.ts +++ b/src/components/admin/Settings/PrefixAlerts/types.ts @@ -68,6 +68,11 @@ export interface SubscriberAccordionProps { expanded?: boolean; } +export interface SubscriberAccordionSummaryProps + extends SubscriberAccordionProps { + duplicateSubscriptionEmails: string[]; +} + export interface SubscriptionDependentProps { subscription: MutableAlertSubscription; } From 11f2bb75bb176e0652261512a8d29cd37f3b502a Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Fri, 29 May 2026 13:42:27 -0400 Subject: [PATCH 32/70] Correct typo in getSubscriptionIndex --- .../Settings/PrefixAlerts/useAlertSubscriptionsStore.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts index 0148cc98f3..decb36d12d 100644 --- a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts +++ b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts @@ -8,7 +8,7 @@ import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; import produce from 'immer'; -import { cloneDeep } from 'lodash'; +import { cloneDeep, isEmpty } from 'lodash'; import { hasOwnProperty } from 'src/utils/misc-utils'; import { bundleSubscriptionsByPrefix } from 'src/utils/notification-utils'; @@ -70,8 +70,8 @@ const getSubscriptionIndex = ( !state.catalogPrefix || !subscriptionId || !hasOwnProperty(state, subscriptionMetadataTarget) || - !state[subscriptionMetadataTarget] || - !hasOwnProperty(state.mutableSubscriptionMetadata, state.catalogPrefix) + isEmpty(state[subscriptionMetadataTarget]) || + !hasOwnProperty(state[subscriptionMetadataTarget], state.catalogPrefix) ) { return -1; } From 9d45424711d4ef9244c36bb3b2295fbba778bb78 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Fri, 29 May 2026 14:15:12 -0400 Subject: [PATCH 33/70] Add simple empty subscriber message to dialog --- .../Dialog/SubscriberInfo/SummaryEmpty.tsx | 40 +++++++++++++++++++ .../Dialog/SubscriberSection/index.tsx | 17 +++++--- src/lang/en-US/Alerts.ts | 1 + 3 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/SummaryEmpty.tsx diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/SummaryEmpty.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/SummaryEmpty.tsx new file mode 100644 index 0000000000..bcc2cd3c4b --- /dev/null +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/SummaryEmpty.tsx @@ -0,0 +1,40 @@ +import { Stack, Typography, useTheme } from '@mui/material'; + +import { useIntl } from 'react-intl'; + +import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; +import { defaultOutline } from 'src/context/Theme'; + +const SummaryEmpty = () => { + const intl = useIntl(); + const theme = useTheme(); + + const catalogPrefix = useAlertSubscriptionsStore( + (state) => state.catalogPrefix + ); + + return ( + + + {intl.formatMessage( + { + id: 'alerts.config.dialog.message.noExistingSubscriptions', + }, + { prefix: {catalogPrefix} } + )} + + + ); +}; + +export default SummaryEmpty; diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx index a0d63940e4..de795c44c0 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx @@ -7,6 +7,7 @@ import { Stack, Typography } from '@mui/material'; import { useIntl } from 'react-intl'; import SubscriberInfo from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo'; +import SummaryEmpty from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/SummaryEmpty'; import AddButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/AddButton'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; import { useGetAlertTypes } from 'src/context/AlertType'; @@ -75,13 +76,17 @@ const SubscriberSection = () => { - {targetSubscriptionMetadata.subscriptions.map( - (subscription, index) => ( - + {targetSubscriptionMetadata.subscriptions.length > 0 ? ( + targetSubscriptionMetadata.subscriptions.map( + (subscription, index) => ( + + ) ) + ) : ( + )} ); diff --git a/src/lang/en-US/Alerts.ts b/src/lang/en-US/Alerts.ts index dbdb360b78..4d4a62ab25 100644 --- a/src/lang/en-US/Alerts.ts +++ b/src/lang/en-US/Alerts.ts @@ -12,6 +12,7 @@ export const Alerts: Record = { 'alerts.config.dialog.error.term.modify': `creating or updating`, 'alerts.config.dialog.label.subscribers': `Recipients ({count})`, 'alerts.config.dialog.label.placeholderSubscriberId': `(new recipient)`, + 'alerts.config.dialog.message.noExistingSubscriptions': `No emails are configured to be notified for prefix, {prefix}. To receive notifications for this prefix, click "Add Recipient" to get started.`, 'alerts.config.dialog.generate.description': `Choose where you'd like notifications to be sent. Email addresses can be for mailing lists, {docLink}, or individual users.`, 'alerts.config.dialog.generate.description.docLink': `Slack channels`, 'alerts.config.dialog.generate.description.docPath': `https://slack.com/intl/en-au/help/articles/206819278-Send-emails-to-Slack`, From fb97861f058c7ef67456a1f5cc63d0738cbf73a7 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 2 Jun 2026 12:48:13 -0400 Subject: [PATCH 34/70] Initialize global prefix settings in store --- src/api/alerts.ts | 39 ++++++++++- src/api/gql/useAllPages.ts | 2 +- src/api/types.ts | 10 +++ .../Settings/PrefixAlerts/Dialog/index.tsx | 3 + .../useAlertSubscriptionsStore.ts | 35 +++++++++- .../PrefixAlerts/useInitializeAlertConfigs.ts | 70 +++++++++++++++++++ src/context/URQL.tsx | 3 + src/types/gql.ts | 12 +++- 8 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts diff --git a/src/api/alerts.ts b/src/api/alerts.ts index 437c9daffd..13c0fb0650 100644 --- a/src/api/alerts.ts +++ b/src/api/alerts.ts @@ -1,9 +1,13 @@ -import type { ReducedAlertSubscriptionQueryResponse } from 'src/api/types'; +import type { + AlertConfigQueryResponse, + ReducedAlertSubscriptionQueryResponse, +} from 'src/api/types'; import type { DataProcessingAlert, AlertSubscription as LegacyAlertSubscription, } from 'src/types'; import type { + AlertConfigQueryInput, AlertSubscriptionMutationInput, AlertSubscriptionsBy, AlertTypeQueryResponse, @@ -22,6 +26,38 @@ import { updateSupabase, } from 'src/services/supabase'; +const AlertConfigQuery = gql` + query AlertConfigs( + $filter: AlertConfigsFilter + $after: String + $first: Int + ) { + alertConfigs(filter: $filter, after: $after, first: $first) { + edges { + node { + catalogPrefixOrName + config + createdAt + detail + effective { + config + provenance { + source + } + } + id + lastModifiedBy + updatedAt + } + } + pageInfo { + endCursor + hasNextPage + } + } + } +`; + const AlertSubscriptionCreateMutation = gql< { catalogPrefix: string; email: string }, AlertSubscriptionMutationInput @@ -176,6 +212,7 @@ const getTaskNotification = async (catalogName: string) => { }; export { + AlertConfigQuery, AlertSubscriptionCreateMutation, AlertSubscriptionDeleteMutation, AlertSubscriptionQuery, diff --git a/src/api/gql/useAllPages.ts b/src/api/gql/useAllPages.ts index e6e5addfe8..a8eaafe30e 100644 --- a/src/api/gql/useAllPages.ts +++ b/src/api/gql/useAllPages.ts @@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from 'react'; import { useQuery } from 'urql'; -interface Connection { +export interface Connection { edges: { node: TNode }[]; pageInfo: { hasNextPage: boolean; diff --git a/src/api/types.ts b/src/api/types.ts index dd6d46f50b..6c5282ba63 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -1,6 +1,16 @@ +import type { Connection } from 'src/api/gql/useAllPages'; +import type { AlertConfigEntry } from 'src/gql-types/graphql'; import type { Entity } from 'src/types'; import type { AlertSubscription } from 'src/types/gql'; +// The `AlertConfigEntryConnection` interface provided by GraphQL +// cannot be used here, unfortunately, if the hook `useAllPages` +// must be invoked. The client defines a generic type for GraphQL +// connections that omits properties found in these connections. +export interface AlertConfigQueryResponse { + alertConfigs: Connection; +} + export interface DraftSpecData { spec: any; catalog_name?: string; diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx index 987cbc964c..d37f36c4fa 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx @@ -20,6 +20,7 @@ import SaveButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/SaveBu import ServerErrors from 'src/components/admin/Settings/PrefixAlerts/Dialog/ServerErrors'; import SubscriberSection from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; +import { useInitializeAlertConfigs } from 'src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs'; import MessageWithLink from 'src/components/content/MessageWithLink'; import DialogTitleWithClose from 'src/components/shared/Dialog/TitleWithClose'; @@ -35,6 +36,8 @@ const AlertSubscriptionDialog = ({ }: AlertSubscriptionDialogProps) => { const intl = useIntl(); + useInitializeAlertConfigs(); + const initializeMutableSubscriptionMetadata = useAlertSubscriptionsStore( (state) => state.initializeMutableSubscriptionMetadata ); diff --git a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts index decb36d12d..905cb77fdb 100644 --- a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts +++ b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts @@ -2,6 +2,7 @@ import type { PostgrestError } from '@supabase/postgrest-js'; import type { ReducedAlertSubscription } from 'src/api/types'; import type { SubscriptionMetadataDictionary } from 'src/components/admin/Settings/PrefixAlerts/types'; import type { AlertTypeInfo } from 'src/gql-types/graphql'; +import type { Schema } from 'src/types'; import type { CombinedError } from 'urql'; import { create } from 'zustand'; @@ -25,6 +26,7 @@ interface AlertSubscriptionState { values: AlertTypeInfo[], fetching: boolean ) => void; + initializeGlobalPrefixSettings: (value: Schema) => void; initializeMutableSubscriptionMetadata: () => void; markSubscriptionForDeletion: ( catalogPrefix: string, @@ -180,6 +182,37 @@ const useAlertSubscriptionsStore = create()( 'alert type options initialized' ), + initializeGlobalPrefixSettings: (value) => + set( + produce((state: AlertSubscriptionState) => { + if (!state.catalogPrefix || isEmpty(value)) { + return; + } + + if ( + !hasOwnProperty( + state.mutableSubscriptionMetadata, + state.catalogPrefix + ) + ) { + state.mutableSubscriptionMetadata[ + state.catalogPrefix + ] = { + settings: value, + subscriptions: [], + }; + + return; + } + + state.mutableSubscriptionMetadata[ + state.catalogPrefix + ].settings = value; + }), + false, + 'global prefix settings initialized' + ), + initializeMutableSubscriptionMetadata: () => set( produce((state: AlertSubscriptionState) => { @@ -410,7 +443,7 @@ const useAlertSubscriptionsStore = create()( !previousValue; }), false, - 'subscription metadata set' + 'subscription viewing status toggled' ), }; }, devtoolsOptions(name)) diff --git a/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts b/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts new file mode 100644 index 0000000000..9fb27b0058 --- /dev/null +++ b/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts @@ -0,0 +1,70 @@ +import { useEffect, useMemo, useRef, useState } from 'react'; + +import { debounce, isEmpty } from 'lodash'; +import { useQuery } from 'urql'; + +import { AlertConfigQuery } from 'src/api/alerts'; +import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; +import { hasOwnProperty } from 'src/utils/misc-utils'; + +export function useInitializeAlertConfigs() { + const catalogPrefix = useAlertSubscriptionsStore( + (state) => state.catalogPrefix + ); + const initializeGlobalPrefixSettings = useAlertSubscriptionsStore( + (state) => state.initializeGlobalPrefixSettings + ); + const mutableSubscriptionMetadata = useAlertSubscriptionsStore( + (state) => state.mutableSubscriptionMetadata + ); + + const [debouncedPrefix, setDebouncedPrefix] = useState(catalogPrefix); + + const updateDebouncedPrefix = useRef( + debounce((prefix) => { + setDebouncedPrefix(prefix); + }, 750) + ); + + const settingsDefined = useMemo( + () => + debouncedPrefix.length > 0 && + hasOwnProperty(mutableSubscriptionMetadata, debouncedPrefix) && + !isEmpty(mutableSubscriptionMetadata[debouncedPrefix].settings), + [debouncedPrefix, mutableSubscriptionMetadata] + ); + + // TODO: Add error handling. + const [{ data, fetching }] = useQuery({ + pause: !debouncedPrefix || settingsDefined, + query: AlertConfigQuery, + variables: { + filter: { catalogPrefixOrName: { startsWith: debouncedPrefix } }, + }, + }); + + useEffect(() => { + updateDebouncedPrefix.current(catalogPrefix); + }, [catalogPrefix, updateDebouncedPrefix]); + + useEffect(() => { + if ( + debouncedPrefix === catalogPrefix && + !settingsDefined && + !fetching && + data?.alertConfigs && + data.alertConfigs.edges.length > 0 + ) { + initializeGlobalPrefixSettings( + data.alertConfigs.edges[0].node.effective.config + ); + } + }, [ + catalogPrefix, + data, + debouncedPrefix, + fetching, + initializeGlobalPrefixSettings, + settingsDefined, + ]); +} diff --git a/src/context/URQL.tsx b/src/context/URQL.tsx index e2a67e7cdb..ef31588479 100644 --- a/src/context/URQL.tsx +++ b/src/context/URQL.tsx @@ -55,8 +55,11 @@ function UrqlConfigProvider({ children }: BaseComponentProps) { keys: { // TODO (gql caching) - see GRAPHQL.md Alert: (_data) => null, + AlertConfig: (_data) => null, AlertSubscription: (_data) => null, AlertTypeInfo: (_data) => null, + EffectiveAlertConfig: (_data) => null, + FieldProvenance: (_data) => null, InviteLink: (data) => null, LiveSpecRef: (_data) => null, PrefixRef: (_data) => null, diff --git a/src/types/gql.ts b/src/types/gql.ts index 0626a32e28..0b060e246f 100644 --- a/src/types/gql.ts +++ b/src/types/gql.ts @@ -1,4 +1,8 @@ -import type { AlertType, AlertTypeInfo } from 'src/gql-types/graphql'; +import type { + AlertConfigsFilter, + AlertType, + AlertTypeInfo, +} from 'src/gql-types/graphql'; import type { ShardEntityTypes } from 'src/stores/ShardDetail/types'; export interface AlertDetailsRecipients { @@ -96,6 +100,12 @@ export interface ActiveAlertCountQueryResponse { export type AlertHistoryForTaskQueryResponse = DefaultAlertingQueryResponse; export type AlertingOverviewQueryResponse = AlertHistoryForTaskQueryResponse; +export interface AlertConfigQueryInput { + after?: string; + filter?: AlertConfigsFilter; + first?: number; +} + export interface AlertHistoryQueryResponse { liveSpecs: { edges: { From ee2a35d6f8b162e13e00cba19f379dcd328bbfd5 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 2 Jun 2026 12:58:36 -0400 Subject: [PATCH 35/70] Move alert type field and selector components to child directory --- .../PrefixAlerts/Dialog/{ => SubscriberInfo}/AlertTypeField.tsx | 2 +- .../Dialog/{ => SubscriberInfo}/AlertTypeSelector.tsx | 0 .../Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/components/admin/Settings/PrefixAlerts/Dialog/{ => SubscriberInfo}/AlertTypeField.tsx (94%) rename src/components/admin/Settings/PrefixAlerts/Dialog/{ => SubscriberInfo}/AlertTypeSelector.tsx (100%) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/AlertTypeField.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/AlertTypeField.tsx similarity index 94% rename from src/components/admin/Settings/PrefixAlerts/Dialog/AlertTypeField.tsx rename to src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/AlertTypeField.tsx index 4aa8ab17bc..b590cba1f9 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/AlertTypeField.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/AlertTypeField.tsx @@ -2,7 +2,7 @@ import type { AlertTypeFieldProps } from 'src/components/admin/Settings/PrefixAl import { Skeleton } from '@mui/material'; -import AlertTypeSelector from 'src/components/admin/Settings/PrefixAlerts/Dialog/AlertTypeSelector'; +import AlertTypeSelector from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/AlertTypeSelector'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; const AlertTypeField = ({ subscription }: AlertTypeFieldProps) => { diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/AlertTypeSelector.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/AlertTypeSelector.tsx similarity index 100% rename from src/components/admin/Settings/PrefixAlerts/Dialog/AlertTypeSelector.tsx rename to src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/AlertTypeSelector.tsx diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx index a0e5812bcf..08ba5a83e0 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx @@ -2,8 +2,8 @@ import type { SubscriberAccordionProps } from 'src/components/admin/Settings/Pre import { AccordionDetails, Stack } from '@mui/material'; -import AlertTypeField from 'src/components/admin/Settings/PrefixAlerts/Dialog/AlertTypeField'; import EmailListField from 'src/components/admin/Settings/PrefixAlerts/Dialog/EmailListField'; +import AlertTypeField from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/AlertTypeField'; const Details = ({ subscription }: SubscriberAccordionProps) => { return ( From 6fd2b7964f9c903a97e491988b4615783562bd8c Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 2 Jun 2026 13:02:08 -0400 Subject: [PATCH 36/70] Move email field and selector components to child directory --- .../Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx | 2 +- .../PrefixAlerts/Dialog/{ => SubscriberInfo}/EmailListField.tsx | 2 +- .../PrefixAlerts/{ => Dialog/SubscriberInfo}/EmailSelector.tsx | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/components/admin/Settings/PrefixAlerts/Dialog/{ => SubscriberInfo}/EmailListField.tsx (95%) rename src/components/admin/Settings/PrefixAlerts/{ => Dialog/SubscriberInfo}/EmailSelector.tsx (100%) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx index 08ba5a83e0..46b59c4480 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx @@ -2,8 +2,8 @@ import type { SubscriberAccordionProps } from 'src/components/admin/Settings/Pre import { AccordionDetails, Stack } from '@mui/material'; -import EmailListField from 'src/components/admin/Settings/PrefixAlerts/Dialog/EmailListField'; import AlertTypeField from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/AlertTypeField'; +import EmailListField from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/EmailListField'; const Details = ({ subscription }: SubscriberAccordionProps) => { return ( diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/EmailListField.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/EmailListField.tsx similarity index 95% rename from src/components/admin/Settings/PrefixAlerts/Dialog/EmailListField.tsx rename to src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/EmailListField.tsx index be678920fd..daef696e21 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/EmailListField.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/EmailListField.tsx @@ -4,7 +4,7 @@ import { TextField } from '@mui/material'; import { useIntl } from 'react-intl'; -import EmailSelector from 'src/components/admin/Settings/PrefixAlerts/EmailSelector'; +import EmailSelector from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/EmailSelector'; const EmailListField = ({ subscription, staticEmail }: EmailListFieldProps) => { const intl = useIntl(); diff --git a/src/components/admin/Settings/PrefixAlerts/EmailSelector.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/EmailSelector.tsx similarity index 100% rename from src/components/admin/Settings/PrefixAlerts/EmailSelector.tsx rename to src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/EmailSelector.tsx From ecf453ba8c113e9db94089568f8163435fb456ba Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 2 Jun 2026 13:03:39 -0400 Subject: [PATCH 37/70] Move SubscriberInfo directory into SubscriberSection directory --- .../SubscriberInfo/AlertTypeField.tsx | 2 +- .../SubscriberInfo/AlertTypeSelector.tsx | 0 .../{ => SubscriberSection}/SubscriberInfo/DeleteButton.tsx | 0 .../{ => SubscriberSection}/SubscriberInfo/Details.tsx | 4 ++-- .../SubscriberInfo/EmailListField.tsx | 2 +- .../SubscriberInfo/EmailSelector.tsx | 0 .../{ => SubscriberSection}/SubscriberInfo/Summary.tsx | 0 .../{ => SubscriberSection}/SubscriberInfo/SummaryEmpty.tsx | 0 .../Dialog/{ => SubscriberSection}/SubscriberInfo/index.tsx | 6 +++--- .../PrefixAlerts/Dialog/SubscriberSection/index.tsx | 4 ++-- 10 files changed, 9 insertions(+), 9 deletions(-) rename src/components/admin/Settings/PrefixAlerts/Dialog/{ => SubscriberSection}/SubscriberInfo/AlertTypeField.tsx (92%) rename src/components/admin/Settings/PrefixAlerts/Dialog/{ => SubscriberSection}/SubscriberInfo/AlertTypeSelector.tsx (100%) rename src/components/admin/Settings/PrefixAlerts/Dialog/{ => SubscriberSection}/SubscriberInfo/DeleteButton.tsx (100%) rename src/components/admin/Settings/PrefixAlerts/Dialog/{ => SubscriberSection}/SubscriberInfo/Details.tsx (84%) rename src/components/admin/Settings/PrefixAlerts/Dialog/{ => SubscriberSection}/SubscriberInfo/EmailListField.tsx (93%) rename src/components/admin/Settings/PrefixAlerts/Dialog/{ => SubscriberSection}/SubscriberInfo/EmailSelector.tsx (100%) rename src/components/admin/Settings/PrefixAlerts/Dialog/{ => SubscriberSection}/SubscriberInfo/Summary.tsx (100%) rename src/components/admin/Settings/PrefixAlerts/Dialog/{ => SubscriberSection}/SubscriberInfo/SummaryEmpty.tsx (100%) rename src/components/admin/Settings/PrefixAlerts/Dialog/{ => SubscriberSection}/SubscriberInfo/index.tsx (94%) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/AlertTypeField.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeField.tsx similarity index 92% rename from src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/AlertTypeField.tsx rename to src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeField.tsx index b590cba1f9..8d2afe88d5 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/AlertTypeField.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeField.tsx @@ -2,7 +2,7 @@ import type { AlertTypeFieldProps } from 'src/components/admin/Settings/PrefixAl import { Skeleton } from '@mui/material'; -import AlertTypeSelector from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/AlertTypeSelector'; +import AlertTypeSelector from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; const AlertTypeField = ({ subscription }: AlertTypeFieldProps) => { diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/AlertTypeSelector.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx similarity index 100% rename from src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/AlertTypeSelector.tsx rename to src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/DeleteButton.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/DeleteButton.tsx similarity index 100% rename from src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/DeleteButton.tsx rename to src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/DeleteButton.tsx diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/Details.tsx similarity index 84% rename from src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx rename to src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/Details.tsx index 46b59c4480..7a43783f92 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/Details.tsx @@ -2,8 +2,8 @@ import type { SubscriberAccordionProps } from 'src/components/admin/Settings/Pre import { AccordionDetails, Stack } from '@mui/material'; -import AlertTypeField from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/AlertTypeField'; -import EmailListField from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/EmailListField'; +import AlertTypeField from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeField'; +import EmailListField from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailListField'; const Details = ({ subscription }: SubscriberAccordionProps) => { return ( diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/EmailListField.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailListField.tsx similarity index 93% rename from src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/EmailListField.tsx rename to src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailListField.tsx index daef696e21..5fbab63912 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/EmailListField.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailListField.tsx @@ -4,7 +4,7 @@ import { TextField } from '@mui/material'; import { useIntl } from 'react-intl'; -import EmailSelector from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/EmailSelector'; +import EmailSelector from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailSelector'; const EmailListField = ({ subscription, staticEmail }: EmailListFieldProps) => { const intl = useIntl(); diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/EmailSelector.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailSelector.tsx similarity index 100% rename from src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/EmailSelector.tsx rename to src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailSelector.tsx diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Summary.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/Summary.tsx similarity index 100% rename from src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Summary.tsx rename to src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/Summary.tsx diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/SummaryEmpty.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/SummaryEmpty.tsx similarity index 100% rename from src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/SummaryEmpty.tsx rename to src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/SummaryEmpty.tsx diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/index.tsx similarity index 94% rename from src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/index.tsx rename to src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/index.tsx index 3a8ae44092..c077aa0db5 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/index.tsx @@ -2,9 +2,9 @@ import type { SubscriberAccordionProps } from 'src/components/admin/Settings/Pre import { Accordion, Stack, useTheme } from '@mui/material'; -import DeleteButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/DeleteButton'; -import Details from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Details'; -import Summary from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/Summary'; +import DeleteButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/DeleteButton'; +import Details from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/Details'; +import Summary from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/Summary'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; import { useEvaluateSubscriptionIneligibility } from 'src/components/admin/Settings/PrefixAlerts/useEvaluateSubscriptionIneligibility'; import { defaultOutline, defaultOutlineColor_hovered } from 'src/context/Theme'; diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx index de795c44c0..1e2c5d27dd 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx @@ -6,9 +6,9 @@ import { Stack, Typography } from '@mui/material'; import { useIntl } from 'react-intl'; -import SubscriberInfo from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo'; -import SummaryEmpty from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberInfo/SummaryEmpty'; import AddButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/AddButton'; +import SubscriberInfo from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo'; +import SummaryEmpty from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/SummaryEmpty'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; import { useGetAlertTypes } from 'src/context/AlertType'; import { hasOwnProperty } from 'src/utils/misc-utils'; From 234807be52375d23f603397d92ee5f1b20f8edbe Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 2 Jun 2026 14:15:58 -0400 Subject: [PATCH 38/70] Add global settings section to dialog --- .../GlobalSettings/DataMovementSetting.tsx | 69 +++++++++++++++++++ .../Dialog/GlobalSettings/index.tsx | 63 +++++++++++++++++ .../Settings/PrefixAlerts/Dialog/index.tsx | 3 + .../admin/Settings/PrefixAlerts/types.ts | 22 +++--- src/lang/en-US/Alerts.ts | 5 +- src/utils/notification-utils.ts | 29 ++++++++ 6 files changed, 181 insertions(+), 10 deletions(-) create mode 100644 src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx create mode 100644 src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx new file mode 100644 index 0000000000..3194769b81 --- /dev/null +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx @@ -0,0 +1,69 @@ +import type { AutocompleteRenderInputParams } from '@mui/material'; +import type { GlobalSettingProps } from 'src/components/admin/Settings/PrefixAlerts/types'; + +import { Autocomplete, Stack, TextField, Typography } from '@mui/material'; + +import { useIntl } from 'react-intl'; + +import useSettingIntervalOptions from 'src/components/shared/Entity/Details/Overview/NotificationSettings/useSettingIntervalOptions'; +import { translateUnconventionalTimeFormat } from 'src/utils/notification-utils'; + +const DataMovementSetting = ({ + prefix, + setting, +}: GlobalSettingProps<{ stalledFor: string }>) => { + const intl = useIntl(); + + const { options } = useSettingIntervalOptions(); + + return ( + + + + {intl.formatMessage({ + id: 'alerts.alertType.humanReadable.data_movement_stalled', + })} + + + + {intl.formatMessage({ + id: 'details.settings.notifications.dataProcessing.noDataProcessedInInterval.message', + })} + + + + options[interval]} + onChange={() => {}} + options={Object.keys(options)} + renderInput={({ + InputProps, + ...params + }: AutocompleteRenderInputParams) => ( + + )} + value={translateUnconventionalTimeFormat( + setting?.condition.stalledFor + )} + /> + + ); +}; + +export default DataMovementSetting; diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx new file mode 100644 index 0000000000..e497d096ca --- /dev/null +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx @@ -0,0 +1,63 @@ +import { useMemo } from 'react'; + +import { Stack, Typography } from '@mui/material'; + +import { useIntl } from 'react-intl'; + +import DataMovementSetting from 'src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting'; +import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; +import { defaultOutline } from 'src/context/Theme'; +import { hasOwnProperty } from 'src/utils/misc-utils'; + +const GlobalSettings = () => { + const intl = useIntl(); + + const catalogPrefix = useAlertSubscriptionsStore( + (state) => state.catalogPrefix + ); + const mutableSubscriptionMetadata = useAlertSubscriptionsStore( + (state) => state.mutableSubscriptionMetadata + ); + + const targetGlobalSettings = useMemo( + () => + hasOwnProperty(mutableSubscriptionMetadata, catalogPrefix) + ? mutableSubscriptionMetadata[catalogPrefix].settings + : {}, + [catalogPrefix, mutableSubscriptionMetadata] + ); + + return ( + + theme.palette.mode === 'dark' ? 'transparent' : 'white', + border: (theme) => defaultOutline[theme.palette.mode], + borderRadius: '6px', + padding: 2, + }} + > + + + {intl.formatMessage({ + id: 'alerts.config.dialog.label.globalSettings', + })} + + + + {intl.formatMessage({ + id: 'alerts.config.dialog.message.globalSettings', + })} + + + + + + ); +}; + +export default GlobalSettings; diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx index d37f36c4fa..391d51f501 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx @@ -15,6 +15,7 @@ import { useIntl } from 'react-intl'; import { useUnmount } from 'react-use'; import DeleteButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton'; +import GlobalSettings from 'src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings'; import PrefixField from 'src/components/admin/Settings/PrefixAlerts/Dialog/PrefixField'; import SaveButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/SaveButton'; import ServerErrors from 'src/components/admin/Settings/PrefixAlerts/Dialog/ServerErrors'; @@ -76,6 +77,8 @@ const AlertSubscriptionDialog = ({ + + diff --git a/src/components/admin/Settings/PrefixAlerts/types.ts b/src/components/admin/Settings/PrefixAlerts/types.ts index 88ee630636..2af340a960 100644 --- a/src/components/admin/Settings/PrefixAlerts/types.ts +++ b/src/components/admin/Settings/PrefixAlerts/types.ts @@ -2,6 +2,7 @@ import type { TableCellProps } from '@mui/material'; import type { Dispatch, SetStateAction } from 'react'; import type { ReducedAlertSubscription } from 'src/api/types'; import type { AlertTypeInfo } from 'src/gql-types/graphql'; +import type { Schema } from 'src/types'; import type { BaseAlertSubscriptionMutationInput } from 'src/types/gql'; export interface AlertSubscriptionDialogProps extends PrefixFieldProps { @@ -28,6 +29,12 @@ export interface AlertTypeSelectorProps extends AlertTypeFieldProps { options: AlertTypeInfo[]; } +export interface BaseGlobalSetting { + condition: T; + autoDisable?: boolean; + enabled?: boolean; +} + export interface DialogActionProps { closeDialog: () => void; } @@ -45,20 +52,17 @@ export interface EmailListFieldProps extends SubscriptionDependentProps { staticEmail?: string; } +export interface GlobalSettingProps { + prefix: string; + setting: BaseGlobalSetting | undefined; +} + export interface MutableAlertSubscription extends ReducedAlertSubscription { id: string; viewing: boolean; deleted?: boolean; } -interface GlobalSetting { - [property: string]: boolean | number | string; -} - -interface GlobalSettingDictionary { - [alertType: string]: GlobalSetting; -} - export interface PrefixFieldProps { staticPrefix?: string; } @@ -78,7 +82,7 @@ export interface SubscriptionDependentProps { } export interface SubscriptionMetadata { - settings: GlobalSettingDictionary; + settings: Schema; subscriptions: MutableAlertSubscription[]; } diff --git a/src/lang/en-US/Alerts.ts b/src/lang/en-US/Alerts.ts index 4d4a62ab25..66d229e2d3 100644 --- a/src/lang/en-US/Alerts.ts +++ b/src/lang/en-US/Alerts.ts @@ -10,9 +10,11 @@ export const Alerts: Record = { 'alerts.config.dialog.error.generic': `An issue was encountered {operation} an alert subscription.`, 'alerts.config.dialog.error.term.delete': `deleting`, 'alerts.config.dialog.error.term.modify': `creating or updating`, + 'alerts.config.dialog.label.globalSettings': `Global Settings`, 'alerts.config.dialog.label.subscribers': `Recipients ({count})`, 'alerts.config.dialog.label.placeholderSubscriberId': `(new recipient)`, 'alerts.config.dialog.message.noExistingSubscriptions': `No emails are configured to be notified for prefix, {prefix}. To receive notifications for this prefix, click "Add Recipient" to get started.`, + 'alerts.config.dialog.message.globalSettings': `Default settings applied to all tasks under this prefix. Individual tasks can override these values in their own notification settings.`, 'alerts.config.dialog.generate.description': `Choose where you'd like notifications to be sent. Email addresses can be for mailing lists, {docLink}, or individual users.`, 'alerts.config.dialog.generate.description.docLink': `Slack channels`, 'alerts.config.dialog.generate.description.docPath': `https://slack.com/intl/en-au/help/articles/206819278-Send-emails-to-Slack`, @@ -69,7 +71,8 @@ export const Alerts: Record = { 'alert.active.noAlerts.title': `All Clear!`, 'alert.active.noAlerts.message': `No active alerts.`, - // Consumed in ui/src/settings/alerts.ts and ui/src/hooks/useAlertTypeContent.ts + // Consumed in ui/src/settings/alerts.ts, ui/src/hooks/useAlertTypeContent.ts, + // and ui/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx 'alerts.alertType.humanReadable.auto_discover_failed': `Auto Discover Failed`, 'alerts.alertType.humanReadable.background_publication_failed': `Background Publication Failed`, 'alerts.alertType.humanReadable.shard_failed': `Task Failure`, diff --git a/src/utils/notification-utils.ts b/src/utils/notification-utils.ts index 0f408c5f95..0531554a03 100644 --- a/src/utils/notification-utils.ts +++ b/src/utils/notification-utils.ts @@ -37,3 +37,32 @@ export const bundleSubscriptionsByPrefix = ( return subscriptionMetadata; }; + +export const translateUnconventionalTimeFormat = ( + value: string | undefined +) => { + switch (value) { + case '1h': + return '01:00:00'; + case '2h': + return '02:00:00'; + case '4h': + return '04:00:00'; + case '8h': + return '08:00:00'; + case '12h': + return '12:00:00'; + case '24h': + return '24:00:00'; + case '1d': + return '24:00:00'; + case '2d': + return '2 days'; + case '3d': + return '3 days'; + case '7d': + return '7 days'; + default: + return 'none'; + } +}; From 3dd464aef304198bf46f1888f84fab7c044867ad Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 2 Jun 2026 14:34:55 -0400 Subject: [PATCH 39/70] Set data movement stalled global setting in dialog --- .../GlobalSettings/DataMovementSetting.tsx | 26 +++++++++++++++-- .../useAlertSubscriptionsStore.ts | 4 +-- .../PrefixAlerts/useInitializeAlertConfigs.ts | 8 ++--- src/utils/notification-utils.ts | 29 +++++++++++++++++-- 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx index 3194769b81..dd56eba8a6 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx @@ -1,12 +1,18 @@ import type { AutocompleteRenderInputParams } from '@mui/material'; import type { GlobalSettingProps } from 'src/components/admin/Settings/PrefixAlerts/types'; +import React from 'react'; + import { Autocomplete, Stack, TextField, Typography } from '@mui/material'; import { useIntl } from 'react-intl'; +import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; import useSettingIntervalOptions from 'src/components/shared/Entity/Details/Overview/NotificationSettings/useSettingIntervalOptions'; -import { translateUnconventionalTimeFormat } from 'src/utils/notification-utils'; +import { + fromUnconventionalTimeFormat, + toUnconventionalTimeFormat, +} from 'src/utils/notification-utils'; const DataMovementSetting = ({ prefix, @@ -16,6 +22,10 @@ const DataMovementSetting = ({ const { options } = useSettingIntervalOptions(); + const initializeGlobalPrefixSettings = useAlertSubscriptionsStore( + (state) => state.setGlobalPrefixSettings + ); + return ( @@ -37,7 +47,17 @@ const DataMovementSetting = ({ disableClearable fullWidth getOptionLabel={(interval) => options[interval]} - onChange={() => {}} + onChange={(_event: React.SyntheticEvent, value: string) => { + const formattedValue = toUnconventionalTimeFormat(value); + + if (formattedValue !== 'none') { + initializeGlobalPrefixSettings({ + dataMovementStalled: { + condition: { stalledFor: formattedValue }, + }, + }); + } + }} options={Object.keys(options)} renderInput={({ InputProps, @@ -58,7 +78,7 @@ const DataMovementSetting = ({ }} /> )} - value={translateUnconventionalTimeFormat( + value={fromUnconventionalTimeFormat( setting?.condition.stalledFor )} /> diff --git a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts index 905cb77fdb..e736b65262 100644 --- a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts +++ b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts @@ -26,7 +26,6 @@ interface AlertSubscriptionState { values: AlertTypeInfo[], fetching: boolean ) => void; - initializeGlobalPrefixSettings: (value: Schema) => void; initializeMutableSubscriptionMetadata: () => void; markSubscriptionForDeletion: ( catalogPrefix: string, @@ -49,6 +48,7 @@ interface AlertSubscriptionState { setEmailErrorsExist: ( value: AlertSubscriptionState['emailErrorsExist'] ) => void; + setGlobalPrefixSettings: (value: Schema) => void; setInitializationError: ( value: AlertSubscriptionState['initializationError'] ) => void; @@ -182,7 +182,7 @@ const useAlertSubscriptionsStore = create()( 'alert type options initialized' ), - initializeGlobalPrefixSettings: (value) => + setGlobalPrefixSettings: (value) => set( produce((state: AlertSubscriptionState) => { if (!state.catalogPrefix || isEmpty(value)) { diff --git a/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts b/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts index 9fb27b0058..f60d996625 100644 --- a/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts +++ b/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts @@ -11,8 +11,8 @@ export function useInitializeAlertConfigs() { const catalogPrefix = useAlertSubscriptionsStore( (state) => state.catalogPrefix ); - const initializeGlobalPrefixSettings = useAlertSubscriptionsStore( - (state) => state.initializeGlobalPrefixSettings + const setGlobalPrefixSettings = useAlertSubscriptionsStore( + (state) => state.setGlobalPrefixSettings ); const mutableSubscriptionMetadata = useAlertSubscriptionsStore( (state) => state.mutableSubscriptionMetadata @@ -55,7 +55,7 @@ export function useInitializeAlertConfigs() { data?.alertConfigs && data.alertConfigs.edges.length > 0 ) { - initializeGlobalPrefixSettings( + setGlobalPrefixSettings( data.alertConfigs.edges[0].node.effective.config ); } @@ -64,7 +64,7 @@ export function useInitializeAlertConfigs() { data, debouncedPrefix, fetching, - initializeGlobalPrefixSettings, + setGlobalPrefixSettings, settingsDefined, ]); } diff --git a/src/utils/notification-utils.ts b/src/utils/notification-utils.ts index 0531554a03..86f5c081b3 100644 --- a/src/utils/notification-utils.ts +++ b/src/utils/notification-utils.ts @@ -38,9 +38,7 @@ export const bundleSubscriptionsByPrefix = ( return subscriptionMetadata; }; -export const translateUnconventionalTimeFormat = ( - value: string | undefined -) => { +export const fromUnconventionalTimeFormat = (value: string | undefined) => { switch (value) { case '1h': return '01:00:00'; @@ -66,3 +64,28 @@ export const translateUnconventionalTimeFormat = ( return 'none'; } }; + +export const toUnconventionalTimeFormat = (value: string | undefined) => { + switch (value) { + case '01:00:00': + return '1h'; + case '02:00:00': + return '2h'; + case '04:00:00': + return '4h'; + case '08:00:00': + return '8h'; + case '12:00:00': + return '12h'; + case '24:00:00': + return '24h'; + case '2 days': + return '2d'; + case '3 days': + return '3d'; + case '7 days': + return '7d'; + default: + return 'none'; + } +}; From 7c7183a7763959d04c0f8347b09aee073db190b3 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 2 Jun 2026 14:41:38 -0400 Subject: [PATCH 40/70] Prevent existing global settings from being removed --- .../Settings/PrefixAlerts/useAlertSubscriptionsStore.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts index e736b65262..776569f8a2 100644 --- a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts +++ b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts @@ -207,10 +207,15 @@ const useAlertSubscriptionsStore = create()( state.mutableSubscriptionMetadata[ state.catalogPrefix - ].settings = value; + ].settings = { + ...state.mutableSubscriptionMetadata[ + state.catalogPrefix + ].settings, + ...value, + }; }), false, - 'global prefix settings initialized' + 'global prefix settings set' ), initializeMutableSubscriptionMetadata: () => From 9cceb0d301425bee60e922a4b9359d07ad2e0e62 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 2 Jun 2026 15:00:11 -0400 Subject: [PATCH 41/70] Unset data movement stalled global setting in dialog --- .../GlobalSettings/DataMovementSetting.tsx | 24 ++++++++------ .../Dialog/GlobalSettings/index.tsx | 8 ++++- .../admin/Settings/PrefixAlerts/types.ts | 16 +++++---- .../useAlertSubscriptionsStore.ts | 33 ++++++++++++------- src/utils/notification-utils.ts | 4 +++ 5 files changed, 56 insertions(+), 29 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx index dd56eba8a6..970cce4631 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx @@ -16,13 +16,14 @@ import { const DataMovementSetting = ({ prefix, - setting, + config, + targetSetting, }: GlobalSettingProps<{ stalledFor: string }>) => { const intl = useIntl(); const { options } = useSettingIntervalOptions(); - const initializeGlobalPrefixSettings = useAlertSubscriptionsStore( + const setGlobalPrefixSettings = useAlertSubscriptionsStore( (state) => state.setGlobalPrefixSettings ); @@ -50,13 +51,16 @@ const DataMovementSetting = ({ onChange={(_event: React.SyntheticEvent, value: string) => { const formattedValue = toUnconventionalTimeFormat(value); - if (formattedValue !== 'none') { - initializeGlobalPrefixSettings({ - dataMovementStalled: { - condition: { stalledFor: formattedValue }, - }, - }); - } + setGlobalPrefixSettings( + formattedValue !== 'none' + ? { + [targetSetting]: { + condition: { stalledFor: formattedValue }, + }, + } + : {}, + targetSetting + ); }} options={Object.keys(options)} renderInput={({ @@ -79,7 +83,7 @@ const DataMovementSetting = ({ /> )} value={fromUnconventionalTimeFormat( - setting?.condition.stalledFor + config?.condition.stalledFor )} /> diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx index e497d096ca..11b2ea871d 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx @@ -8,6 +8,7 @@ import DataMovementSetting from 'src/components/admin/Settings/PrefixAlerts/Dial import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; import { defaultOutline } from 'src/context/Theme'; import { hasOwnProperty } from 'src/utils/misc-utils'; +import { AlertConfigKeys } from 'src/utils/notification-utils'; const GlobalSettings = () => { const intl = useIntl(); @@ -53,8 +54,13 @@ const GlobalSettings = () => { ); diff --git a/src/components/admin/Settings/PrefixAlerts/types.ts b/src/components/admin/Settings/PrefixAlerts/types.ts index 2af340a960..d4c1283ad6 100644 --- a/src/components/admin/Settings/PrefixAlerts/types.ts +++ b/src/components/admin/Settings/PrefixAlerts/types.ts @@ -4,6 +4,7 @@ import type { ReducedAlertSubscription } from 'src/api/types'; import type { AlertTypeInfo } from 'src/gql-types/graphql'; import type { Schema } from 'src/types'; import type { BaseAlertSubscriptionMutationInput } from 'src/types/gql'; +import type { AlertConfigKeys } from 'src/utils/notification-utils'; export interface AlertSubscriptionDialogProps extends PrefixFieldProps { descriptionId: string; @@ -29,12 +30,6 @@ export interface AlertTypeSelectorProps extends AlertTypeFieldProps { options: AlertTypeInfo[]; } -export interface BaseGlobalSetting { - condition: T; - autoDisable?: boolean; - enabled?: boolean; -} - export interface DialogActionProps { closeDialog: () => void; } @@ -52,9 +47,16 @@ export interface EmailListFieldProps extends SubscriptionDependentProps { staticEmail?: string; } +interface GlobalSettingConfig { + condition: T; + autoDisable?: boolean; + enabled?: boolean; +} + export interface GlobalSettingProps { + config: GlobalSettingConfig | undefined; prefix: string; - setting: BaseGlobalSetting | undefined; + targetSetting: AlertConfigKeys; } export interface MutableAlertSubscription extends ReducedAlertSubscription { diff --git a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts index 776569f8a2..f18312385d 100644 --- a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts +++ b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts @@ -9,7 +9,7 @@ import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; import produce from 'immer'; -import { cloneDeep, isEmpty } from 'lodash'; +import { cloneDeep, isEmpty, omit } from 'lodash'; import { hasOwnProperty } from 'src/utils/misc-utils'; import { bundleSubscriptionsByPrefix } from 'src/utils/notification-utils'; @@ -48,7 +48,7 @@ interface AlertSubscriptionState { setEmailErrorsExist: ( value: AlertSubscriptionState['emailErrorsExist'] ) => void; - setGlobalPrefixSettings: (value: Schema) => void; + setGlobalPrefixSettings: (value: Schema, targetSetting?: string) => void; setInitializationError: ( value: AlertSubscriptionState['initializationError'] ) => void; @@ -182,18 +182,21 @@ const useAlertSubscriptionsStore = create()( 'alert type options initialized' ), - setGlobalPrefixSettings: (value) => + setGlobalPrefixSettings: (value, targetSetting) => set( produce((state: AlertSubscriptionState) => { - if (!state.catalogPrefix || isEmpty(value)) { + if (!state.catalogPrefix) { return; } + const valueEmpty = isEmpty(value); + if ( !hasOwnProperty( state.mutableSubscriptionMetadata, state.catalogPrefix - ) + ) && + !valueEmpty ) { state.mutableSubscriptionMetadata[ state.catalogPrefix @@ -207,12 +210,20 @@ const useAlertSubscriptionsStore = create()( state.mutableSubscriptionMetadata[ state.catalogPrefix - ].settings = { - ...state.mutableSubscriptionMetadata[ - state.catalogPrefix - ].settings, - ...value, - }; + ].settings = + valueEmpty && targetSetting + ? omit( + state.mutableSubscriptionMetadata[ + state.catalogPrefix + ].settings, + targetSetting + ) + : { + ...state.mutableSubscriptionMetadata[ + state.catalogPrefix + ].settings, + ...value, + }; }), false, 'global prefix settings set' diff --git a/src/utils/notification-utils.ts b/src/utils/notification-utils.ts index 86f5c081b3..bb10f043e5 100644 --- a/src/utils/notification-utils.ts +++ b/src/utils/notification-utils.ts @@ -2,6 +2,10 @@ import type { OptionsObject } from 'notistack'; import type { ReducedAlertSubscription } from 'src/api/types'; import type { SubscriptionMetadataDictionary } from 'src/components/admin/Settings/PrefixAlerts/types'; +export enum AlertConfigKeys { + DATA_MOVEMENT_STALLED = 'dataMovementStalled', +} + export const snackbarSettings: OptionsObject = { anchorOrigin: { vertical: 'top', From 3ec148245ae3873307f5072151575766aba4edf4 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 2 Jun 2026 15:01:55 -0400 Subject: [PATCH 42/70] Remove commented code fragment from Rows --- src/components/tables/PrefixAlerts/Rows.tsx | 27 --------------------- 1 file changed, 27 deletions(-) diff --git a/src/components/tables/PrefixAlerts/Rows.tsx b/src/components/tables/PrefixAlerts/Rows.tsx index ec0c6e0aef..487626b40c 100644 --- a/src/components/tables/PrefixAlerts/Rows.tsx +++ b/src/components/tables/PrefixAlerts/Rows.tsx @@ -11,33 +11,6 @@ import AlertEditButton from 'src/components/tables/cells/prefixAlerts/EditButton function Row({ row }: RowProps) { const { subscriptions } = row; - // const evaluatedAlertTypes: ChipDisplay[] = useMemo( - // () => - // row.alertTypes - // .map((alertType) => - // alertTypeDefs.find((def) => def.alertType === alertType) - // ) - // .filter((def) => typeof def !== 'undefined') - // .sort((first, second) => - // sortByAlertType( - // { - // isSystemAlert: first.isSystem, - // value: first.displayName, - // }, - // { - // isSystemAlert: second.isSystem, - // value: second.displayName, - // }, - // 'asc' - // ) - // ) - // .map(({ displayName, isSystem }) => ({ - // display: displayName, - // diminishedText: isSystem, - // })), - // [alertTypeDefs, row.alertTypes] - // ); - return ( {subscriptions[0].catalogPrefix} From d10643853384a28640d55c43a1925a6a8750c3f7 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 2 Jun 2026 15:10:14 -0400 Subject: [PATCH 43/70] Update dialog section header styling --- .../Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx | 4 ++-- .../Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx index 11b2ea871d..119f6c8310 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx @@ -39,8 +39,8 @@ const GlobalSettings = () => { padding: 2, }} > - - + + {intl.formatMessage({ id: 'alerts.config.dialog.label.globalSettings', })} diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx index 1e2c5d27dd..6a43032f28 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx @@ -63,7 +63,7 @@ const SubscriberSection = () => { justifyContent: 'space-between', }} > - + {intl.formatMessage( { id: 'alerts.config.dialog.label.subscribers' }, { From 97054bbccafeec9c41fbdb8894081d2d36818a3f Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 16 Jun 2026 09:56:38 -0400 Subject: [PATCH 44/70] Add todo comment in useInitializeAlertConfig --- .../admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts b/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts index f60d996625..13656013b9 100644 --- a/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts +++ b/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts @@ -7,6 +7,8 @@ import { AlertConfigQuery } from 'src/api/alerts'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; import { hasOwnProperty } from 'src/utils/misc-utils'; +// TODO: Figure out the best way to display a loading state for each global +// setting component. export function useInitializeAlertConfigs() { const catalogPrefix = useAlertSubscriptionsStore( (state) => state.catalogPrefix From dc95d361eba9e982b8b6c8a7bdf4c7bea4b0f98c Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 16 Jun 2026 10:00:01 -0400 Subject: [PATCH 45/70] Remove diminished chip styling for alert type chips --- .../SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx | 1 - .../Dialog/SubscriberSection/SubscriberInfo/Summary.tsx | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx index 0828fd37b9..3ea12a8727 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx @@ -153,7 +153,6 @@ const AlertTypeSelector = ({ return ( ({ - display: displayName, - diminishedText: isSystem, - })), + .map(({ displayName }) => ({ display: displayName })), [alertTypeDefs, alertTypes] ); From a2605422efdd9b461dd92d08cf58a1f3124a5421 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 16 Jun 2026 10:18:12 -0400 Subject: [PATCH 46/70] Replace instance of depreciated renderTags prop in AlertTypeSelector --- .../SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx index 3ea12a8727..88f28196e0 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx @@ -129,7 +129,7 @@ const AlertTypeSelector = ({ /> ); }} - renderTags={(values, getTagProps) => { + renderValue={(values, getTagProps) => { return values .sort((first, second) => sortByAlertType( From 56649651c9bc0efba33df1b9535b5cffa0e1f71c Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 16 Jun 2026 10:20:41 -0400 Subject: [PATCH 47/70] Rename memoized values variable in AlertTypeSelector --- .../SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx index 88f28196e0..54bb044d8d 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx @@ -41,7 +41,7 @@ const AlertTypeSelector = ({ [options] ); - const values: AlertTypeInfo[] = useMemo(() => { + const selectedAlertTypes: AlertTypeInfo[] = useMemo(() => { const alertTypes = subscription?.alertTypes ?? []; return options.filter(({ alertType }) => @@ -162,7 +162,7 @@ const AlertTypeSelector = ({ ); }); }} - value={values} + value={selectedAlertTypes} /> ); From 6b6d28c40629f8cd4b62bd35eb5cd894ade50ff3 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 16 Jun 2026 10:41:40 -0400 Subject: [PATCH 48/70] Present system alerts in alert type selector menu --- .../SubscriberInfo/AlertTypeSelector.tsx | 32 +++++++++++++++---- .../Dialog/SelectableAutocompleteOption.tsx | 2 +- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx index 54bb044d8d..ad51b75375 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx @@ -13,6 +13,7 @@ import { useTheme, } from '@mui/material'; +import { Lock } from 'iconoir-react'; import { union } from 'lodash'; import { useIntl } from 'react-intl'; @@ -96,20 +97,37 @@ const AlertTypeSelector = ({ const { key, ...restRenderOptionProps } = renderOptionProps; const { description, displayName, isSystem } = option; - return isSystem ? null : ( + return ( - - {displayName} - + + {displayName} + + + {isSystem ? ( + + ) : null} + {description ? ( )} - {Content} + {Content} ); }; From 1a1cfd7de007432c84e1c141ace72803e915a232 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 16 Jun 2026 10:46:50 -0400 Subject: [PATCH 49/70] Replace instance of depreciated InputProps prop in AlertTypeSelector --- .../SubscriberInfo/AlertTypeSelector.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx index ad51b75375..5741801c77 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx @@ -82,9 +82,11 @@ const AlertTypeSelector = ({ }: AutocompleteRenderInputParams) => ( Date: Thu, 18 Jun 2026 15:12:14 -0400 Subject: [PATCH 50/70] Upsert alert config --- src/api/alerts.ts | 21 +++++ .../PrefixAlerts/Dialog/DeleteButton.tsx | 4 +- .../PrefixAlerts/Dialog/SaveButton.tsx | 4 +- .../admin/Settings/PrefixAlerts/types.ts | 14 ++- ...scription.ts => useModifyAlertMetadata.ts} | 45 +++++++++- .../PrefixAlerts/useUpsertAlertConfig.ts | 85 +++++++++++++++++++ src/context/URQL.tsx | 3 + src/types/gql.ts | 8 ++ src/utils/notification-utils.ts | 14 +-- 9 files changed, 180 insertions(+), 18 deletions(-) rename src/components/admin/Settings/PrefixAlerts/{useModifyAlertSubscription.ts => useModifyAlertMetadata.ts} (72%) create mode 100644 src/components/admin/Settings/PrefixAlerts/useUpsertAlertConfig.ts diff --git a/src/api/alerts.ts b/src/api/alerts.ts index 13c0fb0650..a12a1cb6d4 100644 --- a/src/api/alerts.ts +++ b/src/api/alerts.ts @@ -7,6 +7,7 @@ import type { AlertSubscription as LegacyAlertSubscription, } from 'src/types'; import type { + AlertConfigMutationInput, AlertConfigQueryInput, AlertSubscriptionMutationInput, AlertSubscriptionsBy, @@ -58,6 +59,25 @@ const AlertConfigQuery = gql` } `; +const AlertConfigUpdateMutation = gql< + { catalogPrefixOrName: string }, + AlertConfigMutationInput +>` + mutation UpdateAlertConfigMutation( + $catalogPrefixOrName: Prefix! + $config: JSON! + $detail: String + ) { + updateAlertConfig( + catalogPrefixOrName: $catalogPrefixOrName + config: $config + detail: $detail + ) { + catalogPrefixOrName + } + } +`; + const AlertSubscriptionCreateMutation = gql< { catalogPrefix: string; email: string }, AlertSubscriptionMutationInput @@ -213,6 +233,7 @@ const getTaskNotification = async (catalogName: string) => { export { AlertConfigQuery, + AlertConfigUpdateMutation, AlertSubscriptionCreateMutation, AlertSubscriptionDeleteMutation, AlertSubscriptionQuery, diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx index f5e43a4998..c2d5ffe9b6 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx @@ -7,12 +7,12 @@ import { Button } from '@mui/material'; import { useIntl } from 'react-intl'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; -import { useModifyAlertSubscription } from 'src/components/admin/Settings/PrefixAlerts/useModifyAlertSubscription'; +import { useModifyAlertMetadata } from 'src/components/admin/Settings/PrefixAlerts/useModifyAlertMetadata'; import { hasOwnProperty } from 'src/utils/misc-utils'; const DeleteButton = ({ closeDialog }: DialogActionProps) => { const intl = useIntl(); - const { loading, onClick } = useModifyAlertSubscription(closeDialog, true); + const { loading, onClick } = useModifyAlertMetadata(closeDialog, true); const errorsExist = useAlertSubscriptionsStore( (state) => state.emailErrorsExist || state.prefixErrorsExist diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SaveButton.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SaveButton.tsx index 5647a464e3..1e05e3a164 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SaveButton.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SaveButton.tsx @@ -7,12 +7,12 @@ import { Button } from '@mui/material'; import { useIntl } from 'react-intl'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; -import { useModifyAlertSubscription } from 'src/components/admin/Settings/PrefixAlerts/useModifyAlertSubscription'; +import { useModifyAlertMetadata } from 'src/components/admin/Settings/PrefixAlerts/useModifyAlertMetadata'; import { hasOwnProperty } from 'src/utils/misc-utils'; const SaveButton = ({ closeDialog }: DialogActionProps) => { const intl = useIntl(); - const { loading, onClick } = useModifyAlertSubscription(closeDialog); + const { loading, onClick } = useModifyAlertMetadata(closeDialog); const errorsExist = useAlertSubscriptionsStore( (state) => state.emailErrorsExist || state.prefixErrorsExist diff --git a/src/components/admin/Settings/PrefixAlerts/types.ts b/src/components/admin/Settings/PrefixAlerts/types.ts index d4c1283ad6..380b77f531 100644 --- a/src/components/admin/Settings/PrefixAlerts/types.ts +++ b/src/components/admin/Settings/PrefixAlerts/types.ts @@ -6,6 +6,15 @@ import type { Schema } from 'src/types'; import type { BaseAlertSubscriptionMutationInput } from 'src/types/gql'; import type { AlertConfigKeys } from 'src/utils/notification-utils'; +export interface AlertConfigResponse extends AlertMetadataErrorResponse { + prefix: string; +} + +interface AlertMetadataErrorResponse { + error?: any; + invalid?: boolean; +} + export interface AlertSubscriptionDialogProps extends PrefixFieldProps { descriptionId: string; headerId: string; @@ -15,10 +24,9 @@ export interface AlertSubscriptionDialogProps extends PrefixFieldProps { } export interface AlertSubscriptionResponse - extends BaseAlertSubscriptionMutationInput { + extends AlertMetadataErrorResponse, + BaseAlertSubscriptionMutationInput { id: string; - error?: any; - invalid?: boolean; } // TODO: Remove and replace type with instance of SubscriptionDependentProps. diff --git a/src/components/admin/Settings/PrefixAlerts/useModifyAlertSubscription.ts b/src/components/admin/Settings/PrefixAlerts/useModifyAlertMetadata.ts similarity index 72% rename from src/components/admin/Settings/PrefixAlerts/useModifyAlertSubscription.ts rename to src/components/admin/Settings/PrefixAlerts/useModifyAlertMetadata.ts index 1dd133779a..20286bc51f 100644 --- a/src/components/admin/Settings/PrefixAlerts/useModifyAlertSubscription.ts +++ b/src/components/admin/Settings/PrefixAlerts/useModifyAlertMetadata.ts @@ -1,5 +1,6 @@ import type { PostgrestError } from '@supabase/postgrest-js'; import type { AlertSubscriptionResponse } from 'src/components/admin/Settings/PrefixAlerts/types'; +import type { CombinedError } from 'urql'; import { useState } from 'react'; @@ -7,11 +8,12 @@ import { useIntl } from 'react-intl'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; import { useDeleteAlertSubscription } from 'src/components/admin/Settings/PrefixAlerts/useDeleteAlertSubscription'; +import { useUpsertAlertConfig } from 'src/components/admin/Settings/PrefixAlerts/useUpsertAlertConfig'; import { useUpsertAlertSubscription } from 'src/components/admin/Settings/PrefixAlerts/useUpsertAlertSubscription'; import { BASE_ERROR } from 'src/services/supabase'; import { hasOwnProperty, isPromiseFulfilledResult } from 'src/utils/misc-utils'; -export function useModifyAlertSubscription( +export function useModifyAlertMetadata( closeDialog: () => void, deletionTrigger?: boolean ) { @@ -20,6 +22,8 @@ export function useModifyAlertSubscription( const { upsertSubscription } = useUpsertAlertSubscription(); const { deleteSubscription } = useDeleteAlertSubscription(); + const { upsertConfig } = useUpsertAlertConfig(); + const setServerError = useAlertSubscriptionsStore( (state) => state.setSaveErrors ); @@ -71,7 +75,7 @@ export function useModifyAlertSubscription( } ); - const serverErrors: PostgrestError[] = []; + const serverErrors: (PostgrestError | CombinedError)[] = []; Promise.allSettled(subscriptionQueries).then( (responses) => { @@ -85,7 +89,7 @@ export function useModifyAlertSubscription( // TODO: Detect single subscription deletions when evaluating the operation performed. // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition const serverError = - response.value?.invalid && response.value?.error + response.value?.invalid && !response.value?.error ? { ...BASE_ERROR, message: intl.formatMessage( @@ -103,7 +107,9 @@ export function useModifyAlertSubscription( } : response.value?.error; - serverErrors.push(serverError); + if (serverError) { + serverErrors.push(serverError); + } } else { // TODO: Add LogRocket event for this error scenario. } @@ -114,6 +120,37 @@ export function useModifyAlertSubscription( } ); + const configResponse = await upsertConfig({ + catalogPrefixOrName: catalogPrefix, + config: mutableSubscriptionMetadata[catalogPrefix].settings, + }); + + if (configResponse?.error || configResponse?.invalid) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + const serverError = + configResponse?.invalid && !configResponse?.error + ? { + ...BASE_ERROR, + message: intl.formatMessage( + { + id: 'alerts.config.dialog.error.generic', + }, + { + operation: intl.formatMessage({ + id: deletionTrigger + ? 'alerts.config.dialog.error.term.delete' + : 'alerts.config.dialog.error.term.modify', + }), + } + ), + } + : configResponse?.error; + + if (serverError) { + serverErrors.push(serverError); + } + } + if (serverErrors.length === 0) { closeDialog(); } diff --git a/src/components/admin/Settings/PrefixAlerts/useUpsertAlertConfig.ts b/src/components/admin/Settings/PrefixAlerts/useUpsertAlertConfig.ts new file mode 100644 index 0000000000..160e36b8fd --- /dev/null +++ b/src/components/admin/Settings/PrefixAlerts/useUpsertAlertConfig.ts @@ -0,0 +1,85 @@ +import type { AlertConfigResponse } from 'src/components/admin/Settings/PrefixAlerts/types'; +import type { AlertConfigMutationInput } from 'src/types/gql'; + +import { useCallback } from 'react'; + +import { useMutation } from 'urql'; + +import { AlertConfigUpdateMutation } from 'src/api/alerts'; +import { logRocketEvent } from 'src/services/shared'; + +export function useUpsertAlertConfig() { + const [upsertConfigResult, mutateUpsertConfig] = useMutation( + AlertConfigUpdateMutation + ); + + const upsertConfig = useCallback( + async ({ + catalogPrefixOrName, + config, + detail, + }: AlertConfigMutationInput): Promise => { + return mutateUpsertConfig({ + catalogPrefixOrName, + config, + detail, + }).then( + (response) => { + if (response?.error) { + logRocketEvent('AlertConfig', { + errorResponse: response.error, + operation: 'upsert', + variables: response.operation.variables, + }); + + const { catalogPrefixOrName: responseVariablePrefix } = + response.operation.variables; + + return Promise.resolve({ + error: response.error, + invalid: true, + prefix: responseVariablePrefix, + }); + } + + if (!response || !response?.data) { + logRocketEvent('AlertConfig', { + missingResponseData: !response.data, + operation: 'upsert', + variables: response.operation.variables, + }); + + const { catalogPrefixOrName: responseVariablePrefix } = + response.operation.variables; + + return Promise.resolve({ + invalid: true, + prefix: responseVariablePrefix, + }); + } + + const { catalogPrefixOrName: responsePrefix } = + response.data; + + return Promise.resolve({ + prefix: responsePrefix, + }); + }, + () => { + logRocketEvent('AlertConfig', { + operation: 'upsert', + promiseRejected: 'explicit', + }); + + return Promise.reject(); + } + ); + }, + [mutateUpsertConfig] + ); + + return { + upsertConfig, + upsertConfigResult, + }; +} diff --git a/src/context/URQL.tsx b/src/context/URQL.tsx index ef31588479..acad39c4d3 100644 --- a/src/context/URQL.tsx +++ b/src/context/URQL.tsx @@ -74,6 +74,9 @@ function UrqlConfigProvider({ children }: BaseComponentProps) { deleteInviteLink(_result, _args, cache) { invalidateQuery(cache, 'inviteLinks'); }, + updateAlertConfig(_result, _args, cache) { + invalidateQuery(cache, 'alertConfigs'); + }, createAlertSubscription(_result, _args, cache) { invalidateQuery(cache, 'alertSubscriptions'); }, diff --git a/src/types/gql.ts b/src/types/gql.ts index 0b060e246f..2b18a33dcd 100644 --- a/src/types/gql.ts +++ b/src/types/gql.ts @@ -4,6 +4,7 @@ import type { AlertTypeInfo, } from 'src/gql-types/graphql'; import type { ShardEntityTypes } from 'src/stores/ShardDetail/types'; +import type { Schema } from 'src/types/index'; export interface AlertDetailsRecipients { email: string; @@ -134,6 +135,13 @@ export interface AlertSubscription extends BaseFields { email: string; } +// This interface is used for the upsert alert config mutation. +export interface AlertConfigMutationInput { + catalogPrefixOrName: string; + config: Schema; + detail?: string; +} + // This interface is used for create and update alert subscription mutations. export interface AlertSubscriptionMutationInput extends BaseAlertSubscriptionMutationInput { diff --git a/src/utils/notification-utils.ts b/src/utils/notification-utils.ts index bb10f043e5..3ba3c817dc 100644 --- a/src/utils/notification-utils.ts +++ b/src/utils/notification-utils.ts @@ -56,13 +56,13 @@ export const fromUnconventionalTimeFormat = (value: string | undefined) => { return '12:00:00'; case '24h': return '24:00:00'; - case '1d': + case '1day': return '24:00:00'; - case '2d': + case '2days': return '2 days'; - case '3d': + case '3days': return '3 days'; - case '7d': + case '7days': return '7 days'; default: return 'none'; @@ -84,11 +84,11 @@ export const toUnconventionalTimeFormat = (value: string | undefined) => { case '24:00:00': return '24h'; case '2 days': - return '2d'; + return '2days'; case '3 days': - return '3d'; + return '3days'; case '7 days': - return '7d'; + return '7days'; default: return 'none'; } From 0b7905f41ee6113170157555cc938076e2381d4a Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Mon, 22 Jun 2026 11:36:32 -0400 Subject: [PATCH 51/70] Remove multiple prefix support --- .../PrefixAlerts/Dialog/DeleteButton.tsx | 8 +- .../Dialog/GlobalSettings/index.tsx | 13 +- .../PrefixAlerts/Dialog/SaveButton.tsx | 10 +- .../Dialog/SubscriberSection/index.tsx | 24 +- .../Settings/PrefixAlerts/Dialog/index.tsx | 12 - .../admin/Settings/PrefixAlerts/types.ts | 1 - .../useAlertSubscriptionsStore.ts | 273 +++++++----------- .../useEvaluateSubscriptionIneligibility.ts | 15 +- .../PrefixAlerts/useInitializeAlertConfigs.ts | 4 +- .../PrefixAlerts/useModifyAlertMetadata.ts | 38 +-- .../tables/cells/prefixAlerts/EditButton.tsx | 1 - 11 files changed, 141 insertions(+), 258 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx index c2d5ffe9b6..9ff6621630 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx @@ -8,7 +8,6 @@ import { useIntl } from 'react-intl'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; import { useModifyAlertMetadata } from 'src/components/admin/Settings/PrefixAlerts/useModifyAlertMetadata'; -import { hasOwnProperty } from 'src/utils/misc-utils'; const DeleteButton = ({ closeDialog }: DialogActionProps) => { const intl = useIntl(); @@ -26,12 +25,7 @@ const DeleteButton = ({ closeDialog }: DialogActionProps) => { ); const disabled = useMemo(() => { - const subscriptions = hasOwnProperty( - mutableSubscriptionMetadata, - catalogPrefix - ) - ? mutableSubscriptionMetadata[catalogPrefix].subscriptions - : []; + const { subscriptions } = mutableSubscriptionMetadata; const emptyEmailExists = subscriptions.some( ({ email }) => email.length === 0 diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx index 119f6c8310..de93f40bbb 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx @@ -1,5 +1,3 @@ -import { useMemo } from 'react'; - import { Stack, Typography } from '@mui/material'; import { useIntl } from 'react-intl'; @@ -7,7 +5,6 @@ import { useIntl } from 'react-intl'; import DataMovementSetting from 'src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; import { defaultOutline } from 'src/context/Theme'; -import { hasOwnProperty } from 'src/utils/misc-utils'; import { AlertConfigKeys } from 'src/utils/notification-utils'; const GlobalSettings = () => { @@ -20,14 +17,6 @@ const GlobalSettings = () => { (state) => state.mutableSubscriptionMetadata ); - const targetGlobalSettings = useMemo( - () => - hasOwnProperty(mutableSubscriptionMetadata, catalogPrefix) - ? mutableSubscriptionMetadata[catalogPrefix].settings - : {}, - [catalogPrefix, mutableSubscriptionMetadata] - ); - return ( { { const intl = useIntl(); @@ -22,12 +21,9 @@ const SaveButton = ({ closeDialog }: DialogActionProps) => { (state) => state.catalogPrefix ); const emptyEmailExists = useAlertSubscriptionsStore((state) => - state.catalogPrefix.length > 0 && - hasOwnProperty(state.mutableSubscriptionMetadata, state.catalogPrefix) - ? state.mutableSubscriptionMetadata[ - state.catalogPrefix - ].subscriptions.some(({ email }) => email.length === 0) - : false + state.mutableSubscriptionMetadata.subscriptions.some( + ({ email }) => email.length === 0 + ) ); const disabled = useMemo( diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx index 6a43032f28..847d8f7c5b 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index.tsx @@ -11,16 +11,12 @@ import SubscriberInfo from 'src/components/admin/Settings/PrefixAlerts/Dialog/Su import SummaryEmpty from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/SummaryEmpty'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; import { useGetAlertTypes } from 'src/context/AlertType'; -import { hasOwnProperty } from 'src/utils/misc-utils'; const SubscriberSection = () => { const intl = useIntl(); const [{ fetching, data, error }] = useGetAlertTypes(); - const catalogPrefix = useAlertSubscriptionsStore( - (state) => state.catalogPrefix - ); const mutableSubscriptionMetadata = useAlertSubscriptionsStore( (state) => state.mutableSubscriptionMetadata ); @@ -32,17 +28,15 @@ const SubscriberSection = () => { (state) => state.initializeAlertTypeOptions ); - const targetSubscriptionMetadata: SubscriptionMetadata = useMemo(() => { - if (hasOwnProperty(mutableSubscriptionMetadata, catalogPrefix)) { - return { - ...mutableSubscriptionMetadata[catalogPrefix], - subscriptions: mutableSubscriptionMetadata[ - catalogPrefix - ].subscriptions.filter(({ deleted }) => !deleted), - }; - } - return { settings: {}, subscriptions: [] }; - }, [catalogPrefix, mutableSubscriptionMetadata]); + const targetSubscriptionMetadata: SubscriptionMetadata = useMemo( + () => ({ + ...mutableSubscriptionMetadata, + subscriptions: mutableSubscriptionMetadata.subscriptions.filter( + ({ deleted }) => !deleted + ), + }), + [mutableSubscriptionMetadata] + ); useEffect(() => { if (error) { diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx index 391d51f501..be288d91b7 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx @@ -1,7 +1,5 @@ import type { AlertSubscriptionDialogProps } from 'src/components/admin/Settings/PrefixAlerts/types'; -import { useEffect } from 'react'; - import { Box, Button, @@ -29,7 +27,6 @@ const TITLE_ID = 'alert-subscription-dialog-title'; const AlertSubscriptionDialog = ({ descriptionId, - enableDeletion, headerId, open, setOpen, @@ -39,9 +36,6 @@ const AlertSubscriptionDialog = ({ useInitializeAlertConfigs(); - const initializeMutableSubscriptionMetadata = useAlertSubscriptionsStore( - (state) => state.initializeMutableSubscriptionMetadata - ); const resetSubscriptionState = useAlertSubscriptionsStore( (state) => state.resetState ); @@ -51,12 +45,6 @@ const AlertSubscriptionDialog = ({ resetSubscriptionState(); }; - useEffect(() => { - if (open) { - initializeMutableSubscriptionMetadata(); - } - }, [initializeMutableSubscriptionMetadata, open]); - useUnmount(() => { resetSubscriptionState(); }); diff --git a/src/components/admin/Settings/PrefixAlerts/types.ts b/src/components/admin/Settings/PrefixAlerts/types.ts index 380b77f531..1115a146b4 100644 --- a/src/components/admin/Settings/PrefixAlerts/types.ts +++ b/src/components/admin/Settings/PrefixAlerts/types.ts @@ -20,7 +20,6 @@ export interface AlertSubscriptionDialogProps extends PrefixFieldProps { headerId: string; open: boolean; setOpen: Dispatch>; - enableDeletion?: boolean; } export interface AlertSubscriptionResponse diff --git a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts index f18312385d..84a7ece692 100644 --- a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts +++ b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts @@ -1,15 +1,18 @@ import type { PostgrestError } from '@supabase/postgrest-js'; import type { ReducedAlertSubscription } from 'src/api/types'; -import type { SubscriptionMetadataDictionary } from 'src/components/admin/Settings/PrefixAlerts/types'; +import type { + SubscriptionMetadata, + SubscriptionMetadataDictionary, +} from 'src/components/admin/Settings/PrefixAlerts/types'; import type { AlertTypeInfo } from 'src/gql-types/graphql'; import type { Schema } from 'src/types'; -import type { CombinedError } from 'urql'; import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; import produce from 'immer'; -import { cloneDeep, isEmpty, omit } from 'lodash'; +import { isEmpty, omit } from 'lodash'; +import { type CombinedError } from 'urql'; import { hasOwnProperty } from 'src/utils/misc-utils'; import { bundleSubscriptionsByPrefix } from 'src/utils/notification-utils'; @@ -26,12 +29,12 @@ interface AlertSubscriptionState { values: AlertTypeInfo[], fetching: boolean ) => void; - initializeMutableSubscriptionMetadata: () => void; + initializeMutableSubscriptionMetadata: (catalogPrefix: string) => void; markSubscriptionForDeletion: ( catalogPrefix: string, subscriptionId: string ) => void; - mutableSubscriptionMetadata: SubscriptionMetadataDictionary; + mutableSubscriptionMetadata: SubscriptionMetadata; prefixErrorsExist: boolean; saveErrors: (CombinedError | PostgrestError | null | undefined)[]; subscription: Pick< @@ -59,26 +62,21 @@ interface AlertSubscriptionState { toggleSubscriptionViewingStatus: (subscriptionId: string) => void; } -const getSubscriptionIndex = ( +const getImmutableSubscriptionIndex = ( state: AlertSubscriptionState | Partial, - subscriptionId: string, - immutable?: boolean + subscriptionId: string ): number => { - const subscriptionMetadataTarget = immutable - ? 'subscriptionMetadata' - : 'mutableSubscriptionMetadata'; - if ( !state.catalogPrefix || !subscriptionId || - !hasOwnProperty(state, subscriptionMetadataTarget) || - isEmpty(state[subscriptionMetadataTarget]) || - !hasOwnProperty(state[subscriptionMetadataTarget], state.catalogPrefix) + !hasOwnProperty(state, 'subscriptionMetadata') || + isEmpty(state.subscriptionMetadata) || + !hasOwnProperty(state.subscriptionMetadata, state.catalogPrefix) ) { return -1; } - return state[subscriptionMetadataTarget][ + return state.subscriptionMetadata[ state.catalogPrefix ].subscriptions.findIndex( (subscription) => subscription.id === subscriptionId @@ -103,7 +101,7 @@ const getInitialState = (): Pick< catalogPrefix: '', emailErrorsExist: false, initializationError: null, - mutableSubscriptionMetadata: {}, + mutableSubscriptionMetadata: { settings: {}, subscriptions: [] }, prefixErrorsExist: false, saveErrors: [], subscription: { alertTypes: [], catalogPrefix: '', email: '' }, @@ -140,32 +138,9 @@ const useAlertSubscriptionsStore = create()( viewing: true, }; - if ( - !hasOwnProperty( - state.mutableSubscriptionMetadata, - state.catalogPrefix - ) - ) { - state.mutableSubscriptionMetadata[ - state.catalogPrefix - ] = { - settings: {}, - subscriptions: [templatedSubscription], - }; - - return; - } - - const targetSubscriptions = - state.mutableSubscriptionMetadata[ - state.catalogPrefix - ].subscriptions; - - state.mutableSubscriptionMetadata[ - state.catalogPrefix - ].subscriptions = [ + state.mutableSubscriptionMetadata.subscriptions = [ templatedSubscription, - ...targetSubscriptions, + ...state.mutableSubscriptionMetadata.subscriptions, ]; }), false, @@ -185,43 +160,16 @@ const useAlertSubscriptionsStore = create()( setGlobalPrefixSettings: (value, targetSetting) => set( produce((state: AlertSubscriptionState) => { - if (!state.catalogPrefix) { - return; - } - - const valueEmpty = isEmpty(value); - - if ( - !hasOwnProperty( - state.mutableSubscriptionMetadata, - state.catalogPrefix - ) && - !valueEmpty - ) { - state.mutableSubscriptionMetadata[ - state.catalogPrefix - ] = { - settings: value, - subscriptions: [], - }; - - return; - } - - state.mutableSubscriptionMetadata[ - state.catalogPrefix - ].settings = - valueEmpty && targetSetting + state.mutableSubscriptionMetadata.settings = + isEmpty(value) && targetSetting ? omit( - state.mutableSubscriptionMetadata[ - state.catalogPrefix - ].settings, + state.mutableSubscriptionMetadata + .settings, targetSetting ) : { - ...state.mutableSubscriptionMetadata[ - state.catalogPrefix - ].settings, + ...state.mutableSubscriptionMetadata + .settings, ...value, }; }), @@ -232,9 +180,23 @@ const useAlertSubscriptionsStore = create()( initializeMutableSubscriptionMetadata: () => set( produce((state: AlertSubscriptionState) => { - state.mutableSubscriptionMetadata = cloneDeep( - state.subscriptionMetadata - ); + if ( + state.catalogPrefix.length === 0 || + !hasOwnProperty( + state.subscriptionMetadata, + state.catalogPrefix + ) + ) { + // state.mutableSubscriptionMetadata = { + // settings: {}, + // subscriptions: [], + // }; + + return; + } + + state.mutableSubscriptionMetadata = + state.subscriptionMetadata[state.catalogPrefix]; }), false, 'mutable subscription metadata initialized' @@ -243,32 +205,20 @@ const useAlertSubscriptionsStore = create()( markSubscriptionForDeletion: (_catalogPrefix, subscriptionId) => set( produce((state: AlertSubscriptionState) => { - const mutableSubscriptionIndex = getSubscriptionIndex( - state, - subscriptionId - ); - - if (mutableSubscriptionIndex === -1) { - return; - } - - const immutableSubscriptionIndex = getSubscriptionIndex( - state, - subscriptionId, - true - ); + const immutableSubscriptionIndex = + getImmutableSubscriptionIndex( + state, + subscriptionId + ); // If the alert subscription does not exist in the database, // it can be removed entirely from the array of mutable subscriptions. if (immutableSubscriptionIndex === -1) { - state.mutableSubscriptionMetadata[ - state.catalogPrefix - ].subscriptions = state.mutableSubscriptionMetadata[ - state.catalogPrefix - ].subscriptions.filter( - (subscription) => - subscription.id !== subscriptionId - ); + state.mutableSubscriptionMetadata.subscriptions = + state.mutableSubscriptionMetadata.subscriptions.filter( + (subscription) => + subscription.id !== subscriptionId + ); return; } @@ -277,10 +227,15 @@ const useAlertSubscriptionsStore = create()( // the subscription metadata should be preserved in the // array of mutable subscriptions with it marked for deletion // via the corresponding GraphQL endpoint. - state.mutableSubscriptionMetadata[ - state.catalogPrefix - ].subscriptions[mutableSubscriptionIndex].deleted = - true; + const mutableSubscriptionIndex = + state.mutableSubscriptionMetadata.subscriptions.findIndex( + (subscription) => + subscription.id === subscriptionId + ); + + state.mutableSubscriptionMetadata.subscriptions[ + mutableSubscriptionIndex + ].deleted = true; }), false, 'mark subscription for deletion' @@ -309,58 +264,31 @@ const useAlertSubscriptionsStore = create()( ({ alertType: name }) => name ); - if ( - catalogPrefix.length === 0 || - !hasOwnProperty( - state.mutableSubscriptionMetadata, - catalogPrefix - ) - ) { - state.mutableSubscriptionMetadata[catalogPrefix] = { - settings: {}, - subscriptions: [ - { - alertTypes, - catalogPrefix, - email, - id: crypto.randomUUID(), - viewing: true, - }, - ], - }; - - return; - } - const targetSubscriptions = - state.mutableSubscriptionMetadata[catalogPrefix] - .subscriptions; + state.mutableSubscriptionMetadata.subscriptions; const targetIndex = targetSubscriptions.findIndex( (subscription) => subscription.email === email ); if (targetIndex === -1) { - state.mutableSubscriptionMetadata[catalogPrefix] = { - settings: {}, - subscriptions: [ - ...targetSubscriptions, - { - alertTypes, - catalogPrefix, - email, - id: crypto.randomUUID(), - viewing: true, - }, - ], - }; + state.mutableSubscriptionMetadata.subscriptions = [ + ...targetSubscriptions, + { + alertTypes, + catalogPrefix, + email, + id: crypto.randomUUID(), + viewing: true, + }, + ]; return; } - state.mutableSubscriptionMetadata[ - catalogPrefix - ].subscriptions[targetIndex].alertTypes = alertTypes; + state.mutableSubscriptionMetadata.subscriptions[ + targetIndex + ].alertTypes = alertTypes; }), false, 'alert types set' @@ -396,18 +324,19 @@ const useAlertSubscriptionsStore = create()( setSubscribedEmail: (value, subscriptionId) => set( produce((state: AlertSubscriptionState) => { - const subscriptionIndex = getSubscriptionIndex( - state, - subscriptionId - ); + const subscriptionIndex = + state.mutableSubscriptionMetadata.subscriptions.findIndex( + (subscription) => + subscription.id === subscriptionId + ); if (subscriptionIndex === -1) { return; } - state.mutableSubscriptionMetadata[ - state.catalogPrefix - ].subscriptions[subscriptionIndex].email = value; + state.mutableSubscriptionMetadata.subscriptions[ + subscriptionIndex + ].email = value; }), false, 'subscribed email set' @@ -421,6 +350,24 @@ const useAlertSubscriptionsStore = create()( ? value : `${value}/`; state.prefixErrorsExist = Boolean(errors); + + if ( + value.length > 0 && + hasOwnProperty(state.subscriptionMetadata, value) + ) { + state.mutableSubscriptionMetadata = + state.subscriptionMetadata[value]; + + return; + } + + state.mutableSubscriptionMetadata.subscriptions = + state.mutableSubscriptionMetadata.subscriptions.map( + (subscription) => ({ + ...subscription, + catalogPrefix: state.catalogPrefix, + }) + ); }), false, 'subscribed prefix set' @@ -439,24 +386,24 @@ const useAlertSubscriptionsStore = create()( toggleSubscriptionViewingStatus: (subscriptionId) => set( produce((state: AlertSubscriptionState) => { - const subscriptionIndex = getSubscriptionIndex( - state, - subscriptionId - ); + const subscriptionIndex = + state.mutableSubscriptionMetadata.subscriptions.findIndex( + (subscription) => + subscription.id === subscriptionId + ); if (subscriptionIndex === -1) { return; } const previousValue = - state.mutableSubscriptionMetadata[ - state.catalogPrefix - ].subscriptions[subscriptionIndex].viewing; + state.mutableSubscriptionMetadata.subscriptions[ + subscriptionIndex + ].viewing; - state.mutableSubscriptionMetadata[ - state.catalogPrefix - ].subscriptions[subscriptionIndex].viewing = - !previousValue; + state.mutableSubscriptionMetadata.subscriptions[ + subscriptionIndex + ].viewing = !previousValue; }), false, 'subscription viewing status toggled' diff --git a/src/components/admin/Settings/PrefixAlerts/useEvaluateSubscriptionIneligibility.ts b/src/components/admin/Settings/PrefixAlerts/useEvaluateSubscriptionIneligibility.ts index 584212ed02..0579bb6704 100644 --- a/src/components/admin/Settings/PrefixAlerts/useEvaluateSubscriptionIneligibility.ts +++ b/src/components/admin/Settings/PrefixAlerts/useEvaluateSubscriptionIneligibility.ts @@ -1,25 +1,14 @@ import { useMemo } from 'react'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; -import { hasOwnProperty } from 'src/utils/misc-utils'; export function useEvaluateSubscriptionIneligibility() { - const catalogPrefix = useAlertSubscriptionsStore( - (state) => state.catalogPrefix - ); const mutableSubscriptionMetadata = useAlertSubscriptionsStore( (state) => state.mutableSubscriptionMetadata ); return useMemo(() => { - if (!hasOwnProperty(mutableSubscriptionMetadata, catalogPrefix)) { - return { - duplicateSubscriptionEmails: [], - emptyEmailDetected: false, - }; - } - - const { subscriptions } = mutableSubscriptionMetadata[catalogPrefix]; + const { subscriptions } = mutableSubscriptionMetadata; const emptyEmailDetected = subscriptions.some( (subscription) => subscription.email.length === 0 @@ -42,5 +31,5 @@ export function useEvaluateSubscriptionIneligibility() { duplicateSubscriptionEmails, emptyEmailDetected, }; - }, [catalogPrefix, mutableSubscriptionMetadata]); + }, [mutableSubscriptionMetadata]); } diff --git a/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts b/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts index 13656013b9..7be7ac607d 100644 --- a/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts +++ b/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts @@ -5,7 +5,6 @@ import { useQuery } from 'urql'; import { AlertConfigQuery } from 'src/api/alerts'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; -import { hasOwnProperty } from 'src/utils/misc-utils'; // TODO: Figure out the best way to display a loading state for each global // setting component. @@ -31,8 +30,7 @@ export function useInitializeAlertConfigs() { const settingsDefined = useMemo( () => debouncedPrefix.length > 0 && - hasOwnProperty(mutableSubscriptionMetadata, debouncedPrefix) && - !isEmpty(mutableSubscriptionMetadata[debouncedPrefix].settings), + !isEmpty(mutableSubscriptionMetadata.settings), [debouncedPrefix, mutableSubscriptionMetadata] ); diff --git a/src/components/admin/Settings/PrefixAlerts/useModifyAlertMetadata.ts b/src/components/admin/Settings/PrefixAlerts/useModifyAlertMetadata.ts index 20286bc51f..9cb9f95289 100644 --- a/src/components/admin/Settings/PrefixAlerts/useModifyAlertMetadata.ts +++ b/src/components/admin/Settings/PrefixAlerts/useModifyAlertMetadata.ts @@ -11,7 +11,7 @@ import { useDeleteAlertSubscription } from 'src/components/admin/Settings/Prefix import { useUpsertAlertConfig } from 'src/components/admin/Settings/PrefixAlerts/useUpsertAlertConfig'; import { useUpsertAlertSubscription } from 'src/components/admin/Settings/PrefixAlerts/useUpsertAlertSubscription'; import { BASE_ERROR } from 'src/services/supabase'; -import { hasOwnProperty, isPromiseFulfilledResult } from 'src/utils/misc-utils'; +import { isPromiseFulfilledResult } from 'src/utils/misc-utils'; export function useModifyAlertMetadata( closeDialog: () => void, @@ -47,33 +47,23 @@ export function useModifyAlertMetadata( return Promise.reject('Catalog prefix undefined.'); } - if ( - !hasOwnProperty(mutableSubscriptionMetadata, catalogPrefix) || - mutableSubscriptionMetadata[catalogPrefix].subscriptions.length === - 0 - ) { + if (mutableSubscriptionMetadata.subscriptions.length === 0) { // TODO: Add LogRocket event for this success scenario. return Promise.resolve(); } const subscriptionQueries: Promise[] = - mutableSubscriptionMetadata[catalogPrefix].subscriptions.map( - (subscription) => { - const { - alertTypes, - catalogPrefix: prefix, - email, - } = subscription; - - return deletionTrigger || subscription.deleted - ? deleteSubscription({ email, prefix }) - : upsertSubscription({ - alertTypes, - email, - prefix, - }); - } - ); + mutableSubscriptionMetadata.subscriptions.map((subscription) => { + const { alertTypes, email } = subscription; + + return deletionTrigger || subscription.deleted + ? deleteSubscription({ email, prefix: catalogPrefix }) + : upsertSubscription({ + alertTypes, + email, + prefix: catalogPrefix, + }); + }); const serverErrors: (PostgrestError | CombinedError)[] = []; @@ -122,7 +112,7 @@ export function useModifyAlertMetadata( const configResponse = await upsertConfig({ catalogPrefixOrName: catalogPrefix, - config: mutableSubscriptionMetadata[catalogPrefix].settings, + config: mutableSubscriptionMetadata.settings, }); if (configResponse?.error || configResponse?.invalid) { diff --git a/src/components/tables/cells/prefixAlerts/EditButton.tsx b/src/components/tables/cells/prefixAlerts/EditButton.tsx index a040722cc4..2424008416 100644 --- a/src/components/tables/cells/prefixAlerts/EditButton.tsx +++ b/src/components/tables/cells/prefixAlerts/EditButton.tsx @@ -32,7 +32,6 @@ function AlertEditButton({ Date: Mon, 22 Jun 2026 11:49:55 -0400 Subject: [PATCH 52/70] Display a loading state for DataMovementSetting --- .../GlobalSettings/DataMovementSetting.tsx | 102 ++++++++++-------- .../Dialog/GlobalSettings/index.tsx | 4 + .../Settings/PrefixAlerts/Dialog/index.tsx | 3 - .../admin/Settings/PrefixAlerts/types.ts | 1 + .../useAlertSubscriptionsStore.ts | 26 ----- .../PrefixAlerts/useInitializeAlertConfigs.ts | 4 +- 6 files changed, 65 insertions(+), 75 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx index 970cce4631..5b10668c93 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting.tsx @@ -3,7 +3,13 @@ import type { GlobalSettingProps } from 'src/components/admin/Settings/PrefixAle import React from 'react'; -import { Autocomplete, Stack, TextField, Typography } from '@mui/material'; +import { + Autocomplete, + Skeleton, + Stack, + TextField, + Typography, +} from '@mui/material'; import { useIntl } from 'react-intl'; @@ -15,8 +21,9 @@ import { } from 'src/utils/notification-utils'; const DataMovementSetting = ({ - prefix, config, + loading, + prefix, targetSetting, }: GlobalSettingProps<{ stalledFor: string }>) => { const intl = useIntl(); @@ -43,49 +50,56 @@ const DataMovementSetting = ({ - options[interval]} - onChange={(_event: React.SyntheticEvent, value: string) => { - const formattedValue = toUnconventionalTimeFormat(value); + {loading ? ( + + ) : ( + options[interval]} + onChange={(_event: React.SyntheticEvent, value: string) => { + const formattedValue = + toUnconventionalTimeFormat(value); - setGlobalPrefixSettings( - formattedValue !== 'none' - ? { - [targetSetting]: { - condition: { stalledFor: formattedValue }, - }, - } - : {}, - targetSetting - ); - }} - options={Object.keys(options)} - renderInput={({ - InputProps, - ...params - }: AutocompleteRenderInputParams) => ( - - )} - value={fromUnconventionalTimeFormat( - config?.condition.stalledFor - )} - /> + setGlobalPrefixSettings( + formattedValue !== 'none' + ? { + [targetSetting]: { + condition: { + stalledFor: formattedValue, + }, + }, + } + : {}, + targetSetting + ); + }} + options={Object.keys(options)} + renderInput={({ + InputProps, + ...params + }: AutocompleteRenderInputParams) => ( + + )} + value={fromUnconventionalTimeFormat( + config?.condition.stalledFor + )} + /> + )} ); }; diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx index de93f40bbb..fe2845e2a6 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/index.tsx @@ -4,12 +4,15 @@ import { useIntl } from 'react-intl'; import DataMovementSetting from 'src/components/admin/Settings/PrefixAlerts/Dialog/GlobalSettings/DataMovementSetting'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; +import { useInitializeAlertConfigs } from 'src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs'; import { defaultOutline } from 'src/context/Theme'; import { AlertConfigKeys } from 'src/utils/notification-utils'; const GlobalSettings = () => { const intl = useIntl(); + const { loading: loadingAlertConfigs } = useInitializeAlertConfigs(); + const catalogPrefix = useAlertSubscriptionsStore( (state) => state.catalogPrefix ); @@ -48,6 +51,7 @@ const GlobalSettings = () => { AlertConfigKeys.DATA_MOVEMENT_STALLED ] } + loading={loadingAlertConfigs} prefix={catalogPrefix} targetSetting={AlertConfigKeys.DATA_MOVEMENT_STALLED} /> diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx index be288d91b7..a1ab5bde77 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/index.tsx @@ -19,7 +19,6 @@ import SaveButton from 'src/components/admin/Settings/PrefixAlerts/Dialog/SaveBu import ServerErrors from 'src/components/admin/Settings/PrefixAlerts/Dialog/ServerErrors'; import SubscriberSection from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/index'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; -import { useInitializeAlertConfigs } from 'src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs'; import MessageWithLink from 'src/components/content/MessageWithLink'; import DialogTitleWithClose from 'src/components/shared/Dialog/TitleWithClose'; @@ -34,8 +33,6 @@ const AlertSubscriptionDialog = ({ }: AlertSubscriptionDialogProps) => { const intl = useIntl(); - useInitializeAlertConfigs(); - const resetSubscriptionState = useAlertSubscriptionsStore( (state) => state.resetState ); diff --git a/src/components/admin/Settings/PrefixAlerts/types.ts b/src/components/admin/Settings/PrefixAlerts/types.ts index 1115a146b4..3f4730075e 100644 --- a/src/components/admin/Settings/PrefixAlerts/types.ts +++ b/src/components/admin/Settings/PrefixAlerts/types.ts @@ -62,6 +62,7 @@ interface GlobalSettingConfig { export interface GlobalSettingProps { config: GlobalSettingConfig | undefined; + loading: boolean; prefix: string; targetSetting: AlertConfigKeys; } diff --git a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts index 84a7ece692..3e6e511f37 100644 --- a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts +++ b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts @@ -29,7 +29,6 @@ interface AlertSubscriptionState { values: AlertTypeInfo[], fetching: boolean ) => void; - initializeMutableSubscriptionMetadata: (catalogPrefix: string) => void; markSubscriptionForDeletion: ( catalogPrefix: string, subscriptionId: string @@ -177,31 +176,6 @@ const useAlertSubscriptionsStore = create()( 'global prefix settings set' ), - initializeMutableSubscriptionMetadata: () => - set( - produce((state: AlertSubscriptionState) => { - if ( - state.catalogPrefix.length === 0 || - !hasOwnProperty( - state.subscriptionMetadata, - state.catalogPrefix - ) - ) { - // state.mutableSubscriptionMetadata = { - // settings: {}, - // subscriptions: [], - // }; - - return; - } - - state.mutableSubscriptionMetadata = - state.subscriptionMetadata[state.catalogPrefix]; - }), - false, - 'mutable subscription metadata initialized' - ), - markSubscriptionForDeletion: (_catalogPrefix, subscriptionId) => set( produce((state: AlertSubscriptionState) => { diff --git a/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts b/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts index 7be7ac607d..c3a901e5fb 100644 --- a/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts +++ b/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts @@ -6,8 +6,6 @@ import { useQuery } from 'urql'; import { AlertConfigQuery } from 'src/api/alerts'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; -// TODO: Figure out the best way to display a loading state for each global -// setting component. export function useInitializeAlertConfigs() { const catalogPrefix = useAlertSubscriptionsStore( (state) => state.catalogPrefix @@ -67,4 +65,6 @@ export function useInitializeAlertConfigs() { setGlobalPrefixSettings, settingsDefined, ]); + + return { loading: fetching || !data }; } From c444835edeb7566cb1ec02c0b7b1b081abf1fafe Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Mon, 22 Jun 2026 11:55:26 -0400 Subject: [PATCH 53/70] Use formatted catalog prefix to index subscription metadata on prefix change --- .../Settings/PrefixAlerts/useAlertSubscriptionsStore.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts index 3e6e511f37..da8c76e984 100644 --- a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts +++ b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts @@ -326,11 +326,14 @@ const useAlertSubscriptionsStore = create()( state.prefixErrorsExist = Boolean(errors); if ( - value.length > 0 && - hasOwnProperty(state.subscriptionMetadata, value) + state.catalogPrefix.length > 0 && + hasOwnProperty( + state.subscriptionMetadata, + state.catalogPrefix + ) ) { state.mutableSubscriptionMetadata = - state.subscriptionMetadata[value]; + state.subscriptionMetadata[state.catalogPrefix]; return; } From 8c3c62134020b0a48c30b42045882900266022de Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Mon, 22 Jun 2026 12:50:26 -0400 Subject: [PATCH 54/70] Tightly couple global prefix setting state to prefix input --- .../useAlertSubscriptionsStore.ts | 23 +++++++++++++------ .../PrefixAlerts/useInitializeAlertConfigs.ts | 9 ++++---- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts index da8c76e984..a5e96b14e9 100644 --- a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts +++ b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts @@ -159,6 +159,12 @@ const useAlertSubscriptionsStore = create()( setGlobalPrefixSettings: (value, targetSetting) => set( produce((state: AlertSubscriptionState) => { + if (isEmpty(value) && !targetSetting) { + state.mutableSubscriptionMetadata.settings = {}; + + return; + } + state.mutableSubscriptionMetadata.settings = isEmpty(value) && targetSetting ? omit( @@ -338,13 +344,16 @@ const useAlertSubscriptionsStore = create()( return; } - state.mutableSubscriptionMetadata.subscriptions = - state.mutableSubscriptionMetadata.subscriptions.map( - (subscription) => ({ - ...subscription, - catalogPrefix: state.catalogPrefix, - }) - ); + state.mutableSubscriptionMetadata = { + settings: {}, + subscriptions: + state.mutableSubscriptionMetadata.subscriptions.map( + (subscription) => ({ + ...subscription, + catalogPrefix: state.catalogPrefix, + }) + ), + }; }), false, 'subscribed prefix set' diff --git a/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts b/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts index c3a901e5fb..83b4d6c8ba 100644 --- a/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts +++ b/src/components/admin/Settings/PrefixAlerts/useInitializeAlertConfigs.ts @@ -50,11 +50,12 @@ export function useInitializeAlertConfigs() { debouncedPrefix === catalogPrefix && !settingsDefined && !fetching && - data?.alertConfigs && - data.alertConfigs.edges.length > 0 + data?.alertConfigs ) { setGlobalPrefixSettings( - data.alertConfigs.edges[0].node.effective.config + data.alertConfigs.edges.length > 0 + ? data.alertConfigs.edges[0].node.effective.config + : {} ); } }, [ @@ -66,5 +67,5 @@ export function useInitializeAlertConfigs() { settingsDefined, ]); - return { loading: fetching || !data }; + return { loading: fetching || !data || debouncedPrefix !== catalogPrefix }; } From 1675d7108875653734f89f6585cb534927ffbcb3 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Mon, 22 Jun 2026 13:04:42 -0400 Subject: [PATCH 55/70] Move email error state into MutableAlertSubscription --- .../PrefixAlerts/Dialog/DeleteButton.tsx | 6 +++- .../PrefixAlerts/Dialog/SaveButton.tsx | 6 +++- .../SubscriberInfo/EmailSelector.tsx | 4 +-- .../admin/Settings/PrefixAlerts/types.ts | 1 + .../useAlertSubscriptionsStore.ts | 31 ++++++++++--------- 5 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx index 9ff6621630..983fd6480b 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx @@ -14,7 +14,11 @@ const DeleteButton = ({ closeDialog }: DialogActionProps) => { const { loading, onClick } = useModifyAlertMetadata(closeDialog, true); const errorsExist = useAlertSubscriptionsStore( - (state) => state.emailErrorsExist || state.prefixErrorsExist + (state) => + state.prefixErrorsExist || + state.mutableSubscriptionMetadata.subscriptions.some( + ({ emailErrorsExist }) => emailErrorsExist + ) ); const catalogPrefix = useAlertSubscriptionsStore( diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SaveButton.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SaveButton.tsx index d55056f826..95683b6a98 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SaveButton.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SaveButton.tsx @@ -14,7 +14,11 @@ const SaveButton = ({ closeDialog }: DialogActionProps) => { const { loading, onClick } = useModifyAlertMetadata(closeDialog); const errorsExist = useAlertSubscriptionsStore( - (state) => state.emailErrorsExist || state.prefixErrorsExist + (state) => + state.prefixErrorsExist || + state.mutableSubscriptionMetadata.subscriptions.some( + ({ emailErrorsExist }) => emailErrorsExist + ) ); const catalogPrefix = useAlertSubscriptionsStore( diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailSelector.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailSelector.tsx index 44bac1b1fe..ce22c6d60c 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailSelector.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailSelector.tsx @@ -73,8 +73,8 @@ function EmailSelector({ ); useEffect(() => { - setEmailErrorsExist(inputErrorExists); - }, [inputErrorExists, setEmailErrorsExist]); + setEmailErrorsExist(inputErrorExists, subscriptionId); + }, [inputErrorExists, setEmailErrorsExist, subscriptionId]); return ( diff --git a/src/components/admin/Settings/PrefixAlerts/types.ts b/src/components/admin/Settings/PrefixAlerts/types.ts index 3f4730075e..2137492d92 100644 --- a/src/components/admin/Settings/PrefixAlerts/types.ts +++ b/src/components/admin/Settings/PrefixAlerts/types.ts @@ -71,6 +71,7 @@ export interface MutableAlertSubscription extends ReducedAlertSubscription { id: string; viewing: boolean; deleted?: boolean; + emailErrorsExist?: boolean; } export interface PrefixFieldProps { diff --git a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts index a5e96b14e9..7f0bdfbbbf 100644 --- a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts +++ b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts @@ -23,7 +23,6 @@ interface AlertSubscriptionState { alertTypeOptions: AlertTypeInfo[]; alertTypeOptionsFetching: boolean; catalogPrefix: string; - emailErrorsExist: boolean; initializationError: CombinedError | PostgrestError | null | undefined; initializeAlertTypeOptions: ( values: AlertTypeInfo[], @@ -36,10 +35,6 @@ interface AlertSubscriptionState { mutableSubscriptionMetadata: SubscriptionMetadata; prefixErrorsExist: boolean; saveErrors: (CombinedError | PostgrestError | null | undefined)[]; - subscription: Pick< - ReducedAlertSubscription, - 'alertTypes' | 'catalogPrefix' | 'email' - >; subscriptionMetadata: SubscriptionMetadataDictionary; resetState: () => void; setAlertTypes: ( @@ -47,9 +42,7 @@ interface AlertSubscriptionState { catalogPrefix?: string, email?: string ) => void; - setEmailErrorsExist: ( - value: AlertSubscriptionState['emailErrorsExist'] - ) => void; + setEmailErrorsExist: (value: boolean, subscriptionId: string) => void; setGlobalPrefixSettings: (value: Schema, targetSetting?: string) => void; setInitializationError: ( value: AlertSubscriptionState['initializationError'] @@ -87,23 +80,19 @@ const getInitialState = (): Pick< | 'alertTypeOptions' | 'alertTypeOptionsFetching' | 'catalogPrefix' - | 'emailErrorsExist' | 'initializationError' | 'mutableSubscriptionMetadata' | 'prefixErrorsExist' | 'saveErrors' - | 'subscription' | 'subscriptionMetadata' > => ({ alertTypeOptions: [], alertTypeOptionsFetching: false, catalogPrefix: '', - emailErrorsExist: false, initializationError: null, mutableSubscriptionMetadata: { settings: {}, subscriptions: [] }, prefixErrorsExist: false, saveErrors: [], - subscription: { alertTypes: [], catalogPrefix: '', email: '' }, subscriptionMetadata: {}, }); @@ -274,10 +263,22 @@ const useAlertSubscriptionsStore = create()( 'alert types set' ), - setEmailErrorsExist: (value) => + setEmailErrorsExist: (value, subscriptionId) => set( produce((state: AlertSubscriptionState) => { - state.emailErrorsExist = value; + const subscriptionIndex = + state.mutableSubscriptionMetadata.subscriptions.findIndex( + (subscription) => + subscription.id === subscriptionId + ); + + if (subscriptionIndex === -1) { + return; + } + + state.mutableSubscriptionMetadata.subscriptions[ + subscriptionIndex + ].emailErrorsExist = value; }), false, 'email errors exist set' @@ -325,10 +326,10 @@ const useAlertSubscriptionsStore = create()( setSubscribedPrefix: (value, errors) => set( produce((state: AlertSubscriptionState) => { - state.subscription.catalogPrefix = value; state.catalogPrefix = value.endsWith('/') ? value : `${value}/`; + state.prefixErrorsExist = Boolean(errors); if ( From 06dea8f0df846348f2baceac088b3e1b662b194b Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Mon, 22 Jun 2026 14:20:20 -0400 Subject: [PATCH 56/70] Remove and replace AlertTypeFieldProps with instance of SubscriptionDependentProps --- .../SubscriberSection/SubscriberInfo/AlertTypeField.tsx | 4 ++-- src/components/admin/Settings/PrefixAlerts/types.ts | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeField.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeField.tsx index 8d2afe88d5..857b3ccf8e 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeField.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeField.tsx @@ -1,11 +1,11 @@ -import type { AlertTypeFieldProps } from 'src/components/admin/Settings/PrefixAlerts/types'; +import type { SubscriptionDependentProps } from 'src/components/admin/Settings/PrefixAlerts/types'; import { Skeleton } from '@mui/material'; import AlertTypeSelector from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; -const AlertTypeField = ({ subscription }: AlertTypeFieldProps) => { +const AlertTypeField = ({ subscription }: SubscriptionDependentProps) => { const alertTypeOptions = useAlertSubscriptionsStore( (state) => state.alertTypeOptions ); diff --git a/src/components/admin/Settings/PrefixAlerts/types.ts b/src/components/admin/Settings/PrefixAlerts/types.ts index 2137492d92..7e65d7f490 100644 --- a/src/components/admin/Settings/PrefixAlerts/types.ts +++ b/src/components/admin/Settings/PrefixAlerts/types.ts @@ -28,12 +28,7 @@ export interface AlertSubscriptionResponse id: string; } -// TODO: Remove and replace type with instance of SubscriptionDependentProps. -export interface AlertTypeFieldProps { - subscription: MutableAlertSubscription; -} - -export interface AlertTypeSelectorProps extends AlertTypeFieldProps { +export interface AlertTypeSelectorProps extends SubscriptionDependentProps { options: AlertTypeInfo[]; } From 46d1f8a779b1f65490859839e8d3dfd23725c365 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Mon, 22 Jun 2026 14:30:56 -0400 Subject: [PATCH 57/70] Place email field in error state when duplicate email detected --- .../SubscriberSection/SubscriberInfo/EmailSelector.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailSelector.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailSelector.tsx index ce22c6d60c..5c7dc86dc0 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailSelector.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailSelector.tsx @@ -160,7 +160,10 @@ function EmailSelector({ }: AutocompleteRenderInputParams) => ( Date: Mon, 22 Jun 2026 15:07:38 -0400 Subject: [PATCH 58/70] Display error text when duplicate email detected --- .../SubscriberInfo/EmailSelector.tsx | 20 +++++++++++++------ src/lang/en-US/Alerts.ts | 1 + 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailSelector.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailSelector.tsx index 5c7dc86dc0..f7079d020b 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailSelector.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/EmailSelector.tsx @@ -76,6 +76,9 @@ function EmailSelector({ setEmailErrorsExist(inputErrorExists, subscriptionId); }, [inputErrorExists, setEmailErrorsExist, subscriptionId]); + const duplicateEmailDetected = + duplicateSubscriptionEmails.includes(subscribedEmail); + return ( 0) || (duplicateSubscriptionEmails.length > 0 && - !duplicateSubscriptionEmails.includes(subscribedEmail)) + !duplicateEmailDetected) } filterOptions={(options) => options.filter((option) => { @@ -160,10 +163,7 @@ function EmailSelector({ }: AutocompleteRenderInputParams) => ( {inputErrorExists ? ( - + {intl.formatMessage({ id: 'alerts.config.dialog.emailSelector.inputError', })} ) : null} + + {duplicateEmailDetected ? ( + + {intl.formatMessage({ + id: 'alerts.config.dialog.emailSelector.duplicationError', + })} + + ) : null} ); } diff --git a/src/lang/en-US/Alerts.ts b/src/lang/en-US/Alerts.ts index 66d229e2d3..6b6a6748dd 100644 --- a/src/lang/en-US/Alerts.ts +++ b/src/lang/en-US/Alerts.ts @@ -7,6 +7,7 @@ export const Alerts: Record = { 'alerts.config.dialog.cta.addSubscriber': `Add Recipient`, 'alerts.config.dialog.cta.deleteAll': `Delete Prefix Alerts`, 'alerts.config.dialog.emailSelector.inputError': `Email is incorrectly formatted.`, + 'alerts.config.dialog.emailSelector.duplicationError': `Email is used by another subscription to this prefix.`, 'alerts.config.dialog.error.generic': `An issue was encountered {operation} an alert subscription.`, 'alerts.config.dialog.error.term.delete': `deleting`, 'alerts.config.dialog.error.term.modify': `creating or updating`, From 7a5adeb84b60847dd7f3f795124df7ed15069c75 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 23 Jun 2026 09:56:38 -0400 Subject: [PATCH 59/70] Replace alert type selector with checklist --- .../SubscriberInfo/AlertTypeSelector.tsx | 257 +++++++----------- .../useAlertSubscriptionsStore.ts | 106 ++++---- .../useUpsertAlertSubscription.ts | 1 + 3 files changed, 164 insertions(+), 200 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx index 5741801c77..98b63e0612 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx @@ -1,27 +1,21 @@ -import type { AutocompleteRenderInputParams } from '@mui/material'; import type { AlertTypeSelectorProps } from 'src/components/admin/Settings/PrefixAlerts/types'; -import type { AlertTypeInfo } from 'src/gql-types/graphql'; - -import { useMemo } from 'react'; import { - Autocomplete, + Checkbox, FormControl, + FormControlLabel, + List, + ListItem, Stack, - TextField, Typography, useTheme, } from '@mui/material'; import { Lock } from 'iconoir-react'; -import { union } from 'lodash'; import { useIntl } from 'react-intl'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; -import SelectableAutocompleteOption from 'src/components/shared/Dialog/SelectableAutocompleteOption'; -import { diminishedTextColor } from 'src/context/Theme'; -import { OutlinedChip } from 'src/styledComponents/chips/OutlinedChip'; -import { basicSort_string, sortByAlertType } from 'src/utils/misc-utils'; +import { defaultOutline, diminishedTextColor } from 'src/context/Theme'; const AlertTypeSelector = ({ options, @@ -33,158 +27,113 @@ const AlertTypeSelector = ({ const serverError = useAlertSubscriptionsStore( (state) => state.initializationError ); - const setAlertTypes = useAlertSubscriptionsStore( - (state) => state.setAlertTypes - ); - - const systemAlerts: AlertTypeInfo[] = useMemo( - () => options.filter(({ isSystem }) => isSystem), - [options] + const setSingleAlertType = useAlertSubscriptionsStore( + (state) => state.setSingleAlertType ); - const selectedAlertTypes: AlertTypeInfo[] = useMemo(() => { - const alertTypes = subscription?.alertTypes ?? []; - - return options.filter(({ alertType }) => - alertTypes.includes(alertType) - ); - }, [options, subscription?.alertTypes]); - return ( - - alertType} - isOptionEqualToValue={(option, value) => - option.alertType === value.alertType - } - multiple - onChange={(_event, values) => { - const evaluatedValues = union(values, systemAlerts); - - setAlertTypes( - evaluatedValues, - subscription?.catalogPrefix, - subscription?.email - ); - }} - options={options.sort((first, second) => - basicSort_string( - first.displayName, - second.displayName, - 'asc' - ) - )} - renderInput={({ - InputProps, - ...params - }: AutocompleteRenderInputParams) => ( - - )} - renderOption={(renderOptionProps, option, state) => { - const { key, ...restRenderOptionProps } = renderOptionProps; - const { description, displayName, isSystem } = option; + + + {intl.formatMessage({ + id: 'entityTable.data.alertTypes', + })} + + + + {options.map((option, index) => { + const { alertType, description, displayName, isSystem } = + option; + const alertTypes = subscription?.alertTypes ?? []; return ( - - - + + { + setSingleAlertType( + option.alertType, + event.target.checked, + subscription?.catalogPrefix, + subscription?.email + ); }} - > - {displayName} - - - {isSystem ? ( - + } + disabled={Boolean(serverError)} + label={ + + - ) : null} - - - {description ? ( - - {description} - - ) : null} - - } - renderOptionProps={restRenderOptionProps} - state={state} - /> - ); - }} - renderValue={(values, getTagProps) => { - return values - .sort((first, second) => - sortByAlertType( - { - isSystemAlert: first.isSystem, - value: first.displayName, - }, - { - isSystemAlert: second.isSystem, - value: second.displayName, - }, - 'asc' - ) - ) - .map(({ alertType, displayName, isSystem }, index) => { - const { onDelete, key, ...restOfTagProps } = - getTagProps({ - index, - }); - - return ( - + + {displayName} + + + {isSystem ? ( + + ) : null} + + + {description ? ( + + {description} + + ) : null} + + } + slotProps={{ + typography: { + component: 'div', + width: '100%', + }, + }} + style={{ alignItems: 'flex-start' }} /> - ); - }); - }} - value={selectedAlertTypes} - /> - + + + ); + })} + + ); }; diff --git a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts index 7f0bdfbbbf..23254623b0 100644 --- a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts +++ b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts @@ -37,17 +37,18 @@ interface AlertSubscriptionState { saveErrors: (CombinedError | PostgrestError | null | undefined)[]; subscriptionMetadata: SubscriptionMetadataDictionary; resetState: () => void; - setAlertTypes: ( - values: AlertTypeInfo[], - catalogPrefix?: string, - email?: string - ) => void; setEmailErrorsExist: (value: boolean, subscriptionId: string) => void; setGlobalPrefixSettings: (value: Schema, targetSetting?: string) => void; setInitializationError: ( value: AlertSubscriptionState['initializationError'] ) => void; setSaveErrors: (value: AlertSubscriptionState['saveErrors']) => void; + setSingleAlertType: ( + value: string, + selected: boolean, + catalogPrefix?: string, + email?: string + ) => void; setSubscribedEmail: (value: string, subscriptionId: string) => void; setSubscribedPrefix: (value: string, errors: string | null) => void; setSubscriptionMetadata: (value: ReducedAlertSubscription[]) => void; @@ -222,47 +223,6 @@ const useAlertSubscriptionsStore = create()( 'state reset' ), - setAlertTypes: (values, catalogPrefix, email) => - set( - produce((state: AlertSubscriptionState) => { - if (!catalogPrefix || !email) { - return; - } - - const alertTypes = values.map( - ({ alertType: name }) => name - ); - - const targetSubscriptions = - state.mutableSubscriptionMetadata.subscriptions; - - const targetIndex = targetSubscriptions.findIndex( - (subscription) => subscription.email === email - ); - - if (targetIndex === -1) { - state.mutableSubscriptionMetadata.subscriptions = [ - ...targetSubscriptions, - { - alertTypes, - catalogPrefix, - email, - id: crypto.randomUUID(), - viewing: true, - }, - ]; - - return; - } - - state.mutableSubscriptionMetadata.subscriptions[ - targetIndex - ].alertTypes = alertTypes; - }), - false, - 'alert types set' - ), - setEmailErrorsExist: (value, subscriptionId) => set( produce((state: AlertSubscriptionState) => { @@ -302,6 +262,60 @@ const useAlertSubscriptionsStore = create()( 'save errors set' ), + setSingleAlertType: (value, selected, catalogPrefix, email) => + set( + produce((state: AlertSubscriptionState) => { + if (!catalogPrefix || !email) { + return; + } + + const targetSubscriptions = + state.mutableSubscriptionMetadata.subscriptions; + + const targetIndex = targetSubscriptions.findIndex( + (subscription) => subscription.email === email + ); + + if (targetIndex === -1) { + const baseAlertTypes = state.alertTypeOptions + .filter( + ({ alertType, isDefault, isSystem }) => + isSystem || + (isDefault && alertType !== value) + ) + .map(({ alertType }) => alertType); + + state.mutableSubscriptionMetadata.subscriptions = [ + ...targetSubscriptions, + { + alertTypes: [...baseAlertTypes, value], + catalogPrefix, + email, + id: crypto.randomUUID(), + viewing: true, + }, + ]; + + return; + } + + const { alertTypes: previousAlertTypes } = + state.mutableSubscriptionMetadata.subscriptions[ + targetIndex + ]; + + state.mutableSubscriptionMetadata.subscriptions[ + targetIndex + ].alertTypes = selected + ? [...previousAlertTypes, value] + : previousAlertTypes.filter( + (alertType) => alertType !== value + ); + }), + false, + 'single alert type set' + ), + setSubscribedEmail: (value, subscriptionId) => set( produce((state: AlertSubscriptionState) => { diff --git a/src/components/admin/Settings/PrefixAlerts/useUpsertAlertSubscription.ts b/src/components/admin/Settings/PrefixAlerts/useUpsertAlertSubscription.ts index 9eddf9a4ca..1fba137f7b 100644 --- a/src/components/admin/Settings/PrefixAlerts/useUpsertAlertSubscription.ts +++ b/src/components/admin/Settings/PrefixAlerts/useUpsertAlertSubscription.ts @@ -40,6 +40,7 @@ export function useUpsertAlertSubscription() { // no operation should be performed. if ( existingSubscription && + alertTypes?.length === existingSubscription.alertTypes.length && difference(alertTypes, existingSubscription.alertTypes) .length === 0 ) { From e382266d35b87171b4b42caf4c256fe4bd58872f Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 23 Jun 2026 09:58:36 -0400 Subject: [PATCH 60/70] Rename AlertTypeSelector to AlertTypeList --- .../SubscriberSection/SubscriberInfo/AlertTypeField.tsx | 7 ++----- .../{AlertTypeSelector.tsx => AlertTypeList.tsx} | 9 +++------ src/components/admin/Settings/PrefixAlerts/types.ts | 2 +- 3 files changed, 6 insertions(+), 12 deletions(-) rename src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/{AlertTypeSelector.tsx => AlertTypeList.tsx} (96%) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeField.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeField.tsx index 857b3ccf8e..d9684a645a 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeField.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeField.tsx @@ -2,7 +2,7 @@ import type { SubscriptionDependentProps } from 'src/components/admin/Settings/P import { Skeleton } from '@mui/material'; -import AlertTypeSelector from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector'; +import AlertTypeList from 'src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeList'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; const AlertTypeField = ({ subscription }: SubscriptionDependentProps) => { @@ -16,10 +16,7 @@ const AlertTypeField = ({ subscription }: SubscriptionDependentProps) => { return alertTypeOptionsFetching ? ( ) : ( - + ); }; diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeList.tsx similarity index 96% rename from src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx rename to src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeList.tsx index 98b63e0612..8f90ec5e8d 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeSelector.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeList.tsx @@ -1,4 +1,4 @@ -import type { AlertTypeSelectorProps } from 'src/components/admin/Settings/PrefixAlerts/types'; +import type { AlertTypeListProps } from 'src/components/admin/Settings/PrefixAlerts/types'; import { Checkbox, @@ -17,10 +17,7 @@ import { useIntl } from 'react-intl'; import useAlertSubscriptionsStore from 'src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore'; import { defaultOutline, diminishedTextColor } from 'src/context/Theme'; -const AlertTypeSelector = ({ - options, - subscription, -}: AlertTypeSelectorProps) => { +const AlertTypeList = ({ options, subscription }: AlertTypeListProps) => { const intl = useIntl(); const theme = useTheme(); @@ -137,4 +134,4 @@ const AlertTypeSelector = ({ ); }; -export default AlertTypeSelector; +export default AlertTypeList; diff --git a/src/components/admin/Settings/PrefixAlerts/types.ts b/src/components/admin/Settings/PrefixAlerts/types.ts index 7e65d7f490..97548e67db 100644 --- a/src/components/admin/Settings/PrefixAlerts/types.ts +++ b/src/components/admin/Settings/PrefixAlerts/types.ts @@ -28,7 +28,7 @@ export interface AlertSubscriptionResponse id: string; } -export interface AlertTypeSelectorProps extends SubscriptionDependentProps { +export interface AlertTypeListProps extends SubscriptionDependentProps { options: AlertTypeInfo[]; } From c0d6fa1b260bd586eb47b8d030b63c7f3e7283a9 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 23 Jun 2026 10:03:26 -0400 Subject: [PATCH 61/70] Sort alert type options in state --- .../PrefixAlerts/useAlertSubscriptionsStore.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts index 23254623b0..4f423325a2 100644 --- a/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts +++ b/src/components/admin/Settings/PrefixAlerts/useAlertSubscriptionsStore.ts @@ -14,7 +14,7 @@ import produce from 'immer'; import { isEmpty, omit } from 'lodash'; import { type CombinedError } from 'urql'; -import { hasOwnProperty } from 'src/utils/misc-utils'; +import { hasOwnProperty, sortByAlertType } from 'src/utils/misc-utils'; import { bundleSubscriptionsByPrefix } from 'src/utils/notification-utils'; import { devtoolsOptions } from 'src/utils/store-utils'; @@ -139,7 +139,19 @@ const useAlertSubscriptionsStore = create()( initializeAlertTypeOptions: (values, fetching) => set( produce((state: AlertSubscriptionState) => { - state.alertTypeOptions = values; + state.alertTypeOptions = values.sort((first, second) => + sortByAlertType( + { + isSystemAlert: first.isSystem, + value: first.displayName, + }, + { + isSystemAlert: second.isSystem, + value: second.displayName, + }, + 'asc' + ) + ); state.alertTypeOptionsFetching = fetching; }), false, From 4ab27c2dc92cf8a1af5f4fd82a55bddd53e55b96 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 23 Jun 2026 10:11:33 -0400 Subject: [PATCH 62/70] Update selected state of alert type list items --- .../SubscriberInfo/AlertTypeList.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeList.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeList.tsx index 8f90ec5e8d..950231ee39 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeList.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/SubscriberSection/SubscriberInfo/AlertTypeList.tsx @@ -41,13 +41,19 @@ const AlertTypeList = ({ options, subscription }: AlertTypeListProps) => { const { alertType, description, displayName, isSystem } = option; const alertTypes = subscription?.alertTypes ?? []; + const selected = alertTypes.includes(alertType); return ( { { setSingleAlertType( option.alertType, From 4ae5b3e55538b698ea6eebeff5348d3aab00bcd6 Mon Sep 17 00:00:00 2001 From: Kiahna Tucker Date: Tue, 23 Jun 2026 10:20:18 -0400 Subject: [PATCH 63/70] Disable multi-subscription delete button for new prefixes --- .../Settings/PrefixAlerts/Dialog/DeleteButton.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx b/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx index 983fd6480b..1342ddd9c2 100644 --- a/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx +++ b/src/components/admin/Settings/PrefixAlerts/Dialog/DeleteButton.tsx @@ -27,6 +27,9 @@ const DeleteButton = ({ closeDialog }: DialogActionProps) => { const mutableSubscriptionMetadata = useAlertSubscriptionsStore( (state) => state.mutableSubscriptionMetadata ); + const subscriptionMetadata = useAlertSubscriptionsStore( + (state) => state.subscriptionMetadata + ); const disabled = useMemo(() => { const { subscriptions } = mutableSubscriptionMetadata; @@ -40,9 +43,16 @@ const DeleteButton = ({ closeDialog }: DialogActionProps) => { loading || catalogPrefix.length === 0 || subscriptions.length === 0 || - emptyEmailExists + emptyEmailExists || + !Object.keys(subscriptionMetadata).includes(catalogPrefix) ); - }, [catalogPrefix, errorsExist, loading, mutableSubscriptionMetadata]); + }, [ + catalogPrefix, + errorsExist, + loading, + mutableSubscriptionMetadata, + subscriptionMetadata, + ]); return (