mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 14:28:59 +08:00
feat: disable alerts feature (UI) (#1445)
* feat: added enable disable feature for rules * fix: resolved type issue in getTriggered Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
parent
a6ed6c03c1
commit
5dc6d28f2e
26
frontend/src/api/alerts/patch.ts
Normal file
26
frontend/src/api/alerts/patch.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/patch';
|
||||
|
||||
const patch = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.patch(`/rules/${props.id}`, {
|
||||
...props.data,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default patch;
|
@ -1,10 +1,11 @@
|
||||
import { Button } from 'antd';
|
||||
import { NotificationInstance } from 'antd/lib/notification/index';
|
||||
import deleteAlerts from 'api/alerts/delete';
|
||||
import { State } from 'hooks/useFetch';
|
||||
import React, { useState } from 'react';
|
||||
import { PayloadProps as DeleteAlertPayloadProps } from 'types/api/alerts/delete';
|
||||
import { Alerts } from 'types/api/alerts/getAll';
|
||||
import { GettableAlert } from 'types/api/alerts/get';
|
||||
|
||||
import { ColumnButton } from './styles';
|
||||
|
||||
function DeleteAlert({
|
||||
id,
|
||||
@ -72,20 +73,20 @@ function DeleteAlert({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
<ColumnButton
|
||||
disabled={deleteAlertState.loading || false}
|
||||
loading={deleteAlertState.loading || false}
|
||||
onClick={(): Promise<void> => onDeleteHandler(id)}
|
||||
type="link"
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</ColumnButton>
|
||||
);
|
||||
}
|
||||
|
||||
interface DeleteAlertProps {
|
||||
id: Alerts['id'];
|
||||
setData: React.Dispatch<React.SetStateAction<Alerts[]>>;
|
||||
id: GettableAlert['id'];
|
||||
setData: React.Dispatch<React.SetStateAction<GettableAlert[]>>;
|
||||
notifications: NotificationInstance;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { notification, Tag, Typography } from 'antd';
|
||||
import { notification, Typography } from 'antd';
|
||||
import Table, { ColumnsType } from 'antd/lib/table';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import ROUTES from 'constants/routes';
|
||||
@ -13,15 +13,16 @@ import { UseQueryResult } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { Alerts } from 'types/api/alerts/getAll';
|
||||
import { GettableAlert } from 'types/api/alerts/get';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import DeleteAlert from './DeleteAlert';
|
||||
import { Button, ButtonContainer } from './styles';
|
||||
import { Button, ButtonContainer, ColumnButton, StyledTag } from './styles';
|
||||
import Status from './TableComponents/Status';
|
||||
import ToggleAlertState from './ToggleAlertState';
|
||||
|
||||
function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
const [data, setData] = useState<Alerts[]>(allAlertRules || []);
|
||||
const [data, setData] = useState<GettableAlert[]>(allAlertRules || []);
|
||||
const { t } = useTranslation('common');
|
||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const [addNewAlert, action] = useComponentPermission(
|
||||
@ -53,22 +54,27 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
history.push(`${ROUTES.EDIT_ALERTS}?ruleId=${id}`);
|
||||
};
|
||||
|
||||
const columns: ColumnsType<Alerts> = [
|
||||
const columns: ColumnsType<GettableAlert> = [
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
sorter: (a, b): number =>
|
||||
b.labels.severity.length - a.labels.severity.length,
|
||||
(b.state ? b.state.charCodeAt(0) : 1000) -
|
||||
(a.state ? a.state.charCodeAt(0) : 1000),
|
||||
render: (value): JSX.Element => <Status status={value} />,
|
||||
},
|
||||
{
|
||||
title: 'Alert Name',
|
||||
dataIndex: 'alert',
|
||||
key: 'name',
|
||||
sorter: (a, b): number => a.name.charCodeAt(0) - b.name.charCodeAt(0),
|
||||
sorter: (a, b): number =>
|
||||
(a.alert ? a.alert.charCodeAt(0) : 1000) -
|
||||
(b.alert ? b.alert.charCodeAt(0) : 1000),
|
||||
render: (value, record): JSX.Element => (
|
||||
<Typography.Link onClick={(): void => onEditHandler(record.id.toString())}>
|
||||
<Typography.Link
|
||||
onClick={(): void => onEditHandler(record.id ? record.id.toString() : '')}
|
||||
>
|
||||
{value}
|
||||
</Typography.Link>
|
||||
),
|
||||
@ -78,7 +84,8 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
dataIndex: 'labels',
|
||||
key: 'severity',
|
||||
sorter: (a, b): number =>
|
||||
a.labels.severity.length - b.labels.severity.length,
|
||||
(a.labels ? a.labels.severity.length : 0) -
|
||||
(b.labels ? b.labels.severity.length : 0),
|
||||
render: (value): JSX.Element => {
|
||||
const objectKeys = Object.keys(value);
|
||||
const withSeverityKey = objectKeys.find((e) => e === 'severity') || '';
|
||||
@ -92,6 +99,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
dataIndex: 'labels',
|
||||
key: 'tags',
|
||||
align: 'center',
|
||||
width: 350,
|
||||
render: (value): JSX.Element => {
|
||||
const objectKeys = Object.keys(value);
|
||||
const withOutSeverityKeys = objectKeys.filter((e) => e !== 'severity');
|
||||
@ -104,9 +112,9 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
<>
|
||||
{withOutSeverityKeys.map((e) => {
|
||||
return (
|
||||
<Tag key={e} color="magenta">
|
||||
<StyledTag key={e} color="magenta">
|
||||
{e}: {value[e]}
|
||||
</Tag>
|
||||
</StyledTag>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
@ -120,14 +128,19 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
title: 'Action',
|
||||
dataIndex: 'id',
|
||||
key: 'action',
|
||||
render: (id: Alerts['id']): JSX.Element => {
|
||||
render: (id: GettableAlert['id'], record): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<DeleteAlert notifications={notifications} setData={setData} id={id} />
|
||||
<ToggleAlertState disabled={record.disabled} setData={setData} id={id} />
|
||||
|
||||
<Button onClick={(): void => onEditHandler(id.toString())} type="link">
|
||||
<ColumnButton
|
||||
onClick={(): void => onEditHandler(id.toString())}
|
||||
type="link"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</ColumnButton>
|
||||
|
||||
<DeleteAlert notifications={notifications} setData={setData} id={id} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
@ -159,8 +172,10 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
}
|
||||
|
||||
interface ListAlertProps {
|
||||
allAlertRules: Alerts[];
|
||||
refetch: UseQueryResult<ErrorResponse | SuccessResponse<Alerts[]>>['refetch'];
|
||||
allAlertRules: GettableAlert[];
|
||||
refetch: UseQueryResult<
|
||||
ErrorResponse | SuccessResponse<GettableAlert[]>
|
||||
>['refetch'];
|
||||
}
|
||||
|
||||
export default ListAlert;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Tag } from 'antd';
|
||||
import React from 'react';
|
||||
import { Alerts } from 'types/api/alerts/getAll';
|
||||
import { GettableAlert } from 'types/api/alerts/get';
|
||||
|
||||
function Status({ status }: StatusProps): JSX.Element {
|
||||
switch (status) {
|
||||
@ -16,14 +16,18 @@ function Status({ status }: StatusProps): JSX.Element {
|
||||
return <Tag color="red">Firing</Tag>;
|
||||
}
|
||||
|
||||
case 'disabled': {
|
||||
return <Tag>Disabled</Tag>;
|
||||
}
|
||||
|
||||
default: {
|
||||
return <Tag color="default">Unknown Status</Tag>;
|
||||
return <Tag color="default">Unknown</Tag>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface StatusProps {
|
||||
status: Alerts['state'];
|
||||
status: GettableAlert['state'];
|
||||
}
|
||||
|
||||
export default Status;
|
||||
|
108
frontend/src/container/ListAlertRules/ToggleAlertState.tsx
Normal file
108
frontend/src/container/ListAlertRules/ToggleAlertState.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import { notification } from 'antd';
|
||||
import patchAlert from 'api/alerts/patch';
|
||||
import { State } from 'hooks/useFetch';
|
||||
import React, { useState } from 'react';
|
||||
import { GettableAlert } from 'types/api/alerts/get';
|
||||
import { PayloadProps as PatchPayloadProps } from 'types/api/alerts/patch';
|
||||
|
||||
import { ColumnButton } from './styles';
|
||||
|
||||
function ToggleAlertState({
|
||||
id,
|
||||
disabled,
|
||||
setData,
|
||||
}: ToggleAlertStateProps): JSX.Element {
|
||||
const [apiStatus, setAPIStatus] = useState<State<PatchPayloadProps>>({
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: false,
|
||||
success: false,
|
||||
payload: undefined,
|
||||
});
|
||||
|
||||
const defaultErrorMessage = 'Something went wrong';
|
||||
|
||||
const onToggleHandler = async (
|
||||
id: number,
|
||||
disabled: boolean,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
setAPIStatus((state) => ({
|
||||
...state,
|
||||
loading: true,
|
||||
}));
|
||||
|
||||
const response = await patchAlert({
|
||||
id,
|
||||
data: {
|
||||
disabled,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
setData((state) => {
|
||||
return state.map((alert) => {
|
||||
if (alert.id === id) {
|
||||
return {
|
||||
...alert,
|
||||
disabled: response.payload.disabled,
|
||||
state: response.payload.state,
|
||||
};
|
||||
}
|
||||
return alert;
|
||||
});
|
||||
});
|
||||
|
||||
setAPIStatus((state) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
payload: response.payload,
|
||||
}));
|
||||
notification.success({
|
||||
message: 'Success',
|
||||
});
|
||||
} else {
|
||||
setAPIStatus((state) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
error: true,
|
||||
errorMessage: response.error || defaultErrorMessage,
|
||||
}));
|
||||
|
||||
notification.error({
|
||||
message: response.error || defaultErrorMessage,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
setAPIStatus((state) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
error: true,
|
||||
errorMessage: defaultErrorMessage,
|
||||
}));
|
||||
|
||||
notification.error({
|
||||
message: defaultErrorMessage,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ColumnButton
|
||||
disabled={apiStatus.loading || false}
|
||||
loading={apiStatus.loading || false}
|
||||
onClick={(): Promise<void> => onToggleHandler(id, !disabled)}
|
||||
type="link"
|
||||
>
|
||||
{disabled ? 'Enable' : 'Disable'}
|
||||
</ColumnButton>
|
||||
);
|
||||
}
|
||||
|
||||
interface ToggleAlertStateProps {
|
||||
id: GettableAlert['id'];
|
||||
disabled: boolean;
|
||||
setData: React.Dispatch<React.SetStateAction<GettableAlert[]>>;
|
||||
}
|
||||
|
||||
export default ToggleAlertState;
|
@ -1,4 +1,4 @@
|
||||
import { Button as ButtonComponent } from 'antd';
|
||||
import { Button as ButtonComponent, Tag } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ButtonContainer = styled.div`
|
||||
@ -12,6 +12,20 @@ export const ButtonContainer = styled.div`
|
||||
|
||||
export const Button = styled(ButtonComponent)`
|
||||
&&& {
|
||||
margin-left: 1rem;
|
||||
margin-left: 1em;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ColumnButton = styled(ButtonComponent)`
|
||||
&&& {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
margin-right: 1.5em;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledTag = styled(Tag)`
|
||||
&&& {
|
||||
white-space: normal;
|
||||
}
|
||||
`;
|
||||
|
@ -2,7 +2,7 @@
|
||||
import type { SelectProps } from 'antd';
|
||||
import { Tag } from 'antd';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Alerts } from 'types/api/alerts/getAll';
|
||||
import { Alerts } from 'types/api/alerts/getTriggered';
|
||||
|
||||
import { Container, Select } from './styles';
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { Tag, Typography } from 'antd';
|
||||
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm';
|
||||
import getFormattedDate from 'lib/getFormatedDate';
|
||||
import React from 'react';
|
||||
import { Alerts } from 'types/api/alerts/getAll';
|
||||
import { Alerts } from 'types/api/alerts/getTriggered';
|
||||
|
||||
import Status from '../TableComponents/AlertStatus';
|
||||
import { TableCell, TableRow } from './styles';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { MinusSquareOutlined, PlusSquareOutlined } from '@ant-design/icons';
|
||||
import { Tag } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { Alerts } from 'types/api/alerts/getAll';
|
||||
import { Alerts } from 'types/api/alerts/getTriggered';
|
||||
|
||||
import ExapandableRow from './ExapandableRow';
|
||||
import { IconContainer, StatusContainer, TableCell, TableRow } from './styles';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import groupBy from 'lodash-es/groupBy';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Alerts } from 'types/api/alerts/getAll';
|
||||
import { Alerts } from 'types/api/alerts/getTriggered';
|
||||
|
||||
import { Value } from '../Filter';
|
||||
import { FilterAlerts } from '../utils';
|
||||
|
@ -5,7 +5,7 @@ import AlertStatus from 'container/TriggeredAlerts/TableComponents/AlertStatus';
|
||||
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm';
|
||||
import getFormattedDate from 'lib/getFormatedDate';
|
||||
import React from 'react';
|
||||
import { Alerts } from 'types/api/alerts/getAll';
|
||||
import { Alerts } from 'types/api/alerts/getTriggered';
|
||||
|
||||
import { Value } from './Filter';
|
||||
import { FilterAlerts } from './utils';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import getTriggeredApi from 'api/alerts/getTriggered';
|
||||
import useInterval from 'hooks/useInterval';
|
||||
import React, { useState } from 'react';
|
||||
import { Alerts } from 'types/api/alerts/getAll';
|
||||
import { Alerts } from 'types/api/alerts/getTriggered';
|
||||
|
||||
import Filter, { Value } from './Filter';
|
||||
import FilteredTable from './FilteredTable';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Alerts } from 'types/api/alerts/getAll';
|
||||
import { Alerts } from 'types/api/alerts/getTriggered';
|
||||
|
||||
import { Value } from './Filter';
|
||||
|
||||
|
@ -18,6 +18,7 @@ export interface AlertDef {
|
||||
annotations?: Labels;
|
||||
evalWindow?: string;
|
||||
source?: string;
|
||||
disabled?: boolean;
|
||||
preferredChannels?: string[];
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Alerts } from './getAll';
|
||||
import { AlertDef } from './def';
|
||||
|
||||
export interface Props {
|
||||
id: Alerts['id'];
|
||||
id: AlertDef['id'];
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
|
@ -4,6 +4,13 @@ export interface Props {
|
||||
id: AlertDef['id'];
|
||||
}
|
||||
|
||||
export interface GettableAlert extends AlertDef {
|
||||
id: number;
|
||||
alert: string;
|
||||
state: string;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export type PayloadProps = {
|
||||
data: AlertDef;
|
||||
data: GettableAlert;
|
||||
};
|
||||
|
@ -1,32 +1,3 @@
|
||||
export interface Alerts {
|
||||
labels: AlertsLabel;
|
||||
annotations: {
|
||||
description: string;
|
||||
summary: string;
|
||||
[key: string]: string;
|
||||
};
|
||||
state: string;
|
||||
name: string;
|
||||
id: number;
|
||||
endsAt: string;
|
||||
fingerprint: string;
|
||||
generatorURL: string;
|
||||
receivers: Receivers[];
|
||||
startsAt: string;
|
||||
status: {
|
||||
inhibitedBy: [];
|
||||
silencedBy: [];
|
||||
state: string;
|
||||
};
|
||||
updatedAt: string;
|
||||
}
|
||||
import { GettableAlert } from './get';
|
||||
|
||||
interface Receivers {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface AlertsLabel {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export type PayloadProps = Alerts[];
|
||||
export type PayloadProps = GettableAlert[];
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Alerts } from './getAll';
|
||||
import { AlertDef } from './def';
|
||||
|
||||
export interface Props {
|
||||
silenced: boolean;
|
||||
@ -7,8 +7,8 @@ export interface Props {
|
||||
[key: string]: string | boolean;
|
||||
}
|
||||
export interface Group {
|
||||
alerts: Alerts[];
|
||||
label: Alerts['labels'];
|
||||
alerts: AlertDef[];
|
||||
label: AlertDef['labels'];
|
||||
receiver: {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
@ -1,4 +1,33 @@
|
||||
import { Alerts } from './getAll';
|
||||
export interface Alerts {
|
||||
labels: AlertsLabel;
|
||||
annotations: {
|
||||
description: string;
|
||||
summary: string;
|
||||
[key: string]: string;
|
||||
};
|
||||
state: string;
|
||||
name: string;
|
||||
id: number;
|
||||
endsAt: string;
|
||||
fingerprint: string;
|
||||
generatorURL: string;
|
||||
receivers: Receivers[];
|
||||
startsAt: string;
|
||||
status: {
|
||||
inhibitedBy: [];
|
||||
silencedBy: [];
|
||||
state: string;
|
||||
};
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface Receivers {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface AlertsLabel {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
silenced: boolean;
|
||||
|
12
frontend/src/types/api/alerts/patch.ts
Normal file
12
frontend/src/types/api/alerts/patch.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { GettableAlert } from './get';
|
||||
|
||||
export type PayloadProps = GettableAlert;
|
||||
|
||||
export interface PatchProps {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
id?: number;
|
||||
data: PatchProps;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user