From df751c7f380ccbf7ee4f93e92bebbdf258eb4a49 Mon Sep 17 00:00:00 2001 From: Vishal Sharma Date: Tue, 16 Jul 2024 14:18:59 +0530 Subject: [PATCH] chore: add activation events (#5474) --- frontend/src/container/AllError/index.tsx | 24 ++++++- .../EmptyLogsSearch/EmptyLogsSearch.tsx | 28 +++++++- frontend/src/container/ErrorDetails/index.tsx | 24 ++++++- .../ExplorerOptions/ExplorerOptions.tsx | 67 ++++++++++++++++--- .../ExportPanel/ExportPanelContainer.tsx | 4 +- frontend/src/container/ExportPanel/index.tsx | 2 +- .../DashboardEmptyState.tsx | 7 ++ .../GridCardLayout/GridCardLayout.tsx | 17 ++++- .../GridCardLayout/WidgetHeader/index.tsx | 2 +- .../ListOfDashboard/DashboardsList.tsx | 32 ++++++++- .../ListOfDashboard/ImportJSON/index.tsx | 10 +++ .../src/container/LogsExplorerList/index.tsx | 4 +- .../src/container/LogsExplorerViews/index.tsx | 31 ++++++++- .../MetricsApplication/Tabs/DBCall.tsx | 21 +++++- .../MetricsApplication/Tabs/External.tsx | 20 +++++- .../MetricsApplication/Tabs/Overview.tsx | 20 +++++- .../NewDashboard/ComponentsSlider/index.tsx | 9 ++- .../DashboardDescription/index.tsx | 7 ++ .../LeftContainer/QuerySection/index.tsx | 15 ++++- .../NewWidget/RightContainer/index.tsx | 2 +- frontend/src/container/NewWidget/index.tsx | 30 ++++++++- frontend/src/container/NoLogs/NoLogs.tsx | 6 ++ .../PipelineListsView/PipelineListsView.tsx | 20 +++++- .../ServiceTraces/index.tsx | 24 ++++++- frontend/src/container/SideNav/SideNav.tsx | 26 +++++++ .../TimeSeriesView/TimeSeriesView.tsx | 4 +- .../TracesExplorer/ListView/index.tsx | 4 +- .../TracesExplorer/TracesView/index.tsx | 4 +- .../hooks/queryBuilder/useCreateAlerts.tsx | 23 ++++++- frontend/src/pages/SaveView/index.tsx | 19 +++++- .../pages/TracesExplorer/Filter/Filter.tsx | 6 ++ frontend/src/pages/TracesExplorer/index.tsx | 18 ++++- 32 files changed, 493 insertions(+), 37 deletions(-) diff --git a/frontend/src/container/AllError/index.tsx b/frontend/src/container/AllError/index.tsx index e8c13d88cd..d571c3dba7 100644 --- a/frontend/src/container/AllError/index.tsx +++ b/frontend/src/container/AllError/index.tsx @@ -12,6 +12,7 @@ import { ColumnType, TablePaginationConfig } from 'antd/es/table'; import { FilterValue, SorterResult } from 'antd/es/table/interface'; import { ColumnsType } from 'antd/lib/table'; import { FilterConfirmProps } from 'antd/lib/table/interface'; +import logEvent from 'api/common/logEvent'; import getAll from 'api/errors/getAll'; import getErrorCounts from 'api/errors/getErrorCounts'; import { ResizeTable } from 'components/ResizeTable'; @@ -23,7 +24,8 @@ import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute import useUrlQuery from 'hooks/useUrlQuery'; import createQueryParams from 'lib/createQueryParams'; import history from 'lib/history'; -import { useCallback, useEffect, useMemo } from 'react'; +import { isUndefined } from 'lodash-es'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useQueries } from 'react-query'; import { useSelector } from 'react-redux'; @@ -410,6 +412,26 @@ function AllErrors(): JSX.Element { [pathname], ); + const logEventCalledRef = useRef(false); + useEffect(() => { + if ( + !logEventCalledRef.current && + !isUndefined(errorCountResponse.data?.payload) + ) { + const selectedEnvironments = queries.find( + (val) => val.tagKey === 'resource_deployment_environment', + )?.tagValue; + + logEvent('Exception: List page visited', { + numberOfExceptions: errorCountResponse.data?.payload, + selectedEnvironments, + resourceAttributeUsed: !!queries.length, + }); + logEventCalledRef.current = true; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [errorCountResponse.data?.payload]); + return ( { + if (!logEventCalledRef.current) { + if (dataSource === DataSource.TRACES) { + logEvent('Traces Explorer: No results', { + panelType, + }); + } else if (dataSource === DataSource.LOGS) { + logEvent('Logs Explorer: No results', { + panelType, + }); + } + logEventCalledRef.current = true; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); -export default function EmptyLogsSearch(): JSX.Element { return (
diff --git a/frontend/src/container/ErrorDetails/index.tsx b/frontend/src/container/ErrorDetails/index.tsx index 33a86f9f50..2c87279e3d 100644 --- a/frontend/src/container/ErrorDetails/index.tsx +++ b/frontend/src/container/ErrorDetails/index.tsx @@ -1,6 +1,7 @@ import './styles.scss'; import { Button, Divider, Space, Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; import getNextPrevId from 'api/errors/getNextPrevId'; import Editor from 'components/Editor'; import { ResizeTable } from 'components/ResizeTable'; @@ -9,8 +10,9 @@ import dayjs from 'dayjs'; import { useNotifications } from 'hooks/useNotifications'; import createQueryParams from 'lib/createQueryParams'; import history from 'lib/history'; +import { isUndefined } from 'lodash-es'; import { urlKey } from 'pages/ErrorDetails/utils'; -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useQuery } from 'react-query'; import { useLocation } from 'react-router-dom'; @@ -111,9 +113,29 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element { })); const onClickTraceHandler = (): void => { + logEvent('Exception: Navigate to trace detail page', { + groupId: errorDetail.groupID, + spanId: errorDetail.spanID, + traceId: errorDetail.traceID, + exceptionId: errorDetail.errorId, + }); history.push(`/trace/${errorDetail.traceID}?spanId=${errorDetail.spanID}`); }; + const logEventCalledRef = useRef(false); + useEffect(() => { + if (!logEventCalledRef.current && !isUndefined(data)) { + logEvent('Exception: Detail page visited', { + groupId: errorDetail.groupID, + spanId: errorDetail.spanID, + traceId: errorDetail.traceID, + exceptionId: errorDetail.errorId, + }); + logEventCalledRef.current = true; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data]); + return ( <> {errorDetail.exceptionType} diff --git a/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx b/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx index 1aaf22f796..e925a60a8a 100644 --- a/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx +++ b/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx @@ -14,6 +14,7 @@ import { Tooltip, Typography, } from 'antd'; +import logEvent from 'api/common/logEvent'; import axios from 'axios'; import cx from 'classnames'; import { getViewDetailsUsingViewKey } from 'components/ExplorerCard/utils'; @@ -93,7 +94,23 @@ function ExplorerOptions({ setIsExport(value); }, []); + const { + currentQuery, + panelType, + isStagedQueryUpdated, + redirectWithQueryBuilderData, + } = useQueryBuilder(); + const handleSaveViewModalToggle = (): void => { + if (sourcepage === DataSource.TRACES) { + logEvent('Traces Explorer: Save view clicked', { + panelType, + }); + } else if (sourcepage === DataSource.LOGS) { + logEvent('Logs Explorer: Save view clicked', { + panelType, + }); + } setIsSaveModalOpen(!isSaveModalOpen); }; @@ -104,11 +121,21 @@ function ExplorerOptions({ const { role } = useSelector((state) => state.app); const onCreateAlertsHandler = useCallback(() => { + if (sourcepage === DataSource.TRACES) { + logEvent('Traces Explorer: Create alert', { + panelType, + }); + } else if (sourcepage === DataSource.LOGS) { + logEvent('Logs Explorer: Create alert', { + panelType, + }); + } history.push( `${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent( JSON.stringify(query), )}`, ); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [history, query]); const onCancel = (value: boolean) => (): void => { @@ -116,6 +143,15 @@ function ExplorerOptions({ }; const onAddToDashboard = (): void => { + if (sourcepage === DataSource.TRACES) { + logEvent('Traces Explorer: Add to dashboard clicked', { + panelType, + }); + } else if (sourcepage === DataSource.LOGS) { + logEvent('Logs Explorer: Add to dashboard clicked', { + panelType, + }); + } setIsExport(true); }; @@ -127,13 +163,6 @@ function ExplorerOptions({ refetch: refetchAllView, } = useGetAllViews(sourcepage); - const { - currentQuery, - panelType, - isStagedQueryUpdated, - redirectWithQueryBuilderData, - } = useQueryBuilder(); - const compositeQuery = mapCompositeQueryFromQuery(currentQuery, panelType); const viewName = useGetSearchQueryParam(QueryParams.viewName) || ''; @@ -224,6 +253,17 @@ function ExplorerOptions({ onMenuItemSelectHandler({ key: option.key, }); + if (sourcepage === DataSource.TRACES) { + logEvent('Traces Explorer: Select view', { + panelType, + viewName: option.value, + }); + } else if (sourcepage === DataSource.LOGS) { + logEvent('Logs Explorer: Select view', { + panelType, + viewName: option.value, + }); + } if (ref.current) { ref.current.blur(); } @@ -259,6 +299,17 @@ function ExplorerOptions({ viewName: newViewName, setNewViewName, }); + if (sourcepage === DataSource.TRACES) { + logEvent('Traces Explorer: Save view successful', { + panelType, + viewName: newViewName, + }); + } else if (sourcepage === DataSource.LOGS) { + logEvent('Logs Explorer: Save view successful', { + panelType, + viewName: newViewName, + }); + } }; // TODO: Remove this and move this to scss file @@ -499,7 +550,7 @@ function ExplorerOptions({ export interface ExplorerOptionsProps { isLoading?: boolean; - onExport: (dashboard: Dashboard | null) => void; + onExport: (dashboard: Dashboard | null, isNewDashboard?: boolean) => void; query: Query | null; disabled: boolean; sourcepage: DataSource; diff --git a/frontend/src/container/ExportPanel/ExportPanelContainer.tsx b/frontend/src/container/ExportPanel/ExportPanelContainer.tsx index 9e4a658273..a0927e3692 100644 --- a/frontend/src/container/ExportPanel/ExportPanelContainer.tsx +++ b/frontend/src/container/ExportPanel/ExportPanelContainer.tsx @@ -41,7 +41,7 @@ function ExportPanelContainer({ } = useMutation(createDashboard, { onSuccess: (data) => { if (data.payload) { - onExport(data?.payload); + onExport(data?.payload, true); } refetch(); }, @@ -55,7 +55,7 @@ function ExportPanelContainer({ ({ uuid }) => uuid === selectedDashboardId, ); - onExport(currentSelectedDashboard || null); + onExport(currentSelectedDashboard || null, false); }, [data, selectedDashboardId, onExport]); const handleSelect = useCallback( diff --git a/frontend/src/container/ExportPanel/index.tsx b/frontend/src/container/ExportPanel/index.tsx index f302d83212..86d5f44139 100644 --- a/frontend/src/container/ExportPanel/index.tsx +++ b/frontend/src/container/ExportPanel/index.tsx @@ -40,7 +40,7 @@ function ExportPanel({ export interface ExportPanelProps { isLoading?: boolean; - onExport: (dashboard: Dashboard | null) => void; + onExport: (dashboard: Dashboard | null, isNewDashboard?: boolean) => void; query: Query | null; } diff --git a/frontend/src/container/GridCardLayout/DashboardEmptyState/DashboardEmptyState.tsx b/frontend/src/container/GridCardLayout/DashboardEmptyState/DashboardEmptyState.tsx index 0bfbd5ac28..99e3194d5a 100644 --- a/frontend/src/container/GridCardLayout/DashboardEmptyState/DashboardEmptyState.tsx +++ b/frontend/src/container/GridCardLayout/DashboardEmptyState/DashboardEmptyState.tsx @@ -3,6 +3,7 @@ import './DashboardEmptyState.styles.scss'; import { PlusOutlined } from '@ant-design/icons'; import { Button, Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; import SettingsDrawer from 'container/NewDashboard/DashboardDescription/SettingsDrawer'; import useComponentPermission from 'hooks/useComponentPermission'; import { useDashboard } from 'providers/Dashboard/Dashboard'; @@ -36,6 +37,12 @@ export default function DashboardEmptyState(): JSX.Element { const onEmptyWidgetHandler = useCallback(() => { handleToggleDashboardSlider(true); + logEvent('Dashboard Detail: Add new panel clicked', { + dashboardId: selectedDashboard?.uuid, + dashboardName: selectedDashboard?.data.title, + numberOfPanels: selectedDashboard?.data.widgets?.length, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [handleToggleDashboardSlider]); return (
diff --git a/frontend/src/container/GridCardLayout/GridCardLayout.tsx b/frontend/src/container/GridCardLayout/GridCardLayout.tsx index f0d178866c..75fde3ce7a 100644 --- a/frontend/src/container/GridCardLayout/GridCardLayout.tsx +++ b/frontend/src/container/GridCardLayout/GridCardLayout.tsx @@ -3,6 +3,7 @@ import './GridCardLayout.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Form, Input, Modal, Typography } from 'antd'; import { useForm } from 'antd/es/form/Form'; +import logEvent from 'api/common/logEvent'; import cx from 'classnames'; import { SOMETHING_WENT_WRONG } from 'constants/api'; import { QueryParams } from 'constants/query'; @@ -15,7 +16,7 @@ import { useIsDarkMode } from 'hooks/useDarkMode'; import { useNotifications } from 'hooks/useNotifications'; import useUrlQuery from 'hooks/useUrlQuery'; import history from 'lib/history'; -import { defaultTo } from 'lodash-es'; +import { defaultTo, isUndefined } from 'lodash-es'; import isEqual from 'lodash-es/isEqual'; import { Check, @@ -27,7 +28,7 @@ import { } from 'lucide-react'; import { useDashboard } from 'providers/Dashboard/Dashboard'; import { sortLayout } from 'providers/Dashboard/util'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { FullScreen, FullScreenHandle } from 'react-full-screen'; import { ItemCallback, Layout } from 'react-grid-layout'; import { useDispatch, useSelector } from 'react-redux'; @@ -126,6 +127,18 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element { setDashboardLayout(sortLayout(layouts)); }, [layouts]); + const logEventCalledRef = useRef(false); + useEffect(() => { + if (!logEventCalledRef.current && !isUndefined(data)) { + logEvent('Dashboard Detail: Opened', { + dashboardId: data.uuid, + dashboardName: data.title, + numberOfPanels: data.widgets?.length, + numberOfVariables: Object.keys(data?.variables || {}).length || 0, + }); + logEventCalledRef.current = true; + } + }, [data]); const onSaveHandler = (): void => { if (!selectedDashboard) return; diff --git a/frontend/src/container/GridCardLayout/WidgetHeader/index.tsx b/frontend/src/container/GridCardLayout/WidgetHeader/index.tsx index 1401576ca9..1954b1c458 100644 --- a/frontend/src/container/GridCardLayout/WidgetHeader/index.tsx +++ b/frontend/src/container/GridCardLayout/WidgetHeader/index.tsx @@ -79,7 +79,7 @@ function WidgetHeader({ ); }, [widget.id, widget.panelTypes, widget.query]); - const onCreateAlertsHandler = useCreateAlerts(widget); + const onCreateAlertsHandler = useCreateAlerts(widget, 'dashboardView'); const onDownloadHandler = useCallback((): void => { const csv = unparse(tableProcessedDataRef.current); diff --git a/frontend/src/container/ListOfDashboard/DashboardsList.tsx b/frontend/src/container/ListOfDashboard/DashboardsList.tsx index 39326a558a..f3dc600f9b 100644 --- a/frontend/src/container/ListOfDashboard/DashboardsList.tsx +++ b/frontend/src/container/ListOfDashboard/DashboardsList.tsx @@ -21,6 +21,7 @@ import { Typography, } from 'antd'; import { TableProps } from 'antd/lib'; +import logEvent from 'api/common/logEvent'; import createDashboard from 'api/dashboard/create'; import { AxiosError } from 'axios'; import cx from 'classnames'; @@ -34,7 +35,7 @@ import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard'; import useComponentPermission from 'hooks/useComponentPermission'; import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; -import { get, isEmpty } from 'lodash-es'; +import { get, isEmpty, isUndefined } from 'lodash-es'; import { ArrowDownWideNarrow, ArrowUpRight, @@ -60,6 +61,7 @@ import { useCallback, useEffect, useMemo, + useRef, useState, } from 'react'; import { useTranslation } from 'react-i18next'; @@ -269,6 +271,7 @@ function DashboardsList(): JSX.Element { const onNewDashboardHandler = useCallback(async () => { try { + logEvent('Dashboard List: Create dashboard clicked', {}); setNewDashboardState({ ...newDashboardState, loading: true, @@ -305,6 +308,8 @@ function DashboardsList(): JSX.Element { }, [newDashboardState, t]); const onModalHandler = (uploadedGrafana: boolean): void => { + logEvent('Dashboard List: Import JSON clicked', {}); + setIsImportJSONModalVisible((state) => !state); setUploadedGrafana(uploadedGrafana); }; @@ -441,6 +446,10 @@ function DashboardsList(): JSX.Element { } else { history.push(getLink()); } + logEvent('Dashboard List: Clicked on dashboard', { + dashboardId: dashboard.id, + dashboardName: dashboard.name, + }); }; return ( @@ -619,6 +628,21 @@ function DashboardsList(): JSX.Element { hideOnSinglePage: true, }; + const logEventCalledRef = useRef(false); + useEffect(() => { + if ( + !logEventCalledRef.current && + !isDashboardListLoading && + !isUndefined(dashboardListResponse) + ) { + logEvent('Dashboard List: Page visited', { + number: dashboardListResponse?.length, + }); + logEventCalledRef.current = true; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isDashboardListLoading]); + return (
@@ -705,6 +729,9 @@ function DashboardsList(): JSX.Element { type="text" className="new-dashboard" icon={} + onClick={(): void => { + logEvent('Dashboard List: New dashboard clicked', {}); + }} > New Dashboard @@ -745,6 +772,9 @@ function DashboardsList(): JSX.Element { type="primary" className="periscope-btn primary btn" icon={} + onClick={(): void => { + logEvent('Dashboard List: New dashboard clicked', {}); + }} > New dashboard diff --git a/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx b/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx index 5b95eb4a9a..9bf1db2051 100644 --- a/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx +++ b/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx @@ -5,6 +5,7 @@ import { ExclamationCircleTwoTone } from '@ant-design/icons'; import MEditor, { Monaco } from '@monaco-editor/react'; import { Color } from '@signozhq/design-tokens'; import { Button, Modal, Space, Typography, Upload, UploadProps } from 'antd'; +import logEvent from 'api/common/logEvent'; import createDashboard from 'api/dashboard/create'; import ROUTES from 'constants/routes'; import { useIsDarkMode } from 'hooks/useDarkMode'; @@ -67,6 +68,8 @@ function ImportJSON({ const onClickLoadJsonHandler = async (): Promise => { try { setDashboardCreating(true); + logEvent('Dashboard List: Import and next clicked', {}); + const dashboardData = JSON.parse(editorValue) as DashboardData; if (dashboardData?.layout) { @@ -86,6 +89,10 @@ function ImportJSON({ dashboardId: response.payload.uuid, }), ); + logEvent('Dashboard List: New dashboard imported successfully', { + dashboardId: response.payload?.uuid, + dashboardName: response.payload?.data?.title, + }); } else if (response.error === 'feature usage exceeded') { setIsFeatureAlert(true); notifications.error({ @@ -180,6 +187,9 @@ function ImportJSON({ type="default" className="periscope-btn" icon={} + onClick={(): void => { + logEvent('Dashboard List: Upload JSON file clicked', {}); + }} > {' '} {t('upload_json_file')} diff --git a/frontend/src/container/LogsExplorerList/index.tsx b/frontend/src/container/LogsExplorerList/index.tsx index 0727d7100f..760f3fea30 100644 --- a/frontend/src/container/LogsExplorerList/index.tsx +++ b/frontend/src/container/LogsExplorerList/index.tsx @@ -172,7 +172,9 @@ function LogsExplorerList({ !isFetching && logs.length === 0 && !isError && - isFilterApplied && } + isFilterApplied && ( + + )} {isError && !isLoading && !isFetching && } diff --git a/frontend/src/container/LogsExplorerViews/index.tsx b/frontend/src/container/LogsExplorerViews/index.tsx index b95fd6e6c4..6318e1da71 100644 --- a/frontend/src/container/LogsExplorerViews/index.tsx +++ b/frontend/src/container/LogsExplorerViews/index.tsx @@ -2,6 +2,7 @@ import './LogsExplorerViews.styles.scss'; import { Button } from 'antd'; +import logEvent from 'api/common/logEvent'; import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu'; import { DEFAULT_ENTITY_VERSION } from 'constants/app'; import { LOCALSTORAGE } from 'constants/localStorage'; @@ -37,7 +38,14 @@ import { useNotifications } from 'hooks/useNotifications'; import useUrlQueryData from 'hooks/useUrlQueryData'; import { FlatLogData } from 'lib/logs/flatLogData'; import { getPaginationQueryData } from 'lib/newQueryBuilder/getPaginationQueryData'; -import { cloneDeep, defaultTo, isEmpty, omit, set } from 'lodash-es'; +import { + cloneDeep, + defaultTo, + isEmpty, + isUndefined, + omit, + set, +} from 'lodash-es'; import { Sliders } from 'lucide-react'; import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -310,6 +318,19 @@ function LogsExplorerViews({ ], ); + const logEventCalledRef = useRef(false); + useEffect(() => { + if (!logEventCalledRef.current && !isUndefined(data?.payload)) { + const currentData = data?.payload?.data?.newResult?.data?.result || []; + logEvent('Logs Explorer: Page visited', { + panelType, + isEmpty: !currentData?.[0]?.list, + }); + logEventCalledRef.current = true; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data?.payload]); + const { mutate: updateDashboard, isLoading: isUpdateDashboardLoading, @@ -324,7 +345,7 @@ function LogsExplorerViews({ }, [currentQuery]); const handleExport = useCallback( - (dashboard: Dashboard | null): void => { + (dashboard: Dashboard | null, isNewDashboard?: boolean): void => { if (!dashboard || !panelType) return; const panelTypeParam = AVAILABLE_EXPORT_PANEL_TYPES.includes(panelType) @@ -346,6 +367,12 @@ function LogsExplorerViews({ options.selectColumns, ); + logEvent('Logs Explorer: Add to dashboard successful', { + panelType, + isNewDashboard, + dashboardName: dashboard?.data?.title, + }); + updateDashboard(updatedDashboard, { onSuccess: (data) => { if (data.error) { diff --git a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx index d45476b6f4..a520d98936 100644 --- a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx @@ -1,4 +1,5 @@ import { Col } from 'antd'; +import logEvent from 'api/common/logEvent'; import { ENTITY_VERSION_V4 } from 'constants/app'; import { PANEL_TYPES } from 'constants/queryBuilder'; import Graph from 'container/GridCardLayout/GridCard'; @@ -11,7 +12,7 @@ import { convertRawQueriesToTraceSelectedTags, resourceAttributesToTagFilterItems, } from 'hooks/useResourceAttribute/utils'; -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { useParams } from 'react-router-dom'; import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { EQueryType } from 'types/common/dashboard'; @@ -97,6 +98,24 @@ function DBCall(): JSX.Element { [servicename, tagFilterItems], ); + const logEventCalledRef = useRef(false); + + useEffect(() => { + if (!logEventCalledRef.current) { + const selectedEnvironments = queries.find( + (val) => val.tagKey === 'resource_deployment_environment', + )?.tagValue; + + logEvent('APM: Service detail page visited', { + selectedEnvironments, + resourceAttributeUsed: !!queries.length, + section: 'dbMetrics', + }); + logEventCalledRef.current = true; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const apmToTraceQuery = useGetAPMToTracesQueries({ servicename, isDBCall: true, diff --git a/frontend/src/container/MetricsApplication/Tabs/External.tsx b/frontend/src/container/MetricsApplication/Tabs/External.tsx index 564f0a657e..d224135175 100644 --- a/frontend/src/container/MetricsApplication/Tabs/External.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/External.tsx @@ -1,4 +1,5 @@ import { Col } from 'antd'; +import logEvent from 'api/common/logEvent'; import { ENTITY_VERSION_V4 } from 'constants/app'; import { PANEL_TYPES } from 'constants/queryBuilder'; import Graph from 'container/GridCardLayout/GridCard'; @@ -13,7 +14,7 @@ import { convertRawQueriesToTraceSelectedTags, resourceAttributesToTagFilterItems, } from 'hooks/useResourceAttribute/utils'; -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { useParams } from 'react-router-dom'; import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { EQueryType } from 'types/common/dashboard'; @@ -114,6 +115,23 @@ function External(): JSX.Element { ], }); + const logEventCalledRef = useRef(false); + useEffect(() => { + if (!logEventCalledRef.current) { + const selectedEnvironments = queries.find( + (val) => val.tagKey === 'resource_deployment_environment', + )?.tagValue; + + logEvent('APM: Service detail page visited', { + selectedEnvironments, + resourceAttributeUsed: !!queries.length, + section: 'externalMetrics', + }); + logEventCalledRef.current = true; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const externalCallRPSWidget = useMemo( () => getWidgetQueryBuilder({ diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx index 7410e4ccb3..96cc821fb8 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Overview.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx @@ -1,3 +1,4 @@ +import logEvent from 'api/common/logEvent'; import getTopLevelOperations, { ServiceDataProps, } from 'api/metrics/getTopLevelOperations'; @@ -17,7 +18,7 @@ import useUrlQuery from 'hooks/useUrlQuery'; import history from 'lib/history'; import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin'; import { defaultTo } from 'lodash-es'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useQuery } from 'react-query'; import { useDispatch } from 'react-redux'; import { useLocation, useParams } from 'react-router-dom'; @@ -81,6 +82,23 @@ function Application(): JSX.Element { [handleSetTimeStamp], ); + const logEventCalledRef = useRef(false); + useEffect(() => { + if (!logEventCalledRef.current) { + const selectedEnvironments = queries.find( + (val) => val.tagKey === 'resource_deployment_environment', + )?.tagValue; + + logEvent('APM: Service detail page visited', { + selectedEnvironments, + resourceAttributeUsed: !!queries.length, + section: 'overview', + }); + logEventCalledRef.current = true; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const { data: topLevelOperations, error: topLevelOperationsError, diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx index 2859b87305..18cecc9184 100644 --- a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx +++ b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx @@ -1,6 +1,7 @@ import './ComponentSlider.styles.scss'; import { Card, Modal } from 'antd'; +import logEvent from 'api/common/logEvent'; import { QueryParams } from 'constants/query'; import { PANEL_TYPES } from 'constants/queryBuilder'; import createQueryParams from 'lib/createQueryParams'; @@ -20,6 +21,13 @@ function DashboardGraphSlider(): JSX.Element { const onClickHandler = (name: PANEL_TYPES) => (): void => { const id = uuid(); handleToggleDashboardSlider(false); + logEvent('Dashboard Detail: New panel type selected', { + // dashboardId: '', + // dashboardName: '', + // numberOfPanels: 0, // todo - at this point we don't know these attributes + panelType: name, + widgetId: id, + }); const queryParamsLog = { graphType: name, widgetId: id, @@ -47,7 +55,6 @@ function DashboardGraphSlider(): JSX.Element { PANEL_TYPES_INITIAL_QUERY[name], ), }; - if (name === PANEL_TYPES.LIST) { history.push( `${history.location.pathname}/new?${createQueryParams(queryParamsLog)}`, diff --git a/frontend/src/container/NewDashboard/DashboardDescription/index.tsx b/frontend/src/container/NewDashboard/DashboardDescription/index.tsx index f1288493bd..1c851176c1 100644 --- a/frontend/src/container/NewDashboard/DashboardDescription/index.tsx +++ b/frontend/src/container/NewDashboard/DashboardDescription/index.tsx @@ -2,6 +2,7 @@ import './Description.styles.scss'; import { PlusOutlined } from '@ant-design/icons'; import { Button, Card, Input, Modal, Popover, Tag, Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn'; import { dashboardHelpMessage } from 'components/facingIssueBtn/util'; import { SOMETHING_WENT_WRONG } from 'constants/api'; @@ -126,6 +127,12 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element { const onEmptyWidgetHandler = useCallback(() => { handleToggleDashboardSlider(true); + logEvent('Dashboard Detail: Add new panel clicked', { + dashboardId: selectedDashboard?.uuid, + dashboardName: selectedDashboard?.data.title, + numberOfPanels: selectedDashboard?.data.widgets?.length, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [handleToggleDashboardSlider]); const handleLockDashboardToggle = (): void => { diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx index 3f6deab6f8..2d682b1f06 100644 --- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx @@ -2,6 +2,7 @@ import './QuerySection.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Tabs, Tooltip, Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; import PromQLIcon from 'assets/Dashboard/PromQl'; import TextToolTip from 'components/TextToolTip'; import { PANEL_TYPES } from 'constants/queryBuilder'; @@ -14,7 +15,7 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl'; import { useIsDarkMode } from 'hooks/useDarkMode'; import useUrlQuery from 'hooks/useUrlQuery'; -import { defaultTo } from 'lodash-es'; +import { defaultTo, isUndefined } from 'lodash-es'; import { Atom, Play, Terminal } from 'lucide-react'; import { useDashboard } from 'providers/Dashboard/Dashboard'; import { @@ -122,6 +123,18 @@ function QuerySection({ }; const handleRunQuery = (): void => { + const widgetId = urlQuery.get('widgetId'); + const isNewPanel = isUndefined(widgets?.find((e) => e.id === widgetId)); + + logEvent('Panel Edit: Stage and run query', { + dataSource: currentQuery.builder?.queryData?.[0]?.dataSource, + panelType: selectedWidget.panelTypes, + queryType: currentQuery.queryType, + widgetId: selectedWidget.id, + dashboardId: selectedDashboard?.uuid, + dashboardName: selectedDashboard?.data.title, + isNewPanel, + }); handleStageQuery(currentQuery); }; diff --git a/frontend/src/container/NewWidget/RightContainer/index.tsx b/frontend/src/container/NewWidget/RightContainer/index.tsx index 08387cd069..3cc27a5f16 100644 --- a/frontend/src/container/NewWidget/RightContainer/index.tsx +++ b/frontend/src/container/NewWidget/RightContainer/index.tsx @@ -82,7 +82,7 @@ function RightContainer({ const selectedGraphType = GraphTypes.find((e) => e.name === selectedGraph)?.display || ''; - const onCreateAlertsHandler = useCreateAlerts(selectedWidget); + const onCreateAlertsHandler = useCreateAlerts(selectedWidget, 'panelView'); const allowThreshold = panelTypeVsThreshold[selectedGraph]; const allowSoftMinMax = panelTypeVsSoftMinMax[selectedGraph]; diff --git a/frontend/src/container/NewWidget/index.tsx b/frontend/src/container/NewWidget/index.tsx index 811855b26a..c042269280 100644 --- a/frontend/src/container/NewWidget/index.tsx +++ b/frontend/src/container/NewWidget/index.tsx @@ -3,6 +3,7 @@ import './NewWidget.styles.scss'; import { WarningOutlined } from '@ant-design/icons'; import { Button, Flex, Modal, Space, Tooltip, Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn'; import { chartHelpMessage } from 'components/facingIssueBtn/util'; import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar'; @@ -31,7 +32,7 @@ import { getPreviousWidgets, getSelectedWidgetIndex, } from 'providers/Dashboard/util'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { generatePath, useParams } from 'react-router-dom'; @@ -101,6 +102,8 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element { const [isNewDashboard, setIsNewDashboard] = useState(false); + const logEventCalledRef = useRef(false); + useEffect(() => { const widgetId = query.get('widgetId'); const selectedWidget = widgets?.find((e) => e.id === widgetId); @@ -108,6 +111,18 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element { if (isWidgetNotPresent) { setIsNewDashboard(true); } + + if (!logEventCalledRef.current) { + logEvent('Panel Edit: Page visited', { + panelType: selectedWidget?.panelTypes, + dashboardId: selectedDashboard?.uuid, + widgetId: selectedWidget?.id, + dashboardName: selectedDashboard?.data.title, + isNewPanel: !!isWidgetNotPresent, + dataSource: currentQuery.builder.queryData?.[0]?.dataSource, + }); + logEventCalledRef.current = true; + } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -482,7 +497,20 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element { }; const onSaveDashboard = useCallback((): void => { + const widgetId = query.get('widgetId'); + const selectWidget = widgets?.find((e) => e.id === widgetId); + + logEvent('Panel Edit: Save changes', { + panelType: selectedWidget.panelTypes, + dashboardId: selectedDashboard?.uuid, + widgetId: selectedWidget.id, + dashboardName: selectedDashboard?.data.title, + queryType: currentQuery.queryType, + isNewPanel: isUndefined(selectWidget), + dataSource: currentQuery.builder.queryData?.[0]?.dataSource, + }); setSaveModal(true); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const isQueryBuilderActive = useIsFeatureDisabled( diff --git a/frontend/src/container/NoLogs/NoLogs.tsx b/frontend/src/container/NoLogs/NoLogs.tsx index 71e0d213e8..c4832e8a0e 100644 --- a/frontend/src/container/NoLogs/NoLogs.tsx +++ b/frontend/src/container/NoLogs/NoLogs.tsx @@ -1,6 +1,7 @@ import './NoLogs.styles.scss'; import { Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; import ROUTES from 'constants/routes'; import history from 'lib/history'; import { ArrowUpRight } from 'lucide-react'; @@ -21,6 +22,11 @@ export default function NoLogs({ e.stopPropagation(); if (cloudUser) { + if (dataSource === DataSource.TRACES) { + logEvent('Traces Explorer: Navigate to onboarding', {}); + } else if (dataSource === DataSource.LOGS) { + logEvent('Logs Explorer: Navigate to onboarding', {}); + } history.push( dataSource === 'traces' ? ROUTES.GET_STARTED_APPLICATION_MONITORING diff --git a/frontend/src/container/PipelinePage/PipelineListsView/PipelineListsView.tsx b/frontend/src/container/PipelinePage/PipelineListsView/PipelineListsView.tsx index 6f846e21cb..4e8def2e0c 100644 --- a/frontend/src/container/PipelinePage/PipelineListsView/PipelineListsView.tsx +++ b/frontend/src/container/PipelinePage/PipelineListsView/PipelineListsView.tsx @@ -3,11 +3,19 @@ import './styles.scss'; import { ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { Card, Modal, Table, Typography } from 'antd'; import { ExpandableConfig } from 'antd/es/table/interface'; +import logEvent from 'api/common/logEvent'; import savePipeline from 'api/pipeline/post'; import useAnalytics from 'hooks/analytics/useAnalytics'; import { useNotifications } from 'hooks/useNotifications'; +import { isUndefined } from 'lodash-es'; import cloneDeep from 'lodash-es/cloneDeep'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { useTranslation } from 'react-i18next'; @@ -466,6 +474,16 @@ function PipelineListsView({ getExpandIcon(expanded, onExpand, record), }; + const logEventCalledRef = useRef(false); + useEffect(() => { + if (!logEventCalledRef.current && !isUndefined(currPipelineData)) { + logEvent('Logs Pipelines: List page visited', { + number: currPipelineData?.length, + }); + logEventCalledRef.current = true; + } + }, [currPipelineData]); + return ( <> {contextHolder} diff --git a/frontend/src/container/ServiceApplication/ServiceTraces/index.tsx b/frontend/src/container/ServiceApplication/ServiceTraces/index.tsx index 370697af00..8d6238c68a 100644 --- a/frontend/src/container/ServiceApplication/ServiceTraces/index.tsx +++ b/frontend/src/container/ServiceApplication/ServiceTraces/index.tsx @@ -1,11 +1,13 @@ import localStorageGet from 'api/browser/localstorage/get'; import localStorageSet from 'api/browser/localstorage/set'; +import logEvent from 'api/common/logEvent'; import { SKIP_ONBOARDING } from 'constants/onboarding'; import useErrorNotification from 'hooks/useErrorNotification'; import { useQueryService } from 'hooks/useQueryService'; import useResourceAttribute from 'hooks/useResourceAttribute'; import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils'; -import { useMemo, useState } from 'react'; +import { isUndefined } from 'lodash-es'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { GlobalReducer } from 'types/reducer/globalTime'; @@ -45,6 +47,26 @@ function ServiceTraces(): JSX.Element { setSkipOnboarding(true); }; + const logEventCalledRef = useRef(false); + useEffect(() => { + if (!logEventCalledRef.current && !isUndefined(data)) { + const selectedEnvironments = queries.find( + (val) => val.tagKey === 'resource_deployment_environment', + )?.tagValue; + + const rps = data.reduce((total, service) => total + service.callRate, 0); + + logEvent('APM: List page visited', { + numberOfServices: data?.length, + selectedEnvironments, + resourceAttributeUsed: !!queries.length, + rps, + }); + logEventCalledRef.current = true; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data]); + if ( services.length === 0 && isLoading === false && diff --git a/frontend/src/container/SideNav/SideNav.tsx b/frontend/src/container/SideNav/SideNav.tsx index 82697d78b0..b5eb240af8 100644 --- a/frontend/src/container/SideNav/SideNav.tsx +++ b/frontend/src/container/SideNav/SideNav.tsx @@ -4,6 +4,7 @@ import './SideNav.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Tooltip } from 'antd'; +import logEvent from 'api/common/logEvent'; import cx from 'classnames'; import { FeatureKeys } from 'constants/features'; import ROUTES from 'constants/routes'; @@ -179,6 +180,11 @@ function SideNav({ }; const onClickShortcuts = (e: MouseEvent): void => { + // eslint-disable-next-line sonarjs/no-duplicate-string + logEvent('Sidebar: Menu clicked', { + menuRoute: '/shortcuts', + menuLabel: 'Keyboard Shortcuts', + }); if (isCtrlMetaKey(e)) { openInNewTab('/shortcuts'); } else { @@ -187,6 +193,10 @@ function SideNav({ }; const onClickGetStarted = (event: MouseEvent): void => { + logEvent('Sidebar: Menu clicked', { + menuRoute: '/get-started', + menuLabel: 'Get Started', + }); if (isCtrlMetaKey(event)) { openInNewTab('/get-started'); } else { @@ -313,6 +323,10 @@ function SideNav({ } else if (item) { onClickHandler(item?.key as string, event); } + logEvent('Sidebar: Menu clicked', { + menuRoute: item.key, + menuLabel: item.label, + }); }; useEffect(() => { @@ -440,6 +454,10 @@ function SideNav({ isActive={activeMenuKey === item?.key} onClick={(event: MouseEvent): void => { handleUserManagentMenuItemClick(item?.key as string, event); + logEvent('Sidebar: Menu clicked', { + menuRoute: item.key, + menuLabel: item.label, + }); }} /> ), @@ -456,6 +474,10 @@ function SideNav({ } else { history.push(`${inviteMemberMenuItem.key}`); } + logEvent('Sidebar: Menu clicked', { + menuRoute: inviteMemberMenuItem.key, + menuLabel: inviteMemberMenuItem.label, + }); }} /> )} @@ -470,6 +492,10 @@ function SideNav({ userSettingsMenuItem?.key as string, event, ); + logEvent('Sidebar: Menu clicked', { + menuRoute: userSettingsMenuItem.key, + menuLabel: 'User', + }); }} /> )} diff --git a/frontend/src/container/TimeSeriesView/TimeSeriesView.tsx b/frontend/src/container/TimeSeriesView/TimeSeriesView.tsx index 4abd67de21..49059eddf5 100644 --- a/frontend/src/container/TimeSeriesView/TimeSeriesView.tsx +++ b/frontend/src/container/TimeSeriesView/TimeSeriesView.tsx @@ -155,7 +155,9 @@ function TimeSeriesView({ chartData[0]?.length === 0 && !isLoading && !isError && - isFilterApplied && } + isFilterApplied && ( + + )} {chartData && chartData[0] && diff --git a/frontend/src/container/TracesExplorer/ListView/index.tsx b/frontend/src/container/TracesExplorer/ListView/index.tsx index 2bd7ac7e72..810ffb8241 100644 --- a/frontend/src/container/TracesExplorer/ListView/index.tsx +++ b/frontend/src/container/TracesExplorer/ListView/index.tsx @@ -156,7 +156,9 @@ function ListView({ isFilterApplied }: ListViewProps): JSX.Element { )} - {isDataPresent && isFilterApplied && } + {isDataPresent && isFilterApplied && ( + + )} {!isError && transformedQueryTableData.length !== 0 && ( } + isFilterApplied && ( + + )} {(tableData || []).length !== 0 && ( { +const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => { const queryRangeMutation = useMutation(getQueryRangeFormat); const { selectedTime: globalSelectedInterval } = useSelector< @@ -32,6 +34,24 @@ const useCreateAlerts = (widget?: Widgets): VoidFunction => { return useCallback(() => { if (!widget) return; + if (caller === 'panelView') { + logEvent('Panel Edit: Create alert', { + panelType: widget.panelTypes, + dashboardName: selectedDashboard?.data?.title, + dashboardId: selectedDashboard?.uuid, + widgetId: widget.id, + queryType: widget.query.queryType, + }); + } else if (caller === 'dashboardView') { + logEvent('Dashboard Detail: Panel action', { + action: MenuItemKeys.CreateAlerts, + panelType: widget.panelTypes, + dashboardName: selectedDashboard?.data?.title, + dashboardId: selectedDashboard?.uuid, + widgetId: widget.id, + queryType: widget.query.queryType, + }); + } const { queryPayload } = prepareQueryRangePayload({ query: widget.query, globalSelectedInterval, @@ -57,6 +77,7 @@ const useCreateAlerts = (widget?: Widgets): VoidFunction => { }); }, }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ globalSelectedInterval, notifications, diff --git a/frontend/src/pages/SaveView/index.tsx b/frontend/src/pages/SaveView/index.tsx index 02a2578f0a..efcd3f2a4b 100644 --- a/frontend/src/pages/SaveView/index.tsx +++ b/frontend/src/pages/SaveView/index.tsx @@ -10,6 +10,7 @@ import { TableProps, Typography, } from 'antd'; +import logEvent from 'api/common/logEvent'; import { getViewDetailsUsingViewKey, showErrorNotification, @@ -30,7 +31,7 @@ import { Trash2, X, } from 'lucide-react'; -import { ChangeEvent, useEffect, useState } from 'react'; +import { ChangeEvent, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; @@ -143,6 +144,22 @@ function SaveView(): JSX.Element { viewName: newViewName, }); + const logEventCalledRef = useRef(false); + useEffect(() => { + if (!logEventCalledRef.current && !isLoading) { + if (sourcepage === DataSource.TRACES) { + logEvent('Traces Views: Views visited', { + number: viewsData?.data.data.length, + }); + } else if (sourcepage === DataSource.LOGS) { + logEvent('Logs Views: Views visited', { + number: viewsData?.data.data.length, + }); + } + logEventCalledRef.current = true; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [viewsData?.data.data, isLoading]); const onUpdateQueryHandler = (): void => { updateViewAsync( { diff --git a/frontend/src/pages/TracesExplorer/Filter/Filter.tsx b/frontend/src/pages/TracesExplorer/Filter/Filter.tsx index 1a3fbc785c..3d3895e047 100644 --- a/frontend/src/pages/TracesExplorer/Filter/Filter.tsx +++ b/frontend/src/pages/TracesExplorer/Filter/Filter.tsx @@ -7,6 +7,7 @@ import { VerticalAlignTopOutlined, } from '@ant-design/icons'; import { Button, Flex, Tooltip, Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; import { getMs } from 'container/Trace/Filters/Panel/PanelBody/Duration/util'; import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; @@ -197,6 +198,11 @@ export function Filter(props: FilterProps): JSX.Element { })), }, }; + if (selectedFilters) { + logEvent('Traces Explorer: Sidebar filter used', { + selectedFilters, + }); + } redirectWithQueryBuilderData(preparedQuery); }, [currentQuery, redirectWithQueryBuilderData, selectedFilters], diff --git a/frontend/src/pages/TracesExplorer/index.tsx b/frontend/src/pages/TracesExplorer/index.tsx index ba267d383f..e598673c28 100644 --- a/frontend/src/pages/TracesExplorer/index.tsx +++ b/frontend/src/pages/TracesExplorer/index.tsx @@ -3,6 +3,7 @@ import './TracesExplorer.styles.scss'; import { FilterOutlined } from '@ant-design/icons'; import * as Sentry from '@sentry/react'; import { Button, Card, Tabs, Tooltip } from 'antd'; +import logEvent from 'api/common/logEvent'; import axios from 'axios'; import ExplorerCard from 'components/ExplorerCard/ExplorerCard'; import { LOCALSTORAGE } from 'constants/localStorage'; @@ -25,7 +26,7 @@ import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; import { cloneDeep, isEmpty, set } from 'lodash-es'; import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Dashboard } from 'types/api/dashboard/getAll'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; @@ -135,7 +136,7 @@ function TracesExplorer(): JSX.Element { }; const handleExport = useCallback( - (dashboard: Dashboard | null): void => { + (dashboard: Dashboard | null, isNewDashboard?: boolean): void => { if (!dashboard || !panelType) return; const panelTypeParam = AVAILABLE_EXPORT_PANEL_TYPES.includes(panelType) @@ -157,6 +158,12 @@ function TracesExplorer(): JSX.Element { options.selectColumns, ); + logEvent('Traces Explorer: Add to dashboard successful', { + panelType, + isNewDashboard, + dashboardName: dashboard?.data?.title, + }); + updateDashboard(updatedDashboard, { onSuccess: (data) => { if (data.error) { @@ -223,6 +230,13 @@ function TracesExplorer(): JSX.Element { currentPanelType, ]); const [isOpen, setOpen] = useState(true); + const logEventCalledRef = useRef(false); + useEffect(() => { + if (!logEventCalledRef.current) { + logEvent('Traces Explorer: Page visited', {}); + logEventCalledRef.current = true; + } + }, []); return ( }>