diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6123bd747b..cc1c4399d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -347,7 +347,7 @@ curl -sL https://github.com/SigNoz/signoz/raw/develop/sample-apps/hotrod/hotrod- ```bash kubectl -n sample-application run strzal --image=djbingham/curl \ --restart='OnFailure' -i --tty --rm --command -- curl -X POST -F \ - 'locust_count=6' -F 'hatch_rate=2' http://locust-master:8089/swarm + 'user_count=6' -F 'spawn_rate=2' http://locust-master:8089/swarm ``` **5.1.3 To stop the load generation:** diff --git a/Makefile b/Makefile index 95cf7afdb9..5f4a3c1ac2 100644 --- a/Makefile +++ b/Makefile @@ -188,3 +188,4 @@ test: go test ./pkg/query-service/tests/integration/... go test ./pkg/query-service/rules/... go test ./pkg/query-service/collectorsimulator/... + go test ./pkg/query-service/postprocess/... diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index 03f3cb92f1..99060b38d2 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -146,7 +146,7 @@ services: condition: on-failure query-service: - image: signoz/query-service:0.48.1 + image: signoz/query-service:0.49.0 command: [ "-config=/root/config/prometheus.yml", @@ -199,7 +199,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector: - image: signoz/signoz-otel-collector:0.102.0 + image: signoz/signoz-otel-collector:0.102.1 command: [ "--config=/etc/otel-collector-config.yaml", @@ -237,7 +237,7 @@ services: - query-service otel-collector-migrator: - image: signoz/signoz-schema-migrator:0.102.0 + image: signoz/signoz-schema-migrator:0.102.1 deploy: restart_policy: condition: on-failure diff --git a/deploy/docker/clickhouse-setup/docker-compose-core.yaml b/deploy/docker/clickhouse-setup/docker-compose-core.yaml index bbafa71a1f..6803c29c69 100644 --- a/deploy/docker/clickhouse-setup/docker-compose-core.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose-core.yaml @@ -66,7 +66,7 @@ services: - --storage.path=/data otel-collector-migrator: - image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.0} + image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.1} container_name: otel-migrator command: - "--dsn=tcp://clickhouse:9000" @@ -81,7 +81,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` otel-collector: container_name: signoz-otel-collector - image: signoz/signoz-otel-collector:0.102.0 + image: signoz/signoz-otel-collector:0.102.1 command: [ "--config=/etc/otel-collector-config.yaml", diff --git a/deploy/docker/clickhouse-setup/docker-compose.testing.yaml b/deploy/docker/clickhouse-setup/docker-compose.testing.yaml index c5c13c5de8..23d8195850 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.testing.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.testing.yaml @@ -164,7 +164,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` query-service: - image: signoz/query-service:${DOCKER_TAG:-0.48.1} + image: signoz/query-service:${DOCKER_TAG:-0.49.0} container_name: signoz-query-service command: [ @@ -204,7 +204,7 @@ services: <<: *db-depend frontend: - image: signoz/frontend:${DOCKER_TAG:-0.48.1} + image: signoz/frontend:${DOCKER_TAG:-0.49.0} container_name: signoz-frontend restart: on-failure depends_on: @@ -216,7 +216,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector-migrator: - image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.0} + image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.1} container_name: otel-migrator command: - "--dsn=tcp://clickhouse:9000" @@ -230,7 +230,7 @@ services: otel-collector: - image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.0} + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.1} container_name: signoz-otel-collector command: [ diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index fb7757ba0d..8621bbfe44 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -164,7 +164,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` query-service: - image: signoz/query-service:${DOCKER_TAG:-0.48.1} + image: signoz/query-service:${DOCKER_TAG:-0.49.0} container_name: signoz-query-service command: [ @@ -203,7 +203,7 @@ services: <<: *db-depend frontend: - image: signoz/frontend:${DOCKER_TAG:-0.48.1} + image: signoz/frontend:${DOCKER_TAG:-0.49.0} container_name: signoz-frontend restart: on-failure depends_on: @@ -215,7 +215,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector-migrator: - image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.0} + image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.1} container_name: otel-migrator command: - "--dsn=tcp://clickhouse:9000" @@ -229,7 +229,7 @@ services: otel-collector: - image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.0} + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.1} container_name: signoz-otel-collector command: [ diff --git a/frontend/package.json b/frontend/package.json index d78064278a..2b2e803478 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -88,7 +88,7 @@ "lucide-react": "0.379.0", "mini-css-extract-plugin": "2.4.5", "papaparse": "5.4.1", - "posthog-js": "1.140.1", + "posthog-js": "1.142.1", "rc-tween-one": "3.0.6", "react": "18.2.0", "react-addons-update": "15.6.3", diff --git a/frontend/src/components/DropDown/DropDown.tsx b/frontend/src/components/DropDown/DropDown.tsx index df845b7084..e847e895be 100644 --- a/frontend/src/components/DropDown/DropDown.tsx +++ b/frontend/src/components/DropDown/DropDown.tsx @@ -5,7 +5,13 @@ import { Button, Dropdown, MenuProps } from 'antd'; import { useIsDarkMode } from 'hooks/useDarkMode'; 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 items: MenuProps['items'] = element.map( @@ -23,6 +29,7 @@ function DropDown({ element }: { element: JSX.Element[] }): JSX.Element { items, onMouseEnter: (): void => setDdOpen(true), onMouseLeave: (): void => setDdOpen(false), + onClick: (item): void => onDropDownItemClick?.(item), }} open={isDdOpen} > @@ -40,4 +47,8 @@ function DropDown({ element }: { element: JSX.Element[] }): JSX.Element { ); } +DropDown.defaultProps = { + onDropDownItemClick: (): void => {}, +}; + export default DropDown; diff --git a/frontend/src/components/Logs/RawLogView/index.tsx b/frontend/src/components/Logs/RawLogView/index.tsx index fcb8beeeec..d1ae19fe99 100644 --- a/frontend/src/components/Logs/RawLogView/index.tsx +++ b/frontend/src/components/Logs/RawLogView/index.tsx @@ -62,8 +62,6 @@ function RawLogView({ const isDarkMode = useIsDarkMode(); const isReadOnlyLog = !isLogsExplorerPage || isReadOnly; - const severityText = data.severity_text ? `${data.severity_text} |` : ''; - const logType = getLogIndicatorType(data); const updatedSelecedFields = useMemo( @@ -88,17 +86,16 @@ function RawLogView({ attributesText += ' | '; } - const text = useMemo( - () => + const text = useMemo(() => { + const date = typeof data.timestamp === 'string' - ? `${dayjs(data.timestamp).format( - 'YYYY-MM-DD HH:mm:ss.SSS', - )} | ${attributesText} ${severityText} ${data.body}` - : `${dayjs(data.timestamp / 1e6).format( - 'YYYY-MM-DD HH:mm:ss.SSS', - )} | ${attributesText} ${severityText} ${data.body}`, - [data.timestamp, data.body, severityText, attributesText], - ); + ? dayjs(data.timestamp) + : dayjs(data.timestamp / 1e6); + + return `${date.format('YYYY-MM-DD HH:mm:ss.SSS')} | ${attributesText} ${ + data.body + }`; + }, [data.timestamp, data.body, attributesText]); const handleClickExpand = useCallback(() => { if (activeContextLog || isReadOnly) return; diff --git a/frontend/src/components/ResizeTable/DynamicColumnTable.tsx b/frontend/src/components/ResizeTable/DynamicColumnTable.tsx index fb5d734ee8..5a3dfd39dd 100644 --- a/frontend/src/components/ResizeTable/DynamicColumnTable.tsx +++ b/frontend/src/components/ResizeTable/DynamicColumnTable.tsx @@ -2,7 +2,9 @@ import './DynamicColumnTable.syles.scss'; import { Button, Dropdown, Flex, MenuProps, Switch } from 'antd'; +import { ColumnGroupType, ColumnType } from 'antd/es/table'; import { ColumnsType } from 'antd/lib/table'; +import logEvent from 'api/common/logEvent'; import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn'; import { SlidersHorizontal } from 'lucide-react'; import { memo, useEffect, useState } from 'react'; @@ -22,6 +24,7 @@ function DynamicColumnTable({ dynamicColumns, onDragColumn, facingIssueBtn, + shouldSendAlertsLogEvent, ...restProps }: DynamicColumnTableProps): JSX.Element { const [columnsData, setColumnsData] = useState( @@ -47,11 +50,18 @@ function DynamicColumnTable({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [columns, dynamicColumns]); - const onToggleHandler = (index: number) => ( - checked: boolean, - event: React.MouseEvent, - ): void => { + const onToggleHandler = ( + index: number, + column: ColumnGroupType | ColumnType, + ) => (checked: boolean, event: React.MouseEvent): void => { event.stopPropagation(); + + if (shouldSendAlertsLogEvent) { + logEvent('Alert: Column toggled', { + column: column?.title, + action: checked ? 'Enable' : 'Disable', + }); + } setVisibleColumns({ tablesource, dynamicColumns, @@ -75,7 +85,7 @@ function DynamicColumnTable({
{column.title?.toString()}
c.key === column.key) !== -1} - onChange={onToggleHandler(index)} + onChange={onToggleHandler(index, column)} /> ), diff --git a/frontend/src/components/ResizeTable/ResizeTable.tsx b/frontend/src/components/ResizeTable/ResizeTable.tsx index 90cc588c47..5f8ac7a4a5 100644 --- a/frontend/src/components/ResizeTable/ResizeTable.tsx +++ b/frontend/src/components/ResizeTable/ResizeTable.tsx @@ -3,6 +3,7 @@ import { Table } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import { dragColumnParams } from 'hooks/useDragColumns/configs'; +import { set } from 'lodash-es'; import { SyntheticEvent, useCallback, @@ -20,6 +21,7 @@ import { ResizeTableProps } from './types'; function ResizeTable({ columns, onDragColumn, + pagination, ...restProps }: ResizeTableProps): JSX.Element { const [columnsData, setColumns] = useState([]); @@ -58,14 +60,21 @@ function ResizeTable({ [columnsData, onDragColumn, handleResize], ); - const tableParams = useMemo( - () => ({ + const tableParams = useMemo(() => { + const props = { ...restProps, components: { header: { cell: ResizableHeader } }, columns: mergedColumns, - }), - [mergedColumns, restProps], - ); + }; + + set( + props, + 'pagination', + pagination ? { ...pagination, hideOnSinglePage: true } : false, + ); + + return props; + }, [mergedColumns, pagination, restProps]); useEffect(() => { if (columns) { diff --git a/frontend/src/components/ResizeTable/types.ts b/frontend/src/components/ResizeTable/types.ts index 35a13127a8..693e5ffda1 100644 --- a/frontend/src/components/ResizeTable/types.ts +++ b/frontend/src/components/ResizeTable/types.ts @@ -14,6 +14,7 @@ export interface DynamicColumnTableProps extends TableProps { dynamicColumns: TableProps['columns']; onDragColumn?: (fromIndex: number, toIndex: number) => void; facingIssueBtn?: FacingIssueBtnProps; + shouldSendAlertsLogEvent?: boolean; } export type GetVisibleColumnsFunction = ( diff --git a/frontend/src/container/AllAlertChannels/index.tsx b/frontend/src/container/AllAlertChannels/index.tsx index 5f34264a60..85b42de094 100644 --- a/frontend/src/container/AllAlertChannels/index.tsx +++ b/frontend/src/container/AllAlertChannels/index.tsx @@ -1,13 +1,15 @@ import { PlusOutlined } from '@ant-design/icons'; import { Tooltip, Typography } from 'antd'; import getAll from 'api/channels/getAll'; +import logEvent from 'api/common/logEvent'; import Spinner from 'components/Spinner'; import TextToolTip from 'components/TextToolTip'; import ROUTES from 'constants/routes'; import useComponentPermission from 'hooks/useComponentPermission'; import useFetch from 'hooks/useFetch'; 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 { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; @@ -31,6 +33,14 @@ function AlertChannels(): JSX.Element { const { loading, payload, error, errorMessage } = useFetch(getAll); + useEffect(() => { + if (!isUndefined(payload)) { + logEvent('Alert Channel: Channel list page visited', { + number: payload?.length, + }); + } + }, [payload]); + if (error) { return {errorMessage}; } diff --git a/frontend/src/container/CreateAlertChannels/index.tsx b/frontend/src/container/CreateAlertChannels/index.tsx index d10b6fb225..85d609c24c 100644 --- a/frontend/src/container/CreateAlertChannels/index.tsx +++ b/frontend/src/container/CreateAlertChannels/index.tsx @@ -11,11 +11,12 @@ import testOpsGenie from 'api/channels/testOpsgenie'; import testPagerApi from 'api/channels/testPager'; import testSlackApi from 'api/channels/testSlack'; import testWebhookApi from 'api/channels/testWebhook'; +import logEvent from 'api/common/logEvent'; import ROUTES from 'constants/routes'; import FormAlertChannels from 'container/FormAlertChannels'; import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; -import { useCallback, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -43,6 +44,10 @@ function CreateAlertChannels({ const [formInstance] = Form.useForm(); + useEffect(() => { + logEvent('Alert Channel: Create channel page visited', {}); + }, []); + const [selectedConfig, setSelectedConfig] = useState< Partial< SlackChannel & @@ -139,19 +144,25 @@ function CreateAlertChannels({ description: t('channel_creation_done'), }); history.replace(ROUTES.ALL_CHANNELS); - } else { - notifications.error({ - message: 'Error', - description: response.error || t('channel_creation_failed'), - }); + return { status: 'success', statusMessage: t('channel_creation_done') }; } + notifications.error({ + message: 'Error', + description: response.error || t('channel_creation_failed'), + }); + return { + status: 'failed', + statusMessage: response.error || 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); }, [prepareSlackRequest, t, notifications]); const prepareWebhookRequest = useCallback(() => { @@ -200,19 +211,25 @@ function CreateAlertChannels({ description: t('channel_creation_done'), }); history.replace(ROUTES.ALL_CHANNELS); - } else { - notifications.error({ - message: 'Error', - description: response.error || t('channel_creation_failed'), - }); + return { status: 'success', statusMessage: t('channel_creation_done') }; } + notifications.error({ + message: 'Error', + description: response.error || t('channel_creation_failed'), + }); + return { + status: 'failed', + statusMessage: response.error || 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); }, [prepareWebhookRequest, t, notifications]); const preparePagerRequest = useCallback(() => { @@ -245,8 +262,8 @@ function CreateAlertChannels({ setSavingState(true); const request = preparePagerRequest(); - if (request) { - try { + try { + if (request) { const response = await createPagerApi(request); if (response.statusCode === 200) { @@ -255,20 +272,31 @@ function CreateAlertChannels({ description: t('channel_creation_done'), }); history.replace(ROUTES.ALL_CHANNELS); - } else { - notifications.error({ - message: 'Error', - description: response.error || t('channel_creation_failed'), - }); + return { status: 'success', statusMessage: t('channel_creation_done') }; } - } catch (e) { notifications.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]); const prepareOpsgenieRequest = useCallback( @@ -295,19 +323,25 @@ function CreateAlertChannels({ description: t('channel_creation_done'), }); history.replace(ROUTES.ALL_CHANNELS); - } else { - notifications.error({ - message: 'Error', - description: response.error || t('channel_creation_failed'), - }); + return { status: 'success', statusMessage: t('channel_creation_done') }; } + notifications.error({ + message: 'Error', + description: response.error || t('channel_creation_failed'), + }); + return { + status: 'failed', + statusMessage: response.error || 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); }, [prepareOpsgenieRequest, t, notifications]); const prepareEmailRequest = useCallback( @@ -332,19 +366,25 @@ function CreateAlertChannels({ description: t('channel_creation_done'), }); history.replace(ROUTES.ALL_CHANNELS); - } else { - notifications.error({ - message: 'Error', - description: response.error || t('channel_creation_failed'), - }); + return { status: 'success', statusMessage: t('channel_creation_done') }; } + notifications.error({ + message: 'Error', + description: response.error || t('channel_creation_failed'), + }); + return { + status: 'failed', + statusMessage: response.error || 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); }, [prepareEmailRequest, t, notifications]); const prepareMsTeamsRequest = useCallback( @@ -370,19 +410,25 @@ function CreateAlertChannels({ description: t('channel_creation_done'), }); history.replace(ROUTES.ALL_CHANNELS); - } else { - notifications.error({ - message: 'Error', - description: response.error || t('channel_creation_failed'), - }); + return { status: 'success', statusMessage: t('channel_creation_done') }; } + notifications.error({ + message: 'Error', + description: response.error || t('channel_creation_failed'), + }); + return { + status: 'failed', + statusMessage: response.error || 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); }, [prepareMsTeamsRequest, t, notifications]); const onSaveHandler = useCallback( @@ -400,7 +446,15 @@ function CreateAlertChannels({ const functionToCall = functionMapper[value as keyof typeof functionMapper]; 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 { notifications.error({ message: 'Error', @@ -409,6 +463,7 @@ function CreateAlertChannels({ } } }, + // eslint-disable-next-line react-hooks/exhaustive-deps [ onSlackHandler, onWebhookHandler, @@ -472,14 +527,25 @@ function CreateAlertChannels({ 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) { notifications.error({ message: 'Error', description: t('channel_test_unexpected'), }); } + setTestingState(false); }, + // eslint-disable-next-line react-hooks/exhaustive-deps [ prepareWebhookRequest, t, diff --git a/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx b/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx index cd837b666b..52f4d52215 100644 --- a/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx +++ b/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx @@ -1,4 +1,6 @@ import { Row, Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; +import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { AlertTypes } from 'types/api/alerts/alertTypes'; @@ -34,6 +36,13 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element { default: 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'); } const renderOptions = useMemo( diff --git a/frontend/src/container/CreateAlertRule/index.tsx b/frontend/src/container/CreateAlertRule/index.tsx index e5a4772f30..f7e491cd70 100644 --- a/frontend/src/container/CreateAlertRule/index.tsx +++ b/frontend/src/container/CreateAlertRule/index.tsx @@ -1,4 +1,5 @@ import { Form, Row } from 'antd'; +import logEvent from 'api/common/logEvent'; import { ENTITY_VERSION_V4 } from 'constants/app'; import { QueryParams } from 'constants/query'; import FormAlertRules from 'container/FormAlertRules'; @@ -68,6 +69,8 @@ function CreateRules(): JSX.Element { useEffect(() => { if (alertType) { onSelectType(alertType); + } else { + logEvent('Alert: New alert data source selection page visited', {}); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [alertType]); diff --git a/frontend/src/container/EditAlertChannels/index.tsx b/frontend/src/container/EditAlertChannels/index.tsx index 3c2e956f14..b4fe30d557 100644 --- a/frontend/src/container/EditAlertChannels/index.tsx +++ b/frontend/src/container/EditAlertChannels/index.tsx @@ -11,6 +11,7 @@ import testOpsgenie from 'api/channels/testOpsgenie'; import testPagerApi from 'api/channels/testPager'; import testSlackApi from 'api/channels/testSlack'; import testWebhookApi from 'api/channels/testWebhook'; +import logEvent from 'api/common/logEvent'; import ROUTES from 'constants/routes'; import { ChannelType, @@ -89,7 +90,7 @@ function EditAlertChannels({ description: t('webhook_url_required'), }); setSavingState(false); - return; + return { status: 'failed', statusMessage: t('webhook_url_required') }; } const response = await editSlackApi(prepareSlackRequest()); @@ -101,13 +102,17 @@ function EditAlertChannels({ }); history.replace(ROUTES.ALL_CHANNELS); - } else { - notifications.error({ - message: 'Error', - description: response.error || t('channel_edit_failed'), - }); + return { status: 'success', statusMessage: t('channel_edit_done') }; } + notifications.error({ + message: 'Error', + description: response.error || t('channel_edit_failed'), + }); setSavingState(false); + return { + status: 'failed', + statusMessage: response.error || t('channel_edit_failed'), + }; }, [prepareSlackRequest, t, notifications, selectedConfig]); const prepareWebhookRequest = useCallback(() => { @@ -136,13 +141,13 @@ function EditAlertChannels({ if (selectedConfig?.api_url === '') { showError(t('webhook_url_required')); setSavingState(false); - return; + return { status: 'failed', statusMessage: t('webhook_url_required') }; } if (username && (!password || password === '')) { showError(t('username_no_password')); setSavingState(false); - return; + return { status: 'failed', statusMessage: t('username_no_password') }; } const response = await editWebhookApi(prepareWebhookRequest()); @@ -154,10 +159,15 @@ function EditAlertChannels({ }); history.replace(ROUTES.ALL_CHANNELS); - } else { - showError(response.error || t('channel_edit_failed')); + return { status: 'success', statusMessage: t('channel_edit_done') }; } + showError(response.error || t('channel_edit_failed')); + setSavingState(false); + return { + status: 'failed', + statusMessage: response.error || t('channel_edit_failed'), + }; }, [prepareWebhookRequest, t, notifications, selectedConfig]); const prepareEmailRequest = useCallback( @@ -181,13 +191,18 @@ function EditAlertChannels({ description: t('channel_edit_done'), }); history.replace(ROUTES.ALL_CHANNELS); - } else { - notifications.error({ - message: 'Error', - description: response.error || t('channel_edit_failed'), - }); + return { status: 'success', statusMessage: t('channel_edit_done') }; } + notifications.error({ + message: 'Error', + description: response.error || t('channel_edit_failed'), + }); + setSavingState(false); + return { + status: 'failed', + statusMessage: response.error || t('channel_edit_failed'), + }; }, [prepareEmailRequest, t, notifications]); const preparePagerRequest = useCallback( @@ -218,7 +233,7 @@ function EditAlertChannels({ description: validationError, }); setSavingState(false); - return; + return { status: 'failed', statusMessage: validationError }; } const response = await editPagerApi(preparePagerRequest()); @@ -229,13 +244,18 @@ function EditAlertChannels({ }); history.replace(ROUTES.ALL_CHANNELS); - } else { - notifications.error({ - message: 'Error', - description: response.error || t('channel_edit_failed'), - }); + return { status: 'success', statusMessage: t('channel_edit_done') }; } + notifications.error({ + message: 'Error', + description: response.error || t('channel_edit_failed'), + }); + setSavingState(false); + return { + status: 'failed', + statusMessage: response.error || t('channel_edit_failed'), + }; }, [preparePagerRequest, notifications, selectedConfig, t]); const prepareOpsgenieRequest = useCallback( @@ -259,7 +279,7 @@ function EditAlertChannels({ description: t('api_key_required'), }); setSavingState(false); - return; + return { status: 'failed', statusMessage: t('api_key_required') }; } const response = await editOpsgenie(prepareOpsgenieRequest()); @@ -271,13 +291,18 @@ function EditAlertChannels({ }); history.replace(ROUTES.ALL_CHANNELS); - } else { - notifications.error({ - message: 'Error', - description: response.error || t('channel_edit_failed'), - }); + return { status: 'success', statusMessage: t('channel_edit_done') }; } + notifications.error({ + message: 'Error', + description: response.error || t('channel_edit_failed'), + }); + setSavingState(false); + return { + status: 'failed', + statusMessage: response.error || t('channel_edit_failed'), + }; }, [prepareOpsgenieRequest, t, notifications, selectedConfig]); const prepareMsTeamsRequest = useCallback( @@ -301,7 +326,7 @@ function EditAlertChannels({ description: t('webhook_url_required'), }); setSavingState(false); - return; + return { status: 'failed', statusMessage: t('webhook_url_required') }; } const response = await editMsTeamsApi(prepareMsTeamsRequest()); @@ -313,31 +338,46 @@ function EditAlertChannels({ }); history.replace(ROUTES.ALL_CHANNELS); - } else { - notifications.error({ - message: 'Error', - description: response.error || t('channel_edit_failed'), - }); + return { status: 'success', statusMessage: t('channel_edit_done') }; } + notifications.error({ + message: 'Error', + description: response.error || t('channel_edit_failed'), + }); + setSavingState(false); + return { + status: 'failed', + statusMessage: response.error || t('channel_edit_failed'), + }; }, [prepareMsTeamsRequest, t, notifications, selectedConfig]); const onSaveHandler = useCallback( - (value: ChannelType) => { + async (value: ChannelType) => { + let result; if (value === ChannelType.Slack) { - onSlackEditHandler(); + result = await onSlackEditHandler(); } else if (value === ChannelType.Webhook) { - onWebhookEditHandler(); + result = await onWebhookEditHandler(); } else if (value === ChannelType.Pagerduty) { - onPagerEditHandler(); + result = await onPagerEditHandler(); } else if (value === ChannelType.MsTeams) { - onMsTeamsEditHandler(); + result = await onMsTeamsEditHandler(); } else if (value === ChannelType.Opsgenie) { - onOpsgenieEditHandler(); + result = await onOpsgenieEditHandler(); } 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, onWebhookEditHandler, @@ -399,6 +439,14 @@ function EditAlertChannels({ 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) { notifications.error({ message: 'Error', @@ -407,6 +455,7 @@ function EditAlertChannels({ } setTestingState(false); }, + // eslint-disable-next-line react-hooks/exhaustive-deps [ t, prepareWebhookRequest, diff --git a/frontend/src/container/FormAlertRules/BasicInfo.tsx b/frontend/src/container/FormAlertRules/BasicInfo.tsx index 5fae4a713d..40edb7977e 100644 --- a/frontend/src/container/FormAlertRules/BasicInfo.tsx +++ b/frontend/src/container/FormAlertRules/BasicInfo.tsx @@ -3,6 +3,8 @@ import './FormAlertRules.styles.scss'; import { PlusOutlined } from '@ant-design/icons'; import { Button, Form, Select, Switch, Tooltip } from 'antd'; 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 useComponentPermission from 'hooks/useComponentPermission'; import useFetch from 'hooks/useFetch'; @@ -10,6 +12,7 @@ import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; +import { AlertTypes } from 'types/api/alerts/alertTypes'; import { AlertDef, Labels } from 'types/api/alerts/def'; import AppReducer from 'types/reducer/app'; import { requireErrorMessage } from 'utils/form/requireErrorMessage'; @@ -73,9 +76,24 @@ function BasicInfo({ const noChannels = channels.payload?.length === 0; 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'); + // 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 ( <> {t('alert_form_step3')} diff --git a/frontend/src/container/FormAlertRules/QuerySection.tsx b/frontend/src/container/FormAlertRules/QuerySection.tsx index a567288585..aa56c84571 100644 --- a/frontend/src/container/FormAlertRules/QuerySection.tsx +++ b/frontend/src/container/FormAlertRules/QuerySection.tsx @@ -2,6 +2,7 @@ import './QuerySection.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Tabs, Tooltip } from 'antd'; +import logEvent from 'api/common/logEvent'; import PromQLIcon from 'assets/Dashboard/PromQl'; import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts'; import { ENTITY_VERSION_V4 } from 'constants/app'; @@ -31,6 +32,7 @@ function QuerySection({ runQuery, alertDef, panelType, + ruleId, }: QuerySectionProps): JSX.Element { // init namespace for translations const { t } = useTranslation('alerts'); @@ -158,7 +160,15 @@ function QuerySection({