Skip to content

Commit

Permalink
Merge pull request #170 from openmsupply/154-scheduled-notifications-…
Browse files Browse the repository at this point in the history
…allow-users-be-able-to-add-or-delete-the-queries-to-use-in-their-notification

154 scheduled notifications allow users be able to add or delete the queries to use in their notification
  • Loading branch information
jmbrunskill authored Oct 25, 2023
2 parents 977aa51 + ce185f6 commit 3e80dbd
Show file tree
Hide file tree
Showing 13 changed files with 333 additions and 136 deletions.
5 changes: 4 additions & 1 deletion frontend/packages/common/src/intl/locales/en/system.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,8 @@
"label.test-sql-query": "Test SQL Query",
"tooltip.manage-recipient-list": "Manage Recipient List",
"message.no-parameters": "No parameters found",
"message.no-sensors-selected": "No sensors selected"
"message.no-sensors-selected": "No sensors selected",
"label.select-queries": "Select Queries",
"message.no-queries-selected": "No queries selected",
"label.schedule": "Schedule"
}
6 changes: 1 addition & 5 deletions frontend/packages/common/src/ui/layout/tables/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ import {
Table as MuiTable,
Typography,
} from '@mui/material';
import {
BasicSpinner,
useRegisterActions,
} from '@notify-frontend/common';
import { BasicSpinner, useRegisterActions } from '@notify-frontend/common';

import { TableProps } from './types';
import { DataRow } from './components/DataRow/DataRow';
Expand Down Expand Up @@ -108,7 +105,6 @@ export const DataTableComponent = <T extends RecordWithId>({
display: 'flex',
flexDirection: 'column',
flex: 1,
height: '100%',
}}
>
<MuiTable>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,15 @@ export const BaseNotificationEditPage = <T extends BaseNotificationConfig>({
});
};

const requiredParams = (sqlRecipientLists?.nodes ?? [])
const requiredRecipientParams = (sqlRecipientLists?.nodes ?? [])
.filter(list => draft.sqlRecipientListIds.includes(list.id))
.map(list => list.parameters)
.flat(1);

const requiredConfigParams = draft.requiredParameters;

const requiredParams = requiredRecipientParams.concat(requiredConfigParams);

const allParamsSet = requiredParams.every(param => {
if (param) {
return draft.parsedParameters[param] !== undefined; // This allows the user to set the param to an empty string if they edit the field then delete the value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@ export const NotificationDetailPanel = ({
<PanelRow>
<BufferedTextInput
key={`param-value-${param}`}
required={requiredParams.includes(param)}
InputProps={{
sx: {
backgroundColor: 'background.white',
},
}}
value={params[param ?? '']}
onChange={e => onUpdateParams(param ?? '', e.target.value)}
error={requiredParams.includes(param) && !params[param ?? '']}
/>
{
// if param is not required allow it to be removed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const createCCNotification = (
sqlRecipientListIds: seed?.sqlRecipientListIds ?? [],
parameters: seed?.parameters ?? '{}',
parsedParameters: seed?.parsedParameters ?? {},
requiredParameters: seed?.requiredParameters ?? [],
});

export const CCNotificationEditPage = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export function parseColdChainNotificationConfig(
status: config.status,
parameters: config.parameters,
parsedParameters: TeraUtils.keyedParamsFromTeraJson(config.parameters),
requiredParameters: [],
};
} catch (e) {
showError();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,129 +1,125 @@
import React from 'react';
import {
BasicTextInput,
Box,
BufferedTextArea,
DateTimeInput,
Select,
Typography,
useQueryParamsState,
useTranslation,
} from '@notify-frontend/common';
import { ScheduledNotification } from '../../types';
import { SqlQuerySelector } from '../../components';

const dummySqlQueries = [
{
id: '1',
name: 'Last Sync By Province',
query: `SELECT
category3 AS project, category1_level2 AS province,
COUNT(last_sync_date) AS num_of_stores_synced_once
FROM store_categories sc
JOIN store s ON sc.code = s.code
JOIN (
SELECT site_id, MAX(date) AS last_sync_date FROM site_log GROUP BY site_id
) sl ON s.sync_id_remote_site = sl.site_id
WHERE mode IN ('store', 'dispensary')
AND sc.disabled = false
AND category3 IN ({{project}})
AND category1_level2 IN ({{province}})
GROUP BY category3, category1_level2`,
parameters: ['project', 'province'],
},
{
id: '2',
name: 'First Stock Take',
query: `SELECT
category3 AS project, category1_level2 AS province,
COUNT(first_stock_take_date) AS first_stock_take_date
FROM store_categories sc
JOIN store s ON sc.code = s.code
JOIN (
SELECT store_id, MAX(stock_take_created_date) AS fist_stock_take_date FROM stock_take GROUP BY store_id
) st ON s.id = st.store_id
WHERE mode IN ('store', 'dispensary')
AND sc.disabled = false
AND category3 IN ({{project}})
AND category1_level2 IN ({{province}})
GROUP BY category3, category1_level2 `,
parameters: ['project', 'province'],
},
];
import { useNotificationQueries } from 'packages/system/src/Queries/api';

type ScheduledNotificationEditFormProps = {
onUpdate: (patch: Partial<ScheduledNotification>) => void;
draft: ScheduledNotification;
};

const FormRow = ({
title,
children,
}: {
title: string;
children: React.ReactNode;
}) => (
<Box padding={1}>
<Typography sx={{ fontWeight: 700, fontSize: '13px', marginBottom: '2px' }}>
{title}
</Typography>
<Box paddingLeft={1}>{children}</Box>
</Box>
);

export const ScheduledNotificationEditForm = ({
onUpdate,
draft,
}: ScheduledNotificationEditFormProps) => {
const t = useTranslation('system');
return (
<>
<BasicTextInput
autoFocus
value={draft.subjectTemplate}
required
onChange={e =>
onUpdate({
subjectTemplate: e.target
.value as ScheduledNotification['subjectTemplate'],
})
}
label={t('label.subject-template')}
InputLabelProps={{ shrink: true }}
/>
<BufferedTextArea
value={draft.bodyTemplate}
onChange={e => onUpdate({ bodyTemplate: e.target.value })}
label={t('label.body-template')}
InputProps={{ sx: { backgroundColor: 'background.menu' } }}
InputLabelProps={{ shrink: true }}
/>

<BufferedTextArea
value={draft.parameters}
onChange={e => onUpdate({ parameters: e.target.value })}
label={t('label.parameters')}
InputProps={{ sx: { backgroundColor: 'background.menu' } }}
InputLabelProps={{ shrink: true }}
/>
<Typography sx={{ fontWeight: 700, fontSize: '13px' }}>
Queries
</Typography>
<SqlQuerySelector records={dummySqlQueries} />
const { queryParams } = useQueryParamsState();

<Typography
sx={{ fontWeight: 700, fontSize: '13px', marginBottom: '2px' }}
>
Schedule
</Typography>
<Typography sx={{ fontSize: '10px' }}>Starting From</Typography>
<DateTimeInput
onChange={d =>
onUpdate({
scheduleStartTime: d as ScheduledNotification['scheduleStartTime'],
})
}
date={draft.scheduleStartTime}
/>
<Typography sx={{ fontSize: '10px' }}>Repeat</Typography>
<Select
value={draft.scheduleFrequency}
disabled={false}
onChange={e =>
onUpdate({
scheduleFrequency: e.target
.value as ScheduledNotification['scheduleFrequency'],
})
}
options={[
{ label: t('label.daily'), value: 'daily' },
{ label: t('label.weekly'), value: 'weekly' },
{ label: t('label.monthly'), value: 'monthly' },
]}
/>
</>
const { data, isLoading } = useNotificationQueries(queryParams);
const queries = data?.nodes ?? [];

return (
<Box paddingTop={1}>
<FormRow title={t('label.details')}>
<BasicTextInput
autoFocus
value={draft.subjectTemplate}
required
onChange={e =>
onUpdate({
subjectTemplate: e.target
.value as ScheduledNotification['subjectTemplate'],
})
}
label={t('label.subject-template')}
InputLabelProps={{ shrink: true }}
/>
</FormRow>
<FormRow title="">
<BufferedTextArea
value={draft.bodyTemplate}
onChange={e => onUpdate({ bodyTemplate: e.target.value })}
label={t('label.body-template')}
InputProps={{ sx: { backgroundColor: 'background.menu' } }}
InputLabelProps={{ shrink: true }}
/>
</FormRow>
<FormRow title="">
<BufferedTextArea
value={draft.parameters}
onChange={e => onUpdate({ parameters: e.target.value })}
label={t('label.parameters')}
InputProps={{ sx: { backgroundColor: 'background.menu' } }}
InputLabelProps={{ shrink: true }}
/>
</FormRow>
<Box padding={1}>
<Typography sx={{ fontWeight: 700, fontSize: '13px' }}>
{t('label.queries')}
</Typography>
<SqlQuerySelector
allQueries={queries}
selectedQueryIds={draft.notificationQueryIds}
isLoading={isLoading}
setSelection={props => {
onUpdate(props as Partial<ScheduledNotification>);
}}
/>
</Box>
<FormRow title={t('label.schedule')}>
<Typography sx={{ fontSize: '10px' }}>Starting From</Typography>
<DateTimeInput
onChange={d =>
onUpdate({
scheduleStartTime:
d as ScheduledNotification['scheduleStartTime'],
})
}
date={draft.scheduleStartTime}
/>
<Typography sx={{ fontSize: '10px', paddingTop: 1 }}>Repeat</Typography>
<Select
value={draft.scheduleFrequency}
disabled={false}
onChange={e =>
onUpdate({
scheduleFrequency: e.target
.value as ScheduledNotification['scheduleFrequency'],
})
}
options={[
{ label: t('label.daily'), value: 'daily' },
{ label: t('label.weekly'), value: 'weekly' },
{ label: t('label.monthly'), value: 'monthly' },
]}
/>
</FormRow>
</Box>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
useTranslation,
useNotification,
useParams,
useBreadcrumbs,
} from '@notify-frontend/common';
import { ScheduledNotificationEditForm } from './ScheduledNotificationEditForm';
import { BaseNotificationEditPage } from '../Base/BaseNotificationEditPage';
Expand All @@ -22,6 +23,7 @@ export const ScheduledNotificationEditPage = () => {
const t = useTranslation('system');
const { error } = useNotification();
const parsingErrorSnack = error(t('error.parsing-notification-config'));
const { setSuffix } = useBreadcrumbs();

const { id } = useParams<{ id: string }>();
const [draft, setDraft] = useState<ScheduledNotification>(
Expand All @@ -32,6 +34,7 @@ export const ScheduledNotificationEditPage = () => {
const { data, isLoading } = useNotificationConfigs({
filterBy: { id: { equalTo: id } },
});

useEffect(() => {
const entity = data?.nodes[0];
// Once we get the notification config from the API, parse it and load into the draft
Expand All @@ -40,6 +43,7 @@ export const ScheduledNotificationEditPage = () => {
parsingErrorSnack
);
setDraft(parsedDraft ?? defaultSchedulerNotification);
if (parsedDraft?.title) setSuffix(parsedDraft?.title);
}, [data]);

const { mutateAsync: update, isLoading: updateIsLoading } =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ export const defaultSchedulerNotification: ScheduledNotification = {
sqlRecipientListIds: [],
parameters: '{}',
parsedParameters: {},
requiredParameters: [],
scheduleFrequency: 'daily',
scheduleStartTime: new Date(),
subjectTemplate: '',
bodyTemplate: '',
sqlQueries: [],
notificationQueryIds: [],
status: ConfigStatus.Disabled,
};

Expand Down
Loading

0 comments on commit 3e80dbd

Please sign in to comment.