mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-16 08:55:54 +08:00
feat: added track event in Alerts - (multiple places) (#5354)
* feat: added track event in Alerts - (multiple places) * feat: comment resolve and code refactor * feat: add Alert Channel: Channel list page visited event * feat: removed testSuccess variable and used responseStatus directly * feat: added save status in alert channel: save action * feat: added channel detail in save and test notification event * feat: code refactor * feat: added status message for save and test * feat: added status message for save channel events * feat: code refactor
This commit is contained in:
parent
4f2c314f39
commit
53c6288025
@ -5,7 +5,13 @@ import { Button, Dropdown, MenuProps } from 'antd';
|
|||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
function DropDown({ element }: { element: JSX.Element[] }): JSX.Element {
|
function DropDown({
|
||||||
|
element,
|
||||||
|
onDropDownItemClick,
|
||||||
|
}: {
|
||||||
|
element: JSX.Element[];
|
||||||
|
onDropDownItemClick?: MenuProps['onClick'];
|
||||||
|
}): JSX.Element {
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
const items: MenuProps['items'] = element.map(
|
const items: MenuProps['items'] = element.map(
|
||||||
@ -23,6 +29,7 @@ function DropDown({ element }: { element: JSX.Element[] }): JSX.Element {
|
|||||||
items,
|
items,
|
||||||
onMouseEnter: (): void => setDdOpen(true),
|
onMouseEnter: (): void => setDdOpen(true),
|
||||||
onMouseLeave: (): void => setDdOpen(false),
|
onMouseLeave: (): void => setDdOpen(false),
|
||||||
|
onClick: (item): void => onDropDownItemClick?.(item),
|
||||||
}}
|
}}
|
||||||
open={isDdOpen}
|
open={isDdOpen}
|
||||||
>
|
>
|
||||||
@ -40,4 +47,8 @@ function DropDown({ element }: { element: JSX.Element[] }): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DropDown.defaultProps = {
|
||||||
|
onDropDownItemClick: (): void => {},
|
||||||
|
};
|
||||||
|
|
||||||
export default DropDown;
|
export default DropDown;
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
import './DynamicColumnTable.syles.scss';
|
import './DynamicColumnTable.syles.scss';
|
||||||
|
|
||||||
import { Button, Dropdown, Flex, MenuProps, Switch } from 'antd';
|
import { Button, Dropdown, Flex, MenuProps, Switch } from 'antd';
|
||||||
|
import { ColumnGroupType, ColumnType } from 'antd/es/table';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
||||||
import { SlidersHorizontal } from 'lucide-react';
|
import { SlidersHorizontal } from 'lucide-react';
|
||||||
import { memo, useEffect, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
@ -22,6 +24,7 @@ function DynamicColumnTable({
|
|||||||
dynamicColumns,
|
dynamicColumns,
|
||||||
onDragColumn,
|
onDragColumn,
|
||||||
facingIssueBtn,
|
facingIssueBtn,
|
||||||
|
shouldSendAlertsLogEvent,
|
||||||
...restProps
|
...restProps
|
||||||
}: DynamicColumnTableProps): JSX.Element {
|
}: DynamicColumnTableProps): JSX.Element {
|
||||||
const [columnsData, setColumnsData] = useState<ColumnsType | undefined>(
|
const [columnsData, setColumnsData] = useState<ColumnsType | undefined>(
|
||||||
@ -47,11 +50,18 @@ function DynamicColumnTable({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [columns, dynamicColumns]);
|
}, [columns, dynamicColumns]);
|
||||||
|
|
||||||
const onToggleHandler = (index: number) => (
|
const onToggleHandler = (
|
||||||
checked: boolean,
|
index: number,
|
||||||
event: React.MouseEvent<HTMLButtonElement>,
|
column: ColumnGroupType<any> | ColumnType<any>,
|
||||||
): void => {
|
) => (checked: boolean, event: React.MouseEvent<HTMLButtonElement>): void => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (shouldSendAlertsLogEvent) {
|
||||||
|
logEvent('Alert: Column toggled', {
|
||||||
|
column: column?.title,
|
||||||
|
action: checked ? 'Enable' : 'Disable',
|
||||||
|
});
|
||||||
|
}
|
||||||
setVisibleColumns({
|
setVisibleColumns({
|
||||||
tablesource,
|
tablesource,
|
||||||
dynamicColumns,
|
dynamicColumns,
|
||||||
@ -75,7 +85,7 @@ function DynamicColumnTable({
|
|||||||
<div>{column.title?.toString()}</div>
|
<div>{column.title?.toString()}</div>
|
||||||
<Switch
|
<Switch
|
||||||
checked={columnsData?.findIndex((c) => c.key === column.key) !== -1}
|
checked={columnsData?.findIndex((c) => c.key === column.key) !== -1}
|
||||||
onChange={onToggleHandler(index)}
|
onChange={onToggleHandler(index, column)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
@ -14,6 +14,7 @@ export interface DynamicColumnTableProps extends TableProps<any> {
|
|||||||
dynamicColumns: TableProps<any>['columns'];
|
dynamicColumns: TableProps<any>['columns'];
|
||||||
onDragColumn?: (fromIndex: number, toIndex: number) => void;
|
onDragColumn?: (fromIndex: number, toIndex: number) => void;
|
||||||
facingIssueBtn?: FacingIssueBtnProps;
|
facingIssueBtn?: FacingIssueBtnProps;
|
||||||
|
shouldSendAlertsLogEvent?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GetVisibleColumnsFunction = (
|
export type GetVisibleColumnsFunction = (
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { Tooltip, Typography } from 'antd';
|
import { Tooltip, Typography } from 'antd';
|
||||||
import getAll from 'api/channels/getAll';
|
import getAll from 'api/channels/getAll';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import TextToolTip from 'components/TextToolTip';
|
import TextToolTip from 'components/TextToolTip';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
import useFetch from 'hooks/useFetch';
|
import useFetch from 'hooks/useFetch';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { useCallback } from 'react';
|
import { isUndefined } from 'lodash-es';
|
||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@ -31,6 +33,14 @@ function AlertChannels(): JSX.Element {
|
|||||||
|
|
||||||
const { loading, payload, error, errorMessage } = useFetch(getAll);
|
const { loading, payload, error, errorMessage } = useFetch(getAll);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isUndefined(payload)) {
|
||||||
|
logEvent('Alert Channel: Channel list page visited', {
|
||||||
|
number: payload?.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [payload]);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <Typography>{errorMessage}</Typography>;
|
return <Typography>{errorMessage}</Typography>;
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,12 @@ import testOpsGenie from 'api/channels/testOpsgenie';
|
|||||||
import testPagerApi from 'api/channels/testPager';
|
import testPagerApi from 'api/channels/testPager';
|
||||||
import testSlackApi from 'api/channels/testSlack';
|
import testSlackApi from 'api/channels/testSlack';
|
||||||
import testWebhookApi from 'api/channels/testWebhook';
|
import testWebhookApi from 'api/channels/testWebhook';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import FormAlertChannels from 'container/FormAlertChannels';
|
import FormAlertChannels from 'container/FormAlertChannels';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -43,6 +44,10 @@ function CreateAlertChannels({
|
|||||||
|
|
||||||
const [formInstance] = Form.useForm();
|
const [formInstance] = Form.useForm();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
logEvent('Alert Channel: Create channel page visited', {});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [selectedConfig, setSelectedConfig] = useState<
|
const [selectedConfig, setSelectedConfig] = useState<
|
||||||
Partial<
|
Partial<
|
||||||
SlackChannel &
|
SlackChannel &
|
||||||
@ -139,19 +144,25 @@ function CreateAlertChannels({
|
|||||||
description: t('channel_creation_done'),
|
description: t('channel_creation_done'),
|
||||||
});
|
});
|
||||||
history.replace(ROUTES.ALL_CHANNELS);
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
} else {
|
return { status: 'success', statusMessage: t('channel_creation_done') };
|
||||||
notifications.error({
|
|
||||||
message: 'Error',
|
|
||||||
description: response.error || t('channel_creation_failed'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: response.error || t('channel_creation_failed'),
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
status: 'failed',
|
||||||
|
statusMessage: response.error || t('channel_creation_failed'),
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('channel_creation_failed'),
|
description: t('channel_creation_failed'),
|
||||||
});
|
});
|
||||||
|
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||||
|
} finally {
|
||||||
|
setSavingState(false);
|
||||||
}
|
}
|
||||||
setSavingState(false);
|
|
||||||
}, [prepareSlackRequest, t, notifications]);
|
}, [prepareSlackRequest, t, notifications]);
|
||||||
|
|
||||||
const prepareWebhookRequest = useCallback(() => {
|
const prepareWebhookRequest = useCallback(() => {
|
||||||
@ -200,19 +211,25 @@ function CreateAlertChannels({
|
|||||||
description: t('channel_creation_done'),
|
description: t('channel_creation_done'),
|
||||||
});
|
});
|
||||||
history.replace(ROUTES.ALL_CHANNELS);
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
} else {
|
return { status: 'success', statusMessage: t('channel_creation_done') };
|
||||||
notifications.error({
|
|
||||||
message: 'Error',
|
|
||||||
description: response.error || t('channel_creation_failed'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: response.error || t('channel_creation_failed'),
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
status: 'failed',
|
||||||
|
statusMessage: response.error || t('channel_creation_failed'),
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('channel_creation_failed'),
|
description: t('channel_creation_failed'),
|
||||||
});
|
});
|
||||||
|
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||||
|
} finally {
|
||||||
|
setSavingState(false);
|
||||||
}
|
}
|
||||||
setSavingState(false);
|
|
||||||
}, [prepareWebhookRequest, t, notifications]);
|
}, [prepareWebhookRequest, t, notifications]);
|
||||||
|
|
||||||
const preparePagerRequest = useCallback(() => {
|
const preparePagerRequest = useCallback(() => {
|
||||||
@ -245,8 +262,8 @@ function CreateAlertChannels({
|
|||||||
setSavingState(true);
|
setSavingState(true);
|
||||||
const request = preparePagerRequest();
|
const request = preparePagerRequest();
|
||||||
|
|
||||||
if (request) {
|
try {
|
||||||
try {
|
if (request) {
|
||||||
const response = await createPagerApi(request);
|
const response = await createPagerApi(request);
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
@ -255,20 +272,31 @@ function CreateAlertChannels({
|
|||||||
description: t('channel_creation_done'),
|
description: t('channel_creation_done'),
|
||||||
});
|
});
|
||||||
history.replace(ROUTES.ALL_CHANNELS);
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
} else {
|
return { status: 'success', statusMessage: t('channel_creation_done') };
|
||||||
notifications.error({
|
|
||||||
message: 'Error',
|
|
||||||
description: response.error || t('channel_creation_failed'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('channel_creation_failed'),
|
description: response.error || t('channel_creation_failed'),
|
||||||
});
|
});
|
||||||
|
return {
|
||||||
|
status: 'failed',
|
||||||
|
statusMessage: response.error || t('channel_creation_failed'),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: t('channel_creation_failed'),
|
||||||
|
});
|
||||||
|
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: t('channel_creation_failed'),
|
||||||
|
});
|
||||||
|
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||||
|
} finally {
|
||||||
|
setSavingState(false);
|
||||||
}
|
}
|
||||||
setSavingState(false);
|
|
||||||
}, [t, notifications, preparePagerRequest]);
|
}, [t, notifications, preparePagerRequest]);
|
||||||
|
|
||||||
const prepareOpsgenieRequest = useCallback(
|
const prepareOpsgenieRequest = useCallback(
|
||||||
@ -295,19 +323,25 @@ function CreateAlertChannels({
|
|||||||
description: t('channel_creation_done'),
|
description: t('channel_creation_done'),
|
||||||
});
|
});
|
||||||
history.replace(ROUTES.ALL_CHANNELS);
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
} else {
|
return { status: 'success', statusMessage: t('channel_creation_done') };
|
||||||
notifications.error({
|
|
||||||
message: 'Error',
|
|
||||||
description: response.error || t('channel_creation_failed'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: response.error || t('channel_creation_failed'),
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
status: 'failed',
|
||||||
|
statusMessage: response.error || t('channel_creation_failed'),
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('channel_creation_failed'),
|
description: t('channel_creation_failed'),
|
||||||
});
|
});
|
||||||
|
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||||
|
} finally {
|
||||||
|
setSavingState(false);
|
||||||
}
|
}
|
||||||
setSavingState(false);
|
|
||||||
}, [prepareOpsgenieRequest, t, notifications]);
|
}, [prepareOpsgenieRequest, t, notifications]);
|
||||||
|
|
||||||
const prepareEmailRequest = useCallback(
|
const prepareEmailRequest = useCallback(
|
||||||
@ -332,19 +366,25 @@ function CreateAlertChannels({
|
|||||||
description: t('channel_creation_done'),
|
description: t('channel_creation_done'),
|
||||||
});
|
});
|
||||||
history.replace(ROUTES.ALL_CHANNELS);
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
} else {
|
return { status: 'success', statusMessage: t('channel_creation_done') };
|
||||||
notifications.error({
|
|
||||||
message: 'Error',
|
|
||||||
description: response.error || t('channel_creation_failed'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: response.error || t('channel_creation_failed'),
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
status: 'failed',
|
||||||
|
statusMessage: response.error || t('channel_creation_failed'),
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('channel_creation_failed'),
|
description: t('channel_creation_failed'),
|
||||||
});
|
});
|
||||||
|
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||||
|
} finally {
|
||||||
|
setSavingState(false);
|
||||||
}
|
}
|
||||||
setSavingState(false);
|
|
||||||
}, [prepareEmailRequest, t, notifications]);
|
}, [prepareEmailRequest, t, notifications]);
|
||||||
|
|
||||||
const prepareMsTeamsRequest = useCallback(
|
const prepareMsTeamsRequest = useCallback(
|
||||||
@ -370,19 +410,25 @@ function CreateAlertChannels({
|
|||||||
description: t('channel_creation_done'),
|
description: t('channel_creation_done'),
|
||||||
});
|
});
|
||||||
history.replace(ROUTES.ALL_CHANNELS);
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
} else {
|
return { status: 'success', statusMessage: t('channel_creation_done') };
|
||||||
notifications.error({
|
|
||||||
message: 'Error',
|
|
||||||
description: response.error || t('channel_creation_failed'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: response.error || t('channel_creation_failed'),
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
status: 'failed',
|
||||||
|
statusMessage: response.error || t('channel_creation_failed'),
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('channel_creation_failed'),
|
description: t('channel_creation_failed'),
|
||||||
});
|
});
|
||||||
|
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||||
|
} finally {
|
||||||
|
setSavingState(false);
|
||||||
}
|
}
|
||||||
setSavingState(false);
|
|
||||||
}, [prepareMsTeamsRequest, t, notifications]);
|
}, [prepareMsTeamsRequest, t, notifications]);
|
||||||
|
|
||||||
const onSaveHandler = useCallback(
|
const onSaveHandler = useCallback(
|
||||||
@ -400,7 +446,15 @@ function CreateAlertChannels({
|
|||||||
const functionToCall = functionMapper[value as keyof typeof functionMapper];
|
const functionToCall = functionMapper[value as keyof typeof functionMapper];
|
||||||
|
|
||||||
if (functionToCall) {
|
if (functionToCall) {
|
||||||
functionToCall();
|
const result = await functionToCall();
|
||||||
|
logEvent('Alert Channel: Save channel', {
|
||||||
|
type: value,
|
||||||
|
sendResolvedAlert: selectedConfig.send_resolved,
|
||||||
|
name: selectedConfig.name,
|
||||||
|
new: 'true',
|
||||||
|
status: result?.status,
|
||||||
|
statusMessage: result?.statusMessage,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
@ -409,6 +463,7 @@ function CreateAlertChannels({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[
|
[
|
||||||
onSlackHandler,
|
onSlackHandler,
|
||||||
onWebhookHandler,
|
onWebhookHandler,
|
||||||
@ -472,14 +527,25 @@ function CreateAlertChannels({
|
|||||||
description: t('channel_test_failed'),
|
description: t('channel_test_failed'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logEvent('Alert Channel: Test notification', {
|
||||||
|
type: channelType,
|
||||||
|
sendResolvedAlert: selectedConfig.send_resolved,
|
||||||
|
name: selectedConfig.name,
|
||||||
|
new: 'true',
|
||||||
|
status:
|
||||||
|
response && response.statusCode === 200 ? 'Test success' : 'Test failed',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('channel_test_unexpected'),
|
description: t('channel_test_unexpected'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setTestingState(false);
|
setTestingState(false);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[
|
[
|
||||||
prepareWebhookRequest,
|
prepareWebhookRequest,
|
||||||
t,
|
t,
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { Row, Typography } from 'antd';
|
import { Row, Typography } from 'antd';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||||
@ -34,6 +36,13 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logEvent('Alert: Sample alert link clicked', {
|
||||||
|
dataSource: ALERTS_DATA_SOURCE_MAP[option],
|
||||||
|
link: url,
|
||||||
|
page: 'New alert data source selection page',
|
||||||
|
});
|
||||||
|
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
}
|
}
|
||||||
const renderOptions = useMemo(
|
const renderOptions = useMemo(
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Form, Row } from 'antd';
|
import { Form, Row } from 'antd';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import FormAlertRules from 'container/FormAlertRules';
|
import FormAlertRules from 'container/FormAlertRules';
|
||||||
@ -68,6 +69,8 @@ function CreateRules(): JSX.Element {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (alertType) {
|
if (alertType) {
|
||||||
onSelectType(alertType);
|
onSelectType(alertType);
|
||||||
|
} else {
|
||||||
|
logEvent('Alert: New alert data source selection page visited', {});
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [alertType]);
|
}, [alertType]);
|
||||||
|
@ -11,6 +11,7 @@ import testOpsgenie from 'api/channels/testOpsgenie';
|
|||||||
import testPagerApi from 'api/channels/testPager';
|
import testPagerApi from 'api/channels/testPager';
|
||||||
import testSlackApi from 'api/channels/testSlack';
|
import testSlackApi from 'api/channels/testSlack';
|
||||||
import testWebhookApi from 'api/channels/testWebhook';
|
import testWebhookApi from 'api/channels/testWebhook';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import {
|
import {
|
||||||
ChannelType,
|
ChannelType,
|
||||||
@ -89,7 +90,7 @@ function EditAlertChannels({
|
|||||||
description: t('webhook_url_required'),
|
description: t('webhook_url_required'),
|
||||||
});
|
});
|
||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
return;
|
return { status: 'failed', statusMessage: t('webhook_url_required') };
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await editSlackApi(prepareSlackRequest());
|
const response = await editSlackApi(prepareSlackRequest());
|
||||||
@ -101,13 +102,17 @@ function EditAlertChannels({
|
|||||||
});
|
});
|
||||||
|
|
||||||
history.replace(ROUTES.ALL_CHANNELS);
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
} else {
|
return { status: 'success', statusMessage: t('channel_edit_done') };
|
||||||
notifications.error({
|
|
||||||
message: 'Error',
|
|
||||||
description: response.error || t('channel_edit_failed'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: response.error || t('channel_edit_failed'),
|
||||||
|
});
|
||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
|
return {
|
||||||
|
status: 'failed',
|
||||||
|
statusMessage: response.error || t('channel_edit_failed'),
|
||||||
|
};
|
||||||
}, [prepareSlackRequest, t, notifications, selectedConfig]);
|
}, [prepareSlackRequest, t, notifications, selectedConfig]);
|
||||||
|
|
||||||
const prepareWebhookRequest = useCallback(() => {
|
const prepareWebhookRequest = useCallback(() => {
|
||||||
@ -136,13 +141,13 @@ function EditAlertChannels({
|
|||||||
if (selectedConfig?.api_url === '') {
|
if (selectedConfig?.api_url === '') {
|
||||||
showError(t('webhook_url_required'));
|
showError(t('webhook_url_required'));
|
||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
return;
|
return { status: 'failed', statusMessage: t('webhook_url_required') };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (username && (!password || password === '')) {
|
if (username && (!password || password === '')) {
|
||||||
showError(t('username_no_password'));
|
showError(t('username_no_password'));
|
||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
return;
|
return { status: 'failed', statusMessage: t('username_no_password') };
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await editWebhookApi(prepareWebhookRequest());
|
const response = await editWebhookApi(prepareWebhookRequest());
|
||||||
@ -154,10 +159,15 @@ function EditAlertChannels({
|
|||||||
});
|
});
|
||||||
|
|
||||||
history.replace(ROUTES.ALL_CHANNELS);
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
} else {
|
return { status: 'success', statusMessage: t('channel_edit_done') };
|
||||||
showError(response.error || t('channel_edit_failed'));
|
|
||||||
}
|
}
|
||||||
|
showError(response.error || t('channel_edit_failed'));
|
||||||
|
|
||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
|
return {
|
||||||
|
status: 'failed',
|
||||||
|
statusMessage: response.error || t('channel_edit_failed'),
|
||||||
|
};
|
||||||
}, [prepareWebhookRequest, t, notifications, selectedConfig]);
|
}, [prepareWebhookRequest, t, notifications, selectedConfig]);
|
||||||
|
|
||||||
const prepareEmailRequest = useCallback(
|
const prepareEmailRequest = useCallback(
|
||||||
@ -181,13 +191,18 @@ function EditAlertChannels({
|
|||||||
description: t('channel_edit_done'),
|
description: t('channel_edit_done'),
|
||||||
});
|
});
|
||||||
history.replace(ROUTES.ALL_CHANNELS);
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
} else {
|
return { status: 'success', statusMessage: t('channel_edit_done') };
|
||||||
notifications.error({
|
|
||||||
message: 'Error',
|
|
||||||
description: response.error || t('channel_edit_failed'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: response.error || t('channel_edit_failed'),
|
||||||
|
});
|
||||||
|
|
||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
|
return {
|
||||||
|
status: 'failed',
|
||||||
|
statusMessage: response.error || t('channel_edit_failed'),
|
||||||
|
};
|
||||||
}, [prepareEmailRequest, t, notifications]);
|
}, [prepareEmailRequest, t, notifications]);
|
||||||
|
|
||||||
const preparePagerRequest = useCallback(
|
const preparePagerRequest = useCallback(
|
||||||
@ -218,7 +233,7 @@ function EditAlertChannels({
|
|||||||
description: validationError,
|
description: validationError,
|
||||||
});
|
});
|
||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
return;
|
return { status: 'failed', statusMessage: validationError };
|
||||||
}
|
}
|
||||||
const response = await editPagerApi(preparePagerRequest());
|
const response = await editPagerApi(preparePagerRequest());
|
||||||
|
|
||||||
@ -229,13 +244,18 @@ function EditAlertChannels({
|
|||||||
});
|
});
|
||||||
|
|
||||||
history.replace(ROUTES.ALL_CHANNELS);
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
} else {
|
return { status: 'success', statusMessage: t('channel_edit_done') };
|
||||||
notifications.error({
|
|
||||||
message: 'Error',
|
|
||||||
description: response.error || t('channel_edit_failed'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: response.error || t('channel_edit_failed'),
|
||||||
|
});
|
||||||
|
|
||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
|
return {
|
||||||
|
status: 'failed',
|
||||||
|
statusMessage: response.error || t('channel_edit_failed'),
|
||||||
|
};
|
||||||
}, [preparePagerRequest, notifications, selectedConfig, t]);
|
}, [preparePagerRequest, notifications, selectedConfig, t]);
|
||||||
|
|
||||||
const prepareOpsgenieRequest = useCallback(
|
const prepareOpsgenieRequest = useCallback(
|
||||||
@ -259,7 +279,7 @@ function EditAlertChannels({
|
|||||||
description: t('api_key_required'),
|
description: t('api_key_required'),
|
||||||
});
|
});
|
||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
return;
|
return { status: 'failed', statusMessage: t('api_key_required') };
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await editOpsgenie(prepareOpsgenieRequest());
|
const response = await editOpsgenie(prepareOpsgenieRequest());
|
||||||
@ -271,13 +291,18 @@ function EditAlertChannels({
|
|||||||
});
|
});
|
||||||
|
|
||||||
history.replace(ROUTES.ALL_CHANNELS);
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
} else {
|
return { status: 'success', statusMessage: t('channel_edit_done') };
|
||||||
notifications.error({
|
|
||||||
message: 'Error',
|
|
||||||
description: response.error || t('channel_edit_failed'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: response.error || t('channel_edit_failed'),
|
||||||
|
});
|
||||||
|
|
||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
|
return {
|
||||||
|
status: 'failed',
|
||||||
|
statusMessage: response.error || t('channel_edit_failed'),
|
||||||
|
};
|
||||||
}, [prepareOpsgenieRequest, t, notifications, selectedConfig]);
|
}, [prepareOpsgenieRequest, t, notifications, selectedConfig]);
|
||||||
|
|
||||||
const prepareMsTeamsRequest = useCallback(
|
const prepareMsTeamsRequest = useCallback(
|
||||||
@ -301,7 +326,7 @@ function EditAlertChannels({
|
|||||||
description: t('webhook_url_required'),
|
description: t('webhook_url_required'),
|
||||||
});
|
});
|
||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
return;
|
return { status: 'failed', statusMessage: t('webhook_url_required') };
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await editMsTeamsApi(prepareMsTeamsRequest());
|
const response = await editMsTeamsApi(prepareMsTeamsRequest());
|
||||||
@ -313,31 +338,46 @@ function EditAlertChannels({
|
|||||||
});
|
});
|
||||||
|
|
||||||
history.replace(ROUTES.ALL_CHANNELS);
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
} else {
|
return { status: 'success', statusMessage: t('channel_edit_done') };
|
||||||
notifications.error({
|
|
||||||
message: 'Error',
|
|
||||||
description: response.error || t('channel_edit_failed'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: response.error || t('channel_edit_failed'),
|
||||||
|
});
|
||||||
|
|
||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
|
return {
|
||||||
|
status: 'failed',
|
||||||
|
statusMessage: response.error || t('channel_edit_failed'),
|
||||||
|
};
|
||||||
}, [prepareMsTeamsRequest, t, notifications, selectedConfig]);
|
}, [prepareMsTeamsRequest, t, notifications, selectedConfig]);
|
||||||
|
|
||||||
const onSaveHandler = useCallback(
|
const onSaveHandler = useCallback(
|
||||||
(value: ChannelType) => {
|
async (value: ChannelType) => {
|
||||||
|
let result;
|
||||||
if (value === ChannelType.Slack) {
|
if (value === ChannelType.Slack) {
|
||||||
onSlackEditHandler();
|
result = await onSlackEditHandler();
|
||||||
} else if (value === ChannelType.Webhook) {
|
} else if (value === ChannelType.Webhook) {
|
||||||
onWebhookEditHandler();
|
result = await onWebhookEditHandler();
|
||||||
} else if (value === ChannelType.Pagerduty) {
|
} else if (value === ChannelType.Pagerduty) {
|
||||||
onPagerEditHandler();
|
result = await onPagerEditHandler();
|
||||||
} else if (value === ChannelType.MsTeams) {
|
} else if (value === ChannelType.MsTeams) {
|
||||||
onMsTeamsEditHandler();
|
result = await onMsTeamsEditHandler();
|
||||||
} else if (value === ChannelType.Opsgenie) {
|
} else if (value === ChannelType.Opsgenie) {
|
||||||
onOpsgenieEditHandler();
|
result = await onOpsgenieEditHandler();
|
||||||
} else if (value === ChannelType.Email) {
|
} else if (value === ChannelType.Email) {
|
||||||
onEmailEditHandler();
|
result = await onEmailEditHandler();
|
||||||
}
|
}
|
||||||
|
logEvent('Alert Channel: Save channel', {
|
||||||
|
type: value,
|
||||||
|
sendResolvedAlert: selectedConfig.send_resolved,
|
||||||
|
name: selectedConfig.name,
|
||||||
|
new: 'false',
|
||||||
|
status: result?.status,
|
||||||
|
statusMessage: result?.statusMessage,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[
|
[
|
||||||
onSlackEditHandler,
|
onSlackEditHandler,
|
||||||
onWebhookEditHandler,
|
onWebhookEditHandler,
|
||||||
@ -399,6 +439,14 @@ function EditAlertChannels({
|
|||||||
description: t('channel_test_failed'),
|
description: t('channel_test_failed'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
logEvent('Alert Channel: Test notification', {
|
||||||
|
type: channelType,
|
||||||
|
sendResolvedAlert: selectedConfig.send_resolved,
|
||||||
|
name: selectedConfig.name,
|
||||||
|
new: 'false',
|
||||||
|
status:
|
||||||
|
response && response.statusCode === 200 ? 'Test success' : 'Test failed',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
@ -407,6 +455,7 @@ function EditAlertChannels({
|
|||||||
}
|
}
|
||||||
setTestingState(false);
|
setTestingState(false);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[
|
[
|
||||||
t,
|
t,
|
||||||
prepareWebhookRequest,
|
prepareWebhookRequest,
|
||||||
|
@ -3,6 +3,8 @@ import './FormAlertRules.styles.scss';
|
|||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { Button, Form, Select, Switch, Tooltip } from 'antd';
|
import { Button, Form, Select, Switch, Tooltip } from 'antd';
|
||||||
import getChannels from 'api/channels/getAll';
|
import getChannels from 'api/channels/getAll';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
import useFetch from 'hooks/useFetch';
|
import useFetch from 'hooks/useFetch';
|
||||||
@ -10,6 +12,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||||
import { AlertDef, Labels } from 'types/api/alerts/def';
|
import { AlertDef, Labels } from 'types/api/alerts/def';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
|
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
|
||||||
@ -73,9 +76,24 @@ function BasicInfo({
|
|||||||
|
|
||||||
const noChannels = channels.payload?.length === 0;
|
const noChannels = channels.payload?.length === 0;
|
||||||
const handleCreateNewChannels = useCallback(() => {
|
const handleCreateNewChannels = useCallback(() => {
|
||||||
|
logEvent('Alert: Create notification channel button clicked', {
|
||||||
|
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||||
|
ruleId: isNewRule ? 0 : alertDef?.id,
|
||||||
|
});
|
||||||
window.open(ROUTES.CHANNELS_NEW, '_blank');
|
window.open(ROUTES.CHANNELS_NEW, '_blank');
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!channels.loading && isNewRule) {
|
||||||
|
logEvent('Alert: New alert creation page visited', {
|
||||||
|
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||||
|
numberOfChannels: channels.payload?.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [channels.payload, channels.loading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StepHeading> {t('alert_form_step3')} </StepHeading>
|
<StepHeading> {t('alert_form_step3')} </StepHeading>
|
||||||
|
@ -2,6 +2,7 @@ import './QuerySection.styles.scss';
|
|||||||
|
|
||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Button, Tabs, Tooltip } from 'antd';
|
import { Button, Tabs, Tooltip } from 'antd';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import PromQLIcon from 'assets/Dashboard/PromQl';
|
import PromQLIcon from 'assets/Dashboard/PromQl';
|
||||||
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||||
@ -31,6 +32,7 @@ function QuerySection({
|
|||||||
runQuery,
|
runQuery,
|
||||||
alertDef,
|
alertDef,
|
||||||
panelType,
|
panelType,
|
||||||
|
ruleId,
|
||||||
}: QuerySectionProps): JSX.Element {
|
}: QuerySectionProps): JSX.Element {
|
||||||
// init namespace for translations
|
// init namespace for translations
|
||||||
const { t } = useTranslation('alerts');
|
const { t } = useTranslation('alerts');
|
||||||
@ -158,7 +160,15 @@ function QuerySection({
|
|||||||
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={runQuery}
|
onClick={(): void => {
|
||||||
|
runQuery();
|
||||||
|
logEvent('Alert: Stage and run query', {
|
||||||
|
dataSource: ALERTS_DATA_SOURCE_MAP[alertType],
|
||||||
|
isNewRule: !ruleId || ruleId === 0,
|
||||||
|
ruleId,
|
||||||
|
queryType: queryCategory,
|
||||||
|
});
|
||||||
|
}}
|
||||||
className="stage-run-query"
|
className="stage-run-query"
|
||||||
icon={<Play size={14} />}
|
icon={<Play size={14} />}
|
||||||
>
|
>
|
||||||
@ -228,6 +238,7 @@ interface QuerySectionProps {
|
|||||||
runQuery: VoidFunction;
|
runQuery: VoidFunction;
|
||||||
alertDef: AlertDef;
|
alertDef: AlertDef;
|
||||||
panelType: PANEL_TYPES;
|
panelType: PANEL_TYPES;
|
||||||
|
ruleId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default QuerySection;
|
export default QuerySection;
|
||||||
|
@ -12,8 +12,10 @@ import {
|
|||||||
} from 'antd';
|
} from 'antd';
|
||||||
import saveAlertApi from 'api/alerts/save';
|
import saveAlertApi from 'api/alerts/save';
|
||||||
import testAlertApi from 'api/alerts/testAlert';
|
import testAlertApi from 'api/alerts/testAlert';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
||||||
import { alertHelpMessage } from 'components/facingIssueBtn/util';
|
import { alertHelpMessage } from 'components/facingIssueBtn/util';
|
||||||
|
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||||
import { FeatureKeys } from 'constants/features';
|
import { FeatureKeys } from 'constants/features';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
@ -338,8 +340,13 @@ function FormAlertRules({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const postableAlert = memoizedPreparePostData();
|
const postableAlert = memoizedPreparePostData();
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
let logData = {
|
||||||
|
status: 'error',
|
||||||
|
statusMessage: t('unexpected_error'),
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const apiReq =
|
const apiReq =
|
||||||
ruleId && ruleId > 0
|
ruleId && ruleId > 0
|
||||||
@ -349,10 +356,15 @@ function FormAlertRules({
|
|||||||
const response = await saveAlertApi(apiReq);
|
const response = await saveAlertApi(apiReq);
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
|
logData = {
|
||||||
|
status: 'success',
|
||||||
|
statusMessage:
|
||||||
|
!ruleId || ruleId === 0 ? t('rule_created') : t('rule_edited'),
|
||||||
|
};
|
||||||
|
|
||||||
notifications.success({
|
notifications.success({
|
||||||
message: 'Success',
|
message: 'Success',
|
||||||
description:
|
description: logData.statusMessage,
|
||||||
!ruleId || ruleId === 0 ? t('rule_created') : t('rule_edited'),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// invalidate rule in cache
|
// invalidate rule in cache
|
||||||
@ -367,18 +379,42 @@ function FormAlertRules({
|
|||||||
history.replace(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`);
|
history.replace(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} else {
|
} else {
|
||||||
|
logData = {
|
||||||
|
status: 'error',
|
||||||
|
statusMessage: response.error || t('unexpected_error'),
|
||||||
|
};
|
||||||
|
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: response.error || t('unexpected_error'),
|
description: logData.statusMessage,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logData = {
|
||||||
|
status: 'error',
|
||||||
|
statusMessage: t('unexpected_error'),
|
||||||
|
};
|
||||||
|
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('unexpected_error'),
|
description: logData.statusMessage,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
|
logEvent('Alert: Save alert', {
|
||||||
|
...logData,
|
||||||
|
dataSource: ALERTS_DATA_SOURCE_MAP[postableAlert?.alertType as AlertTypes],
|
||||||
|
channelNames: postableAlert?.preferredChannels,
|
||||||
|
broadcastToAll: postableAlert?.broadcastToAll,
|
||||||
|
isNewRule: !ruleId || ruleId === 0,
|
||||||
|
ruleId,
|
||||||
|
queryType: currentQuery.queryType,
|
||||||
|
alertId: postableAlert?.id,
|
||||||
|
alertName: postableAlert?.alert,
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [
|
}, [
|
||||||
isFormValid,
|
isFormValid,
|
||||||
memoizedPreparePostData,
|
memoizedPreparePostData,
|
||||||
@ -414,6 +450,7 @@ function FormAlertRules({
|
|||||||
}
|
}
|
||||||
const postableAlert = memoizedPreparePostData();
|
const postableAlert = memoizedPreparePostData();
|
||||||
|
|
||||||
|
let statusResponse = { status: 'failed', message: '' };
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await testAlertApi({ data: postableAlert });
|
const response = await testAlertApi({ data: postableAlert });
|
||||||
@ -425,25 +462,43 @@ function FormAlertRules({
|
|||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('no_alerts_found'),
|
description: t('no_alerts_found'),
|
||||||
});
|
});
|
||||||
|
statusResponse = { status: 'failed', message: t('no_alerts_found') };
|
||||||
} else {
|
} else {
|
||||||
notifications.success({
|
notifications.success({
|
||||||
message: 'Success',
|
message: 'Success',
|
||||||
description: t('rule_test_fired'),
|
description: t('rule_test_fired'),
|
||||||
});
|
});
|
||||||
|
statusResponse = { status: 'success', message: t('rule_test_fired') };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: response.error || t('unexpected_error'),
|
description: response.error || t('unexpected_error'),
|
||||||
});
|
});
|
||||||
|
statusResponse = {
|
||||||
|
status: 'failed',
|
||||||
|
message: response.error || t('unexpected_error'),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('unexpected_error'),
|
description: t('unexpected_error'),
|
||||||
});
|
});
|
||||||
|
statusResponse = { status: 'failed', message: t('unexpected_error') };
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
logEvent('Alert: Test notification', {
|
||||||
|
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||||
|
channelNames: postableAlert?.preferredChannels,
|
||||||
|
broadcastToAll: postableAlert?.broadcastToAll,
|
||||||
|
isNewRule: !ruleId || ruleId === 0,
|
||||||
|
ruleId,
|
||||||
|
queryType: currentQuery.queryType,
|
||||||
|
status: statusResponse.status,
|
||||||
|
statusMessage: statusResponse.message,
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [t, isFormValid, memoizedPreparePostData, notifications]);
|
}, [t, isFormValid, memoizedPreparePostData, notifications]);
|
||||||
|
|
||||||
const renderBasicInfo = (): JSX.Element => (
|
const renderBasicInfo = (): JSX.Element => (
|
||||||
@ -513,6 +568,16 @@ function FormAlertRules({
|
|||||||
|
|
||||||
const isRuleCreated = !ruleId || ruleId === 0;
|
const isRuleCreated = !ruleId || ruleId === 0;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isRuleCreated) {
|
||||||
|
logEvent('Alert: Edit page visited', {
|
||||||
|
ruleId,
|
||||||
|
dataSource: ALERTS_DATA_SOURCE_MAP[alertType as AlertTypes],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
function handleRedirection(option: AlertTypes): void {
|
function handleRedirection(option: AlertTypes): void {
|
||||||
let url = '';
|
let url = '';
|
||||||
switch (option) {
|
switch (option) {
|
||||||
@ -535,6 +600,13 @@ function FormAlertRules({
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
logEvent('Alert: Check example alert clicked', {
|
||||||
|
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||||
|
isNewRule: !ruleId || ruleId === 0,
|
||||||
|
ruleId,
|
||||||
|
queryType: currentQuery.queryType,
|
||||||
|
link: url,
|
||||||
|
});
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -572,6 +644,7 @@ function FormAlertRules({
|
|||||||
alertDef={alertDef}
|
alertDef={alertDef}
|
||||||
panelType={panelType || PANEL_TYPES.TIME_SERIES}
|
panelType={panelType || PANEL_TYPES.TIME_SERIES}
|
||||||
key={currentQuery.queryType}
|
key={currentQuery.queryType}
|
||||||
|
ruleId={ruleId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RuleOptions
|
<RuleOptions
|
||||||
|
@ -7,17 +7,20 @@ interface AlertInfoCardProps {
|
|||||||
header: string;
|
header: string;
|
||||||
subheader: string;
|
subheader: string;
|
||||||
link: string;
|
link: string;
|
||||||
|
onClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertInfoCard({
|
function AlertInfoCard({
|
||||||
header,
|
header,
|
||||||
subheader,
|
subheader,
|
||||||
link,
|
link,
|
||||||
|
onClick,
|
||||||
}: AlertInfoCardProps): JSX.Element {
|
}: AlertInfoCardProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="alert-info-card"
|
className="alert-info-card"
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
|
onClick();
|
||||||
window.open(link, '_blank');
|
window.open(link, '_blank');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -2,6 +2,7 @@ import './AlertsEmptyState.styles.scss';
|
|||||||
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { Button, Divider, Typography } from 'antd';
|
import { Button, Divider, Typography } from 'antd';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
@ -10,12 +11,26 @@ import { useCallback, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
|
|
||||||
import AlertInfoCard from './AlertInfoCard';
|
import AlertInfoCard from './AlertInfoCard';
|
||||||
import { ALERT_CARDS, ALERT_INFO_LINKS } from './alertLinks';
|
import { ALERT_CARDS, ALERT_INFO_LINKS } from './alertLinks';
|
||||||
import InfoLinkText from './InfoLinkText';
|
import InfoLinkText from './InfoLinkText';
|
||||||
|
|
||||||
|
const alertLogEvents = (
|
||||||
|
title: string,
|
||||||
|
link: string,
|
||||||
|
dataSource?: DataSource,
|
||||||
|
): void => {
|
||||||
|
const attributes = {
|
||||||
|
link,
|
||||||
|
page: 'Alert empty state page',
|
||||||
|
};
|
||||||
|
|
||||||
|
logEvent(title, dataSource ? { ...attributes, dataSource } : attributes);
|
||||||
|
};
|
||||||
|
|
||||||
export function AlertsEmptyState(): JSX.Element {
|
export function AlertsEmptyState(): JSX.Element {
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
const { role, featureResponse } = useSelector<AppState, AppReducer>(
|
const { role, featureResponse } = useSelector<AppState, AppReducer>(
|
||||||
@ -91,18 +106,33 @@ export function AlertsEmptyState(): JSX.Element {
|
|||||||
link="https://youtu.be/xjxNIqiv4_M"
|
link="https://youtu.be/xjxNIqiv4_M"
|
||||||
leftIconVisible
|
leftIconVisible
|
||||||
rightIconVisible
|
rightIconVisible
|
||||||
|
onClick={(): void =>
|
||||||
|
alertLogEvents(
|
||||||
|
'Alert: Video tutorial link clicked',
|
||||||
|
'https://youtu.be/xjxNIqiv4_M',
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ALERT_INFO_LINKS.map((info) => (
|
{ALERT_INFO_LINKS.map((info) => {
|
||||||
<InfoLinkText
|
const logEventTriggered = (): void =>
|
||||||
key={info.link}
|
alertLogEvents(
|
||||||
infoText={info.infoText}
|
'Alert: Tutorial doc link clicked',
|
||||||
link={info.link}
|
info.link,
|
||||||
leftIconVisible={info.leftIconVisible}
|
info.dataSource,
|
||||||
rightIconVisible={info.rightIconVisible}
|
);
|
||||||
/>
|
return (
|
||||||
))}
|
<InfoLinkText
|
||||||
|
key={info.link}
|
||||||
|
infoText={info.infoText}
|
||||||
|
link={info.link}
|
||||||
|
leftIconVisible={info.leftIconVisible}
|
||||||
|
rightIconVisible={info.rightIconVisible}
|
||||||
|
onClick={logEventTriggered}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div className="get-started-text">
|
<div className="get-started-text">
|
||||||
@ -113,14 +143,23 @@ export function AlertsEmptyState(): JSX.Element {
|
|||||||
</Divider>
|
</Divider>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ALERT_CARDS.map((card) => (
|
{ALERT_CARDS.map((card) => {
|
||||||
<AlertInfoCard
|
const logEventTriggered = (): void =>
|
||||||
key={card.link}
|
alertLogEvents(
|
||||||
header={card.header}
|
'Alert: Sample alert link clicked',
|
||||||
subheader={card.subheader}
|
card.link,
|
||||||
link={card.link}
|
card.dataSource,
|
||||||
/>
|
);
|
||||||
))}
|
return (
|
||||||
|
<AlertInfoCard
|
||||||
|
key={card.link}
|
||||||
|
header={card.header}
|
||||||
|
subheader={card.subheader}
|
||||||
|
link={card.link}
|
||||||
|
onClick={logEventTriggered}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -6,6 +6,7 @@ interface InfoLinkTextProps {
|
|||||||
link: string;
|
link: string;
|
||||||
leftIconVisible: boolean;
|
leftIconVisible: boolean;
|
||||||
rightIconVisible: boolean;
|
rightIconVisible: boolean;
|
||||||
|
onClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function InfoLinkText({
|
function InfoLinkText({
|
||||||
@ -13,10 +14,12 @@ function InfoLinkText({
|
|||||||
link,
|
link,
|
||||||
leftIconVisible,
|
leftIconVisible,
|
||||||
rightIconVisible,
|
rightIconVisible,
|
||||||
|
onClick,
|
||||||
}: InfoLinkTextProps): JSX.Element {
|
}: InfoLinkTextProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
|
onClick();
|
||||||
window.open(link, '_blank');
|
window.open(link, '_blank');
|
||||||
}}
|
}}
|
||||||
className="info-link-container"
|
className="info-link-container"
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
export const ALERT_INFO_LINKS = [
|
export const ALERT_INFO_LINKS = [
|
||||||
{
|
{
|
||||||
infoText: 'How to create Metrics-based alerts',
|
infoText: 'How to create Metrics-based alerts',
|
||||||
@ -5,6 +7,7 @@ export const ALERT_INFO_LINKS = [
|
|||||||
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page',
|
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page',
|
||||||
leftIconVisible: false,
|
leftIconVisible: false,
|
||||||
rightIconVisible: true,
|
rightIconVisible: true,
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
infoText: 'How to create Log-based alerts',
|
infoText: 'How to create Log-based alerts',
|
||||||
@ -12,6 +15,7 @@ export const ALERT_INFO_LINKS = [
|
|||||||
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-empty-page',
|
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-empty-page',
|
||||||
leftIconVisible: false,
|
leftIconVisible: false,
|
||||||
rightIconVisible: true,
|
rightIconVisible: true,
|
||||||
|
dataSource: DataSource.LOGS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
infoText: 'How to create Trace-based alerts',
|
infoText: 'How to create Trace-based alerts',
|
||||||
@ -19,6 +23,7 @@ export const ALERT_INFO_LINKS = [
|
|||||||
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-empty-page',
|
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-empty-page',
|
||||||
leftIconVisible: false,
|
leftIconVisible: false,
|
||||||
rightIconVisible: true,
|
rightIconVisible: true,
|
||||||
|
dataSource: DataSource.TRACES,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -26,24 +31,28 @@ export const ALERT_CARDS = [
|
|||||||
{
|
{
|
||||||
header: 'Alert on high memory usage',
|
header: 'Alert on high memory usage',
|
||||||
subheader: "Monitor your host's memory usage",
|
subheader: "Monitor your host's memory usage",
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
link:
|
link:
|
||||||
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-memory-usage-for-host-goes-above-400-mb-or-any-fixed-memory',
|
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-memory-usage-for-host-goes-above-400-mb-or-any-fixed-memory',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Alert on slow external API calls',
|
header: 'Alert on slow external API calls',
|
||||||
subheader: 'Monitor your external API calls',
|
subheader: 'Monitor your external API calls',
|
||||||
|
dataSource: DataSource.TRACES,
|
||||||
link:
|
link:
|
||||||
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-external-api-latency-p90-is-over-1-second-for-last-5-mins',
|
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-external-api-latency-p90-is-over-1-second-for-last-5-mins',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Alert on high percentage of timeout errors in logs',
|
header: 'Alert on high percentage of timeout errors in logs',
|
||||||
subheader: 'Monitor your logs for errors',
|
subheader: 'Monitor your logs for errors',
|
||||||
|
dataSource: DataSource.LOGS,
|
||||||
link:
|
link:
|
||||||
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-percentage-of-redis-timeout-error-logs-greater-than-7-in-last-5-mins',
|
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-percentage-of-redis-timeout-error-logs-greater-than-7-in-last-5-mins',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Alert on high error percentage of an endpoint',
|
header: 'Alert on high error percentage of an endpoint',
|
||||||
subheader: 'Monitor your API endpoint',
|
subheader: 'Monitor your API endpoint',
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
link:
|
link:
|
||||||
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page#3-alert-when-the-error-percentage-for-an-endpoint-exceeds-5',
|
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page#3-alert-when-the-error-percentage-for-an-endpoint-exceeds-5',
|
||||||
},
|
},
|
||||||
|
@ -3,6 +3,7 @@ import { PlusOutlined } from '@ant-design/icons';
|
|||||||
import { Input, Typography } from 'antd';
|
import { Input, Typography } from 'antd';
|
||||||
import type { ColumnsType } from 'antd/es/table/interface';
|
import type { ColumnsType } from 'antd/es/table/interface';
|
||||||
import saveAlertApi from 'api/alerts/save';
|
import saveAlertApi from 'api/alerts/save';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import DropDown from 'components/DropDown/DropDown';
|
import DropDown from 'components/DropDown/DropDown';
|
||||||
import { listAlertMessage } from 'components/facingIssueBtn/util';
|
import { listAlertMessage } from 'components/facingIssueBtn/util';
|
||||||
import {
|
import {
|
||||||
@ -41,7 +42,7 @@ import {
|
|||||||
} from './styles';
|
} from './styles';
|
||||||
import Status from './TableComponents/Status';
|
import Status from './TableComponents/Status';
|
||||||
import ToggleAlertState from './ToggleAlertState';
|
import ToggleAlertState from './ToggleAlertState';
|
||||||
import { filterAlerts } from './utils';
|
import { alertActionLogEvent, filterAlerts } from './utils';
|
||||||
|
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
|
|
||||||
@ -107,12 +108,16 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
}, [notificationsApi, t]);
|
}, [notificationsApi, t]);
|
||||||
|
|
||||||
const onClickNewAlertHandler = useCallback(() => {
|
const onClickNewAlertHandler = useCallback(() => {
|
||||||
|
logEvent('Alert: New alert button clicked', {
|
||||||
|
number: allAlertRules?.length,
|
||||||
|
});
|
||||||
featureResponse
|
featureResponse
|
||||||
.refetch()
|
.refetch()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
history.push(ROUTES.ALERTS_NEW);
|
history.push(ROUTES.ALERTS_NEW);
|
||||||
})
|
})
|
||||||
.catch(handleError);
|
.catch(handleError);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [featureResponse, handleError]);
|
}, [featureResponse, handleError]);
|
||||||
|
|
||||||
const onEditHandler = (record: GettableAlert) => (): void => {
|
const onEditHandler = (record: GettableAlert) => (): void => {
|
||||||
@ -321,6 +326,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
width: 10,
|
width: 10,
|
||||||
render: (id: GettableAlert['id'], record): JSX.Element => (
|
render: (id: GettableAlert['id'], record): JSX.Element => (
|
||||||
<DropDown
|
<DropDown
|
||||||
|
onDropDownItemClick={(item): void => alertActionLogEvent(item.key, record)}
|
||||||
element={[
|
element={[
|
||||||
<ToggleAlertState
|
<ToggleAlertState
|
||||||
key="1"
|
key="1"
|
||||||
@ -388,6 +394,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
|
shouldSendAlertsLogEvent
|
||||||
dynamicColumns={dynamicColumns}
|
dynamicColumns={dynamicColumns}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
pagination={paginationConfig}
|
pagination={paginationConfig}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { Space } from 'antd';
|
import { Space } from 'antd';
|
||||||
import getAll from 'api/alerts/getAll';
|
import getAll from 'api/alerts/getAll';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import ReleaseNote from 'components/ReleaseNote';
|
import ReleaseNote from 'components/ReleaseNote';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { useEffect } from 'react';
|
import { isUndefined } from 'lodash-es';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
@ -19,8 +21,19 @@ function ListAlertRules(): JSX.Element {
|
|||||||
cacheTime: 0,
|
cacheTime: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const logEventCalledRef = useRef(false);
|
||||||
|
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!logEventCalledRef.current && !isUndefined(data?.payload)) {
|
||||||
|
logEvent('Alert: List page visited', {
|
||||||
|
number: data?.payload?.length,
|
||||||
|
});
|
||||||
|
logEventCalledRef.current = true;
|
||||||
|
}
|
||||||
|
}, [data?.payload]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === 'error' || (status === 'success' && data.statusCode >= 400)) {
|
if (status === 'error' || (status === 'success' && data.statusCode >= 400)) {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||||
|
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||||
import { GettableAlert } from 'types/api/alerts/get';
|
import { GettableAlert } from 'types/api/alerts/get';
|
||||||
|
|
||||||
export const filterAlerts = (
|
export const filterAlerts = (
|
||||||
@ -23,3 +26,32 @@ export const filterAlerts = (
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const alertActionLogEvent = (
|
||||||
|
action: string,
|
||||||
|
record: GettableAlert,
|
||||||
|
): void => {
|
||||||
|
let actionValue = '';
|
||||||
|
switch (action) {
|
||||||
|
case '0':
|
||||||
|
actionValue = 'Enable/Disable';
|
||||||
|
break;
|
||||||
|
case '1':
|
||||||
|
actionValue = 'Edit';
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
actionValue = 'Clone';
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
actionValue = 'Delete';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
logEvent('Alert: Action', {
|
||||||
|
ruleId: record.id,
|
||||||
|
dataSource: ALERTS_DATA_SOURCE_MAP[record.alertType as AlertTypes],
|
||||||
|
name: record.alert,
|
||||||
|
action: actionValue,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import getTriggeredApi from 'api/alerts/getTriggered';
|
import getTriggeredApi from 'api/alerts/getTriggered';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
import useAxiosError from 'hooks/useAxiosError';
|
import useAxiosError from 'hooks/useAxiosError';
|
||||||
|
import { isUndefined } from 'lodash-es';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@ -13,6 +16,8 @@ function TriggeredAlerts(): JSX.Element {
|
|||||||
(state) => state.app.user?.userId,
|
(state) => state.app.user?.userId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const hasLoggedEvent = useRef(false); // Track if logEvent has been called
|
||||||
|
|
||||||
const handleError = useAxiosError();
|
const handleError = useAxiosError();
|
||||||
|
|
||||||
const alertsResponse = useQuery(
|
const alertsResponse = useQuery(
|
||||||
@ -29,6 +34,15 @@ function TriggeredAlerts(): JSX.Element {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!hasLoggedEvent.current && !isUndefined(alertsResponse.data?.payload)) {
|
||||||
|
logEvent('Alert: Triggered alert list page visited', {
|
||||||
|
number: alertsResponse.data?.payload?.length,
|
||||||
|
});
|
||||||
|
hasLoggedEvent.current = true;
|
||||||
|
}
|
||||||
|
}, [alertsResponse.data?.payload]);
|
||||||
|
|
||||||
if (alertsResponse.error) {
|
if (alertsResponse.error) {
|
||||||
return <TriggerComponent allAlerts={[]} />;
|
return <TriggerComponent allAlerts={[]} />;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user