Skip to content

Commit

Permalink
UI: Edit Alerts (#1402)
Browse files Browse the repository at this point in the history
Adds UI to edit an alert

<img width="1436" alt="Screenshot 2024-02-29 at 12 49 47 AM"
src="https://github.com/PeerDB-io/peerdb/assets/65964360/26d16d3c-44f2-4c52-9410-b095b6ca3a60">

![Screenshot 2024-03-04 at 8 57
12 PM](https://github.com/PeerDB-io/peerdb/assets/65964360/a45254d9-7033-42cc-b116-e0c282a78350)
  • Loading branch information
Amogh-Bharadwaj authored Mar 4, 2024
1 parent 5853e4e commit b794f19
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 35 deletions.
57 changes: 41 additions & 16 deletions ui/app/alert-config/new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ import { PulseLoader } from 'react-spinners';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { alertConfigReqSchema, alertConfigType } from './validation';

export interface AlertConfigProps {
id?: bigint;
serviceType: string;
authToken: string;
channelIdString: string;
slotLagGBAlertThreshold: number;
openConnectionsAlertThreshold: number;
forEdit?: boolean;
}

const notifyErr = (errMsg: string) => {
toast.error(errMsg, {
position: 'bottom-center',
Expand All @@ -21,29 +32,30 @@ function ConfigLabel() {
);
}

const NewAlertConfig = () => {
const [serviceType, setServiceType] = useState<string>();
const [authToken, setAuthToken] = useState<string>();
const [channelIdString, setChannelIdString] = useState<string>();
const [slotLagMBAlertThreshold, setSlotLagMBAlertThreshold] =
useState<number>();
const NewAlertConfig = (alertProps: AlertConfigProps) => {
const [serviceType, setServiceType] = useState<string>('slack');
const [authToken, setAuthToken] = useState<string>(alertProps.authToken);
const [channelIdString, setChannelIdString] = useState<string>(
alertProps.channelIdString
);
const [slotLagGBAlertThreshold, setSlotLagGBAlertThreshold] =
useState<number>(alertProps.slotLagGBAlertThreshold);
const [openConnectionsAlertThreshold, setOpenConnectionsAlertThreshold] =
useState<number>();
useState<number>(alertProps.openConnectionsAlertThreshold);
const [loading, setLoading] = useState(false);
const handleAdd = async () => {
if (serviceType !== 'slack') {
notifyErr('Service Type must be selected');
return;
}
console.log(slotLagMBAlertThreshold);
console.log(openConnectionsAlertThreshold);

const alertConfigReq: alertConfigType = {
serviceType: serviceType,
serviceConfig: {
auth_token: authToken ?? '',
channel_ids: channelIdString?.split(',')!,
slot_lag_mb_alert_threshold: slotLagMBAlertThreshold || 0,
open_connections_alert_threshold: openConnectionsAlertThreshold || 0,
slot_lag_mb_alert_threshold: slotLagGBAlertThreshold * 1000 || 20000,
open_connections_alert_threshold: openConnectionsAlertThreshold || 5,
},
};
const alertReqValidity = alertConfigReqSchema.safeParse(alertConfigReq);
Expand All @@ -52,8 +64,11 @@ const NewAlertConfig = () => {
return;
}
setLoading(true);
if (alertProps.forEdit) {
alertConfigReq.id = Number(alertProps.id);
}
const createRes = await fetch('/api/alert-config', {
method: 'POST',
method: alertProps.forEdit ? 'PUT' : 'POST',
body: JSON.stringify(alertConfigReq),
});
const createStatus = await createRes.text();
Expand Down Expand Up @@ -86,6 +101,10 @@ const NewAlertConfig = () => {
},
]}
placeholder='Select provider'
defaultValue={{
value: 'slack',
label: 'Slack',
}}
formatOptionLabel={ConfigLabel}
onChange={(val, _) => val && setServiceType(val.value)}
/>
Expand Down Expand Up @@ -113,14 +132,14 @@ const NewAlertConfig = () => {
</div>

<div>
<p>Slot Lag Alert Threshold (in MB)</p>
<p>Slot Lag Alert Threshold (in GB)</p>
<TextField
style={{ height: '2.5rem', marginTop: '0.5rem' }}
variant='simple'
type={'number'}
placeholder='optional'
value={slotLagMBAlertThreshold}
onChange={(e) => setSlotLagMBAlertThreshold(e.target.valueAsNumber)}
value={slotLagGBAlertThreshold}
onChange={(e) => setSlotLagGBAlertThreshold(e.target.valueAsNumber)}
/>
</div>

Expand All @@ -143,7 +162,13 @@ const NewAlertConfig = () => {
onClick={handleAdd}
variant='normalSolid'
>
{loading ? <PulseLoader color='white' size={10} /> : 'Create'}
{loading ? (
<PulseLoader color='white' size={10} />
) : alertProps.forEdit ? (
'Update'
) : (
'Create'
)}
</Button>
<ToastContainer />
</div>
Expand Down
72 changes: 55 additions & 17 deletions ui/app/alert-config/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';
import AlertDropdown from '@/components/AlertDropdown';
import ConfigJSONView from '@/components/ConfigJSONView';
import { DropDialog } from '@/components/DropDialog';
import { Button } from '@/lib/Button';
import { Icon } from '@/lib/Icon';
import { Label } from '@/lib/Label';
Expand All @@ -11,8 +11,7 @@ import { PulseLoader } from 'react-spinners';
import useSWR from 'swr';
import { UAlertConfigResponse } from '../dto/AlertDTO';
import { fetcher } from '../utils/swr';
import NewAlertConfig from './new';

import NewAlertConfig, { AlertConfigProps } from './new';
const ServiceIcon = (serviceType: string) => {
switch (serviceType.toLowerCase()) {
default:
Expand All @@ -22,15 +21,40 @@ const ServiceIcon = (serviceType: string) => {
const AlertConfigPage: React.FC = () => {
const {
data: alerts,
error,
isLoading,
}: {
data: UAlertConfigResponse[];
error: any;
isLoading: boolean;
} = useSWR('/api/alert-config', fetcher);
const [newConfig, setNewConfig] = useState(false);
const blankAlert: AlertConfigProps = {
serviceType: '',
authToken: '',
channelIdString: '',
slotLagGBAlertThreshold: 20,
openConnectionsAlertThreshold: 5,
forEdit: false,
};
const [inEditOrAddMode, setInEditOrAddMode] = useState(false);
const [editAlertConfig, setEditAlertConfig] =
useState<AlertConfigProps>(blankAlert);

const onEdit = (alertConfig: UAlertConfigResponse, id: bigint) => {
setInEditOrAddMode(true);
const configJSON = JSON.stringify(alertConfig.service_config);
const channelIds: string[] = JSON.parse(configJSON)?.channel_ids;
setEditAlertConfig({
id,
serviceType: alertConfig.service_type,
authToken: JSON.parse(configJSON)?.auth_token,
channelIdString: channelIds.join(','),
slotLagGBAlertThreshold:
(JSON.parse(configJSON)?.slot_lag_mb_alert_threshold as number) / 1000,
openConnectionsAlertThreshold:
JSON.parse(configJSON)?.open_connections_alert_threshold,
forEdit: true,
});
};
return (
<div style={{ padding: '2rem' }}>
<Label variant='title3'>Alert Configurations</Label>
Expand All @@ -54,16 +78,22 @@ const AlertConfigPage: React.FC = () => {
>
<Table>
{alerts?.length ? (
alerts.map((alert: UAlertConfigResponse, index) => (
alerts.map((alertConfig: UAlertConfigResponse, index) => (
<TableRow key={index}>
<TableCell style={{ width: '10%' }}>{alert.id}</TableCell>
<TableCell style={{ width: '10%' }}>
{ServiceIcon(alert.service_type)}
{alertConfig.id}
</TableCell>
<TableCell style={{ width: '10%' }}>
{ServiceIcon(alertConfig.service_type)}
</TableCell>
<TableCell>
<div style={{ height: '8em' }}>
<div style={{ height: '10em' }}>
<ConfigJSONView
config={JSON.stringify(alert.service_config, null, 2)}
config={JSON.stringify(
alertConfig.service_config,
null,
2
)}
/>
</div>
</TableCell>
Expand All @@ -75,7 +105,11 @@ const AlertConfigPage: React.FC = () => {
justifyContent: 'center',
}}
>
<DropDialog mode='ALERT' dropArgs={{ id: alert.id }} />
<AlertDropdown
disable={inEditOrAddMode}
alertId={alertConfig.id}
onEdit={() => onEdit(alertConfig, alertConfig.id)}
/>
</div>
</TableCell>
</TableRow>
Expand All @@ -94,17 +128,21 @@ const AlertConfigPage: React.FC = () => {
</>
)}
<Button
variant='normalSolid'
disabled={newConfig}
variant={inEditOrAddMode ? 'peer' : 'normalSolid'}
style={{ display: 'flex', alignItems: 'center', marginTop: '2rem' }}
onClick={() => setNewConfig(true)}
onClick={() => {
if (inEditOrAddMode) {
setEditAlertConfig(blankAlert);
}
setInEditOrAddMode((prev) => !prev);
}}
>
<Icon name='add' />
<Icon name={inEditOrAddMode ? 'cancel' : 'add'} />
<Label as='label' style={{ fontSize: 14 }}>
Add Configuration
{inEditOrAddMode ? 'Cancel' : 'Add Configuration'}
</Label>
</Button>
{newConfig && <NewAlertConfig />}
{inEditOrAddMode && <NewAlertConfig {...editAlertConfig} />}
</div>
);
};
Expand Down
1 change: 1 addition & 0 deletions ui/app/alert-config/validation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import z from 'zod';

export const alertConfigReqSchema = z.object({
id: z.optional(z.number()),
serviceType: z.enum(['slack'], {
errorMap: (issue, ctx) => ({ message: 'Invalid service type' }),
}),
Expand Down
18 changes: 18 additions & 0 deletions ui/app/api/alert-config/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,21 @@ export async function DELETE(request: Request) {

return new Response(deleteStatus);
}

export async function PUT(request: Request) {
const alertConfigReq: alertConfigType = await request.json();
const editRes = await prisma.alerting_config.update({
data: {
service_type: alertConfigReq.serviceType,
service_config: alertConfigReq.serviceConfig,
},
where: {
id: alertConfigReq.id,
},
});
let editStatus: 'success' | 'error' = 'error';
if (editRes.id) {
editStatus = 'success';
}
return new Response(editStatus);
}
62 changes: 62 additions & 0 deletions ui/components/AlertDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Button } from '@/lib/Button/Button';
import { Icon } from '@/lib/Icon';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { useState } from 'react';
import { DropDialog } from './DropDialog';
const AlertDropdown = ({
disable,
alertId,
onEdit,
}: {
disable: boolean;
alertId: bigint;
onEdit: () => void;
}) => {
const [open, setOpen] = useState(false);
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};

const handleClose = () => {
setOpen(false);
};

return (
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button
aria-controls={open ? 'menu-list-grow' : undefined}
aria-haspopup='true'
onClick={handleToggle}
>
<Icon name='menu' />
</Button>
</DropdownMenu.Trigger>

<DropdownMenu.Portal>
<DropdownMenu.Content
style={{
border: '1px solid rgba(0,0,0,0.1)',
borderRadius: '0.5rem',
}}
>
<DropdownMenu.Item>
<Button
variant='normalBorderless'
style={{ width: '100%', fontWeight: 'lighter' }}
onClick={onEdit}
disabled={disable}
>
Edit
</Button>
</DropdownMenu.Item>
<DropdownMenu.Item>
<DropDialog mode='ALERT' dropArgs={{ id: alertId }} />
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
);
};

export default AlertDropdown;
10 changes: 8 additions & 2 deletions ui/components/DropDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,14 @@ export const DropDialog = ({
noInteract={true}
size='large'
triggerButton={
<Button variant='drop' style={{ color: 'black' }}>
<Icon name='delete' />
<Button variant='drop'>
{mode === 'ALERT' ? (
<Label as='label' style={{ color: 'coral' }}>
Delete
</Label>
) : (
<Icon name='delete' />
)}
</Button>
}
>
Expand Down
Loading

0 comments on commit b794f19

Please sign in to comment.