diff --git a/frontend/public/locales/en/titles.json b/frontend/public/locales/en/titles.json index 74a5bf9210..da719fdebf 100644 --- a/frontend/public/locales/en/titles.json +++ b/frontend/public/locales/en/titles.json @@ -68,5 +68,6 @@ "INFRASTRUCTURE_MONITORING_KUBERNETES": "SigNoz | Infra Monitoring", "METRICS_EXPLORER": "SigNoz | Metrics Explorer", "METRICS_EXPLORER_EXPLORER": "SigNoz | Metrics Explorer", - "METRICS_EXPLORER_VIEWS": "SigNoz | Metrics Explorer" + "METRICS_EXPLORER_VIEWS": "SigNoz | Metrics Explorer", + "API_MONITORING": "SigNoz | API Monitoring" } diff --git a/frontend/src/constants/features.ts b/frontend/src/constants/features.ts index 93bf8e4ee4..50ba72d034 100644 --- a/frontend/src/constants/features.ts +++ b/frontend/src/constants/features.ts @@ -25,5 +25,6 @@ export enum FeatureKeys { ANOMALY_DETECTION = 'ANOMALY_DETECTION', AWS_INTEGRATION = 'AWS_INTEGRATION', ONBOARDING_V3 = 'ONBOARDING_V3', + THIRD_PARTY_API = 'THIRD_PARTY_API', TRACE_FUNNELS = 'TRACE_FUNNELS', } diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/AllEndPoints.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/AllEndPoints.tsx index be638aa795..d24d6a066b 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/AllEndPoints.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/AllEndPoints.tsx @@ -1,5 +1,6 @@ import { LoadingOutlined } from '@ant-design/icons'; import { Select, Spin, Table, Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; import { ENTITY_VERSION_V4 } from 'constants/app'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { @@ -151,6 +152,7 @@ function AllEndPoints({ if (groupBy.length === 0) { setSelectedEndPointName(record.endpointName); // this will open up the endpoint details tab setSelectedView(VIEW_TYPES.ENDPOINT_DETAILS); + logEvent('API Monitoring: Endpoint name row clicked', {}); } else { handleGroupByRowClick(record); // this will prepare the nested query payload } diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.styles.scss b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.styles.scss index d2fb7fb6c7..b4dc5657e7 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.styles.scss +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.styles.scss @@ -392,6 +392,39 @@ gap: 20px; padding-top: 20px; + .endpoint-meta-data { + display: flex; + gap: 8px; + .endpoint-meta-data-pill { + display: flex; + align-items: flex-start; + border-radius: 4px; + border: 1px solid var(--bg-slate-300); + width: fit-content; + .endpoint-meta-data-label { + display: flex; + padding: 6px 8px; + align-items: center; + gap: 4px; + border-right: 1px solid var(--bg-slate-300); + color: var(--text-vanilla-100); + background: var(--bg-slate-500); + height: calc(100% - 12px); + } + + .endpoint-meta-data-value { + display: flex; + padding: 6px 8px; + justify-content: center; + align-items: center; + gap: 10px; + color: var(--text-vanilla-400); + background: var(--bg-slate-400); + height: calc(100% - 12px); + } + } + } + .endpoint-details-filters-container { display: flex; flex-direction: row; @@ -405,6 +438,13 @@ } } + .ant-select-item, + .ant-select-item-option-content { + flex: auto; + white-space: normal; + overflow-wrap: break-word; + } + .status-code-table-container { border-radius: 3px; border: 1px solid var(--bg-slate-500); @@ -809,6 +849,13 @@ width: 100%; } } + + .ant-select-item, + .ant-select-item-option-content { + flex: auto; + white-space: normal; + overflow-wrap: break-word; + } } .lightMode { @@ -917,6 +964,20 @@ } } + .endpoint-meta-data { + .endpoint-meta-data-pill { + .endpoint-meta-data-label { + color: var(--text-ink-300); + background: var(--bg-vanilla-100); + } + + .endpoint-meta-data-value { + color: var(--text-ink-300); + background: var(--bg-vanilla-100); + } + } + } + .status-code-table-container { .ant-table { .ant-table-thead > tr > th { diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.tsx index 70109e9df0..fa82cd3f5d 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.tsx @@ -19,12 +19,14 @@ function DomainDetails({ selectedDomainIndex, setSelectedDomainIndex, domainListLength, + domainListFilters, }: { domainData: any; handleClose: () => void; selectedDomainIndex: number; setSelectedDomainIndex: (index: number) => void; domainListLength: number; + domainListFilters: IBuilderQuery['filters']; }): JSX.Element { const [selectedView, setSelectedView] = useState(VIEWS.ALL_ENDPOINTS); const [selectedEndPointName, setSelectedEndPointName] = useState(''); @@ -132,6 +134,7 @@ function DomainDetails({ domainName={domainData.domainName} endPointName={selectedEndPointName} setSelectedEndPointName={setSelectedEndPointName} + domainListFilters={domainListFilters} /> )} diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/EndPointDetails.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/EndPointDetails.tsx index 4b8959b4bb..949152ecee 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/EndPointDetails.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/EndPointDetails.tsx @@ -2,7 +2,10 @@ import { ENTITY_VERSION_V4 } from 'constants/app'; import { initialQueriesMap } from 'constants/queryBuilder'; import { END_POINT_DETAILS_QUERY_KEYS_ARRAY, + extractPortAndEndpoint, getEndPointDetailsQueryPayload, + getLatencyOverTimeWidgetData, + getRateOverTimeWidgetData, } from 'container/ApiMonitoring/utils'; import QueryBuilderSearchV2 from 'container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2'; import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; @@ -27,10 +30,12 @@ function EndPointDetails({ domainName, endPointName, setSelectedEndPointName, + domainListFilters, }: { domainName: string; endPointName: string; setSelectedEndPointName: (value: string) => void; + domainListFilters: IBuilderQuery['filters']; }): JSX.Element { const { maxTime, minTime } = useSelector( (state) => state.globalTime, @@ -101,8 +106,6 @@ function EndPointDetails({ const [ endPointMetricsDataQuery, endPointStatusCodeDataQuery, - endPointRateOverTimeDataQuery, - endPointLatencyOverTimeDataQuery, endPointDropDownDataQuery, endPointDependentServicesDataQuery, endPointStatusCodeBarChartsDataQuery, @@ -115,12 +118,29 @@ function EndPointDetails({ endPointDetailsDataQueries[3], endPointDetailsDataQueries[4], endPointDetailsDataQueries[5], - endPointDetailsDataQueries[6], - endPointDetailsDataQueries[7], ], [endPointDetailsDataQueries], ); + const { endpoint, port } = useMemo( + () => extractPortAndEndpoint(endPointName), + [endPointName], + ); + + const [rateOverTimeWidget, latencyOverTimeWidget] = useMemo( + () => [ + getRateOverTimeWidgetData(domainName, endPointName, { + items: [...domainListFilters.items, ...filters.items], + op: filters.op, + }), + getLatencyOverTimeWidgetData(domainName, endPointName, { + items: [...domainListFilters.items, ...filters.items], + op: filters.op, + }), + ], + [domainName, endPointName, filters, domainListFilters], + ); + return (
@@ -129,6 +149,8 @@ function EndPointDetails({ selectedEndPointName={endPointName} setSelectedEndPointName={setSelectedEndPointName} endPointDropDownDataQuery={endPointDropDownDataQuery} + parentContainerDiv=".endpoint-details-filters-container" + dropdownStyle={{ width: 'calc(100% - 36px)' }} />
@@ -141,6 +163,16 @@ function EndPointDetails({ />
+
+
+
Endpoint
+
{endpoint || '-'}
+
+
+
Port
+
{port || '-'}
+
+
{!isServicesFilterApplied && ( - - + + ); } diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/EndPointDetailsWrapper.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/EndPointDetailsWrapper.tsx index cdbdc7e4ea..3473faf218 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/EndPointDetailsWrapper.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/EndPointDetailsWrapper.tsx @@ -8,6 +8,7 @@ import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { SuccessResponse } from 'types/api'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { GlobalReducer } from 'types/reducer/globalTime'; import EndPointDetailsZeroState from './components/EndPointDetailsZeroState'; @@ -17,10 +18,12 @@ function EndPointDetailsWrapper({ domainName, endPointName, setSelectedEndPointName, + domainListFilters, }: { domainName: string; endPointName: string; setSelectedEndPointName: (value: string) => void; + domainListFilters: IBuilderQuery['filters']; }): JSX.Element { const { maxTime, minTime } = useSelector( (state) => state.globalTime, @@ -69,6 +72,7 @@ function EndPointDetailsWrapper({ domainName={domainName} endPointName={endPointName} setSelectedEndPointName={setSelectedEndPointName} + domainListFilters={domainListFilters} /> ); } diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointDetailsZeroState.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointDetailsZeroState.tsx index 981dfdaac0..96b945dd21 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointDetailsZeroState.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointDetailsZeroState.tsx @@ -28,6 +28,8 @@ function EndPointDetailsZeroState({ diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointMetrics.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointMetrics.tsx index dd60b89a59..7999e7e06c 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointMetrics.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointMetrics.tsx @@ -70,7 +70,7 @@ function EndPointMetrics({ ) : ( - {metricsData?.rate}/sec + {metricsData?.rate} ops/sec )} diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointsDropDown.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointsDropDown.tsx index 5cb4f30670..a351cf4208 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointsDropDown.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointsDropDown.tsx @@ -8,16 +8,22 @@ interface EndPointsDropDownProps { selectedEndPointName?: string; setSelectedEndPointName: (value: string) => void; endPointDropDownDataQuery: UseQueryResult, unknown>; + parentContainerDiv?: string; + dropdownStyle?: React.CSSProperties; } const defaultProps = { selectedEndPointName: '', + parentContainerDiv: '', + dropdownStyle: {}, }; function EndPointsDropDown({ selectedEndPointName, setSelectedEndPointName, endPointDropDownDataQuery, + parentContainerDiv, + dropdownStyle, }: EndPointsDropDownProps): JSX.Element { const { data, isLoading, isFetching } = endPointDropDownDataQuery; @@ -39,6 +45,13 @@ function EndPointsDropDown({ style={{ width: '100%' }} onChange={handleChange} options={formattedData} + getPopupContainer={ + parentContainerDiv + ? (): HTMLElement => + document.querySelector(parentContainerDiv) as HTMLElement + : (triggerNode): HTMLElement => triggerNode.parentNode as HTMLElement + } + dropdownStyle={dropdownStyle} /> ); } diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/ExpandedRow.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/ExpandedRow.tsx index e8cf25c93d..5b7bb8b5e5 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/ExpandedRow.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/ExpandedRow.tsx @@ -1,6 +1,7 @@ import { LoadingOutlined } from '@ant-design/icons'; import { Spin, Table } from 'antd'; import { ColumnType } from 'antd/lib/table'; +import logEvent from 'api/common/logEvent'; import { ENTITY_VERSION_V4 } from 'constants/app'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { @@ -114,6 +115,7 @@ function ExpandedRow({ onClick: (): void => { setSelectedEndPointName(record.endpointName); setSelectedView(VIEW_TYPES.ENDPOINT_DETAILS); + logEvent('API Monitoring: Endpoint name row clicked', {}); }, className: 'expanded-clickable-row', })} diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/MetricOverTimeGraph.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/MetricOverTimeGraph.tsx index fd4b3bb262..ea242fa20b 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/MetricOverTimeGraph.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/MetricOverTimeGraph.tsx @@ -1,110 +1,18 @@ -import { Card, Skeleton, Typography } from 'antd'; -import cx from 'classnames'; -import Uplot from 'components/Uplot'; -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { - apiWidgetInfo, - extractPortAndEndpoint, - getFormattedChartData, -} from 'container/ApiMonitoring/utils'; -import { useIsDarkMode } from 'hooks/useDarkMode'; -import { useResizeObserver } from 'hooks/useDimensions'; -import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; -import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; -import { useCallback, useMemo, useRef } from 'react'; -import { UseQueryResult } from 'react-query'; -import { useSelector } from 'react-redux'; -import { AppState } from 'store/reducers'; -import { SuccessResponse } from 'types/api'; -import { GlobalReducer } from 'types/reducer/globalTime'; -import { Options } from 'uplot'; - -import ErrorState from './ErrorState'; - -function MetricOverTimeGraph({ - metricOverTimeDataQuery, - widgetInfoIndex, - endPointName, -}: { - metricOverTimeDataQuery: UseQueryResult, unknown>; - widgetInfoIndex: number; - endPointName: string; -}): JSX.Element { - const { data } = metricOverTimeDataQuery; - - const { minTime, maxTime } = useSelector( - (state) => state.globalTime, - ); - - const graphRef = useRef(null); - const dimensions = useResizeObserver(graphRef); - - const { endpoint } = extractPortAndEndpoint(endPointName); - - const formattedChartData = useMemo( - () => getFormattedChartData(data?.payload, [endpoint]), - [data?.payload, endpoint], - ); - - const chartData = useMemo(() => getUPlotChartData(formattedChartData), [ - formattedChartData, - ]); - - const isDarkMode = useIsDarkMode(); - - const options = useMemo( - () => - getUPlotChartOptions({ - apiResponse: formattedChartData, - isDarkMode, - dimensions, - yAxisUnit: apiWidgetInfo[widgetInfoIndex].yAxisUnit, - softMax: null, - softMin: null, - minTimeScale: Math.floor(minTime / 1e9), - maxTimeScale: Math.floor(maxTime / 1e9), - panelType: PANEL_TYPES.TIME_SERIES, - }), - [ - formattedChartData, - minTime, - maxTime, - widgetInfoIndex, - dimensions, - isDarkMode, - ], - ); - - const renderCardContent = useCallback( - (query: UseQueryResult, unknown>): JSX.Element => { - if (query.isLoading) { - return ; - } - - if (query.error) { - return ; - } - - return ( -
- -
- ); - }, - [options, chartData], - ); +import { Card } from 'antd'; +import GridCard from 'container/GridCardLayout/GridCard'; +import { Widgets } from 'types/api/dashboard/getAll'; +function MetricOverTimeGraph({ widget }: { widget: Widgets }): JSX.Element { return (
- {apiWidgetInfo[widgetInfoIndex].title} -
- {renderCardContent(metricOverTimeDataQuery)} +
+ {}} + customOnDragSelect={(): void => {}} + />
diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/StatusCodeBarCharts.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/StatusCodeBarCharts.tsx index bba17d1f21..c153d07698 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/StatusCodeBarCharts.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/StatusCodeBarCharts.tsx @@ -1,13 +1,22 @@ +import { Color } from '@signozhq/design-tokens'; import { Button, Card, Skeleton, Typography } from 'antd'; import cx from 'classnames'; +import { useGetGraphCustomSeries } from 'components/CeleryTask/useGetGraphCustomSeries'; +import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer'; import Uplot from 'components/Uplot'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { + getCustomFiltersForBarChart, getFormattedEndPointStatusCodeChartData, + getStatusCodeBarChartWidgetData, statusCodeWidgetInfo, } from 'container/ApiMonitoring/utils'; +import { handleGraphClick } from 'container/GridCardLayout/GridCard/utils'; +import { useGraphClickToShowButton } from 'container/GridCardLayout/useGraphClickToShowButton'; +import useNavigateToExplorerPages from 'container/GridCardLayout/useNavigateToExplorerPages'; import { useIsDarkMode } from 'hooks/useDarkMode'; import { useResizeObserver } from 'hooks/useDimensions'; +import { useNotifications } from 'hooks/useNotifications'; import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; import { useCallback, useMemo, useRef, useState } from 'react'; @@ -15,6 +24,8 @@ import { UseQueryResult } from 'react-query'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { SuccessResponse } from 'types/api'; +import { Widgets } from 'types/api/dashboard/getAll'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { GlobalReducer } from 'types/reducer/globalTime'; import { Options } from 'uplot'; @@ -23,6 +34,10 @@ import ErrorState from './ErrorState'; function StatusCodeBarCharts({ endPointStatusCodeBarChartsDataQuery, endPointStatusCodeLatencyBarChartsDataQuery, + domainName, + endPointName, + domainListFilters, + filters, }: { endPointStatusCodeBarChartsDataQuery: UseQueryResult< SuccessResponse, @@ -32,6 +47,10 @@ function StatusCodeBarCharts({ SuccessResponse, unknown >; + domainName: string; + endPointName: string; + domainListFilters: IBuilderQuery['filters']; + filters: IBuilderQuery['filters']; }): JSX.Element { // 0 : Status Code Count // 1 : Status Code Latency @@ -85,6 +104,72 @@ function StatusCodeBarCharts({ const isDarkMode = useIsDarkMode(); + const graphClick = useGraphClickToShowButton({ + graphRef, + isButtonEnabled: true, + buttonClassName: 'view-onclick-show-button', + }); + + const navigateToExplorer = useNavigateToExplorer(); + + const navigateToExplorerPages = useNavigateToExplorerPages(); + const { notifications } = useNotifications(); + + const { getCustomSeries } = useGetGraphCustomSeries({ + isDarkMode, + drawStyle: 'bars', + colorMapping: { + '200-299': Color.BG_FOREST_500, + '300-399': Color.BG_AMBER_400, + '400-499': Color.BG_CHERRY_500, + '500-599': Color.BG_ROBIN_500, + Other: Color.BG_SIENNA_500, + }, + }); + + const widget = useMemo( + () => + getStatusCodeBarChartWidgetData(domainName, endPointName, { + items: [...domainListFilters.items, ...filters.items], + op: filters.op, + }), + [domainName, endPointName, domainListFilters, filters], + ); + + const graphClickHandler = useCallback( + ( + xValue: number, + yValue: number, + mouseX: number, + mouseY: number, + metric?: { [key: string]: string }, + queryData?: { queryName: string; inFocusOrNot: boolean }, + ): void => { + const customFilters = getCustomFiltersForBarChart(metric); + handleGraphClick({ + xValue, + yValue, + mouseX, + mouseY, + metric, + queryData, + widget, + navigateToExplorerPages, + navigateToExplorer, + notifications, + graphClick, + customFilters, + }); + }, + [ + widget, + navigateToExplorerPages, + navigateToExplorer, + notifications, + graphClick, + ], + ); + const options = useMemo( () => getUPlotChartOptions({ @@ -100,6 +185,8 @@ function StatusCodeBarCharts({ minTimeScale: Math.floor(minTime / 1e9), maxTimeScale: Math.floor(maxTime / 1e9), panelType: PANEL_TYPES.BAR, + onClickHandler: graphClickHandler, + customSeries: getCustomSeries, }), [ minTime, @@ -109,6 +196,8 @@ function StatusCodeBarCharts({ formattedEndPointStatusCodeBarChartsDataPayload, formattedEndPointStatusCodeLatencyBarChartsDataPayload, isDarkMode, + graphClickHandler, + getCustomSeries, ], ); diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainList.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainList.tsx index b326feb519..7b3f0cd6d8 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainList.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainList.tsx @@ -3,6 +3,7 @@ import '../Explorer.styles.scss'; import { LoadingOutlined } from '@ant-design/icons'; import { Spin, Table, Typography } from 'antd'; import axios from 'api'; +import logEvent from 'api/common/logEvent'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { AxiosError } from 'axios'; import cx from 'classnames'; @@ -130,6 +131,7 @@ function DomainList({ (item) => item.key === record.key, ); setSelectedDomainIndex(dataIndex); + logEvent('API Monitoring: Domain name row clicked', {}); } }, className: 'expanded-clickable-row', @@ -147,6 +149,7 @@ function DomainList({ handleClose={(): void => { setSelectedDomainIndex(-1); }} + domainListFilters={query?.filters} /> )} diff --git a/frontend/src/container/ApiMonitoring/Explorer/Explorer.tsx b/frontend/src/container/ApiMonitoring/Explorer/Explorer.tsx index 5a1de332c1..6ac17b9634 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Explorer.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Explorer.tsx @@ -3,13 +3,14 @@ import './Explorer.styles.scss'; import { FilterOutlined } from '@ant-design/icons'; import * as Sentry from '@sentry/react'; import { Switch, Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; import cx from 'classnames'; import QuickFilters from 'components/QuickFilters/QuickFilters'; import { QuickFiltersSource } from 'components/QuickFilters/types'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations'; import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback'; -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; @@ -21,6 +22,10 @@ function Explorer(): JSX.Element { const { currentQuery } = useQueryBuilder(); + useEffect(() => { + logEvent('API Monitoring: Landing page visited', {}); + }, []); + const { handleChangeQueryData } = useQueryOperations({ index: 0, query: currentQuery.builder.queryData[0], @@ -64,7 +69,12 @@ function Explorer(): JSX.Element { style={{ marginLeft: 'auto' }} checked={showIP} onClick={(): void => { - setShowIP((showIP) => !showIP); + setShowIP((showIP): boolean => { + logEvent('API Monitoring: Show IP addresses clicked', { + showIP: !showIP, + }); + return !showIP; + }); }} />
diff --git a/frontend/src/container/ApiMonitoring/utils.tsx b/frontend/src/container/ApiMonitoring/utils.tsx index 1706934708..bb4f46c0a5 100644 --- a/frontend/src/container/ApiMonitoring/utils.tsx +++ b/frontend/src/container/ApiMonitoring/utils.tsx @@ -8,16 +8,23 @@ import { } from 'components/QuickFilters/types'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import { GraphClickMetaData } from 'container/GridCardLayout/useNavigateToExplorerPages'; +import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory'; import dayjs from 'dayjs'; import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; import { cloneDeep } from 'lodash-es'; import { ArrowUpDown, ChevronDown, ChevronRight } from 'lucide-react'; +import { getWidgetQuery } from 'pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil'; +import { Widgets } from 'types/api/dashboard/getAll'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { BaseAutocompleteData, DataTypes, } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { + IBuilderQuery, + TagFilterItem, +} from 'types/api/queryBuilder/queryBuilderData'; import { QueryData } from 'types/api/widgets/getQuery'; import { EQueryType } from 'types/common/dashboard'; import { DataSource } from 'types/common/queryBuilder'; @@ -128,12 +135,15 @@ export const columnsConfig: ColumnType[] = [ sorter: false, align: 'right', className: `column`, - render: (lastUsed: number): string => getLastUsedRelativeTime(lastUsed), + render: (lastUsed: number | string): string => + lastUsed === 'n/a' || lastUsed === '-' + ? '-' + : getLastUsedRelativeTime(lastUsed as number), }, { title: (
- Rate /s + Rate ops/s
), dataIndex: 'rate', @@ -155,21 +165,26 @@ export const columnsConfig: ColumnType[] = [ sorter: false, align: 'right', className: `column`, - render: (errorRate: number): React.ReactNode => ( - { - const errorRatePercent = Number((errorRate * 100).toFixed(1)); - if (errorRatePercent >= 90) return Color.BG_SAKURA_500; - if (errorRatePercent >= 60) return Color.BG_AMBER_500; - return Color.BG_FOREST_500; - })()} - className="progress-bar error-rate" - /> - ), + render: (errorRate: number | string): React.ReactNode => { + if (errorRate === 'n/a' || errorRate === '-') { + return '-'; + } + return ( + { + const errorRatePercent = Number(((errorRate as number) * 100).toFixed(1)); + if (errorRatePercent >= 90) return Color.BG_SAKURA_500; + if (errorRatePercent >= 60) return Color.BG_AMBER_500; + return Color.BG_FOREST_500; + })()} + className="progress-bar error-rate" + /> + ); + }, }, { title: ( @@ -217,9 +232,9 @@ interface APIMonitoringResponseRow { data: { endpoints: number; error_rate: number; - lastseen: number; + lastseen: number | string; [domainNameKey]: string; - p99: number; + p99: number | string; rps: number; }; } @@ -232,12 +247,12 @@ interface EndPointsResponseRow { export interface APIDomainsRowData { key: string; - domainName: React.ReactNode; - endpointCount: React.ReactNode; - rate: React.ReactNode; - errorRate: React.ReactNode; - latency: React.ReactNode; - lastUsed: React.ReactNode; + domainName: string; + endpointCount: number | string; + rate: number | string; + errorRate: number | string; + latency: number | string; + lastUsed: string; } // Rename this to a proper name @@ -246,12 +261,20 @@ export const formatDataForTable = ( ): APIDomainsRowData[] => data?.map((domain) => ({ key: v4(), - domainName: domain.data[domainNameKey] || '', - endpointCount: domain.data.endpoints, - rate: domain.data.rps, - errorRate: domain.data.error_rate, - latency: Math.round(domain.data.p99 / 1000000), // Convert from nanoseconds to milliseconds - lastUsed: new Date(Math.floor(domain.data.lastseen / 1000000)).toISOString(), // Convert from nanoseconds to milliseconds + domainName: domain?.data[domainNameKey] || '-', + endpointCount: domain?.data?.endpoints || '-', + rate: domain.data.rps || '-', + errorRate: domain.data.error_rate || '-', + latency: + domain.data.p99 === 'n/a' + ? '-' + : Math.round(Number(domain.data.p99) / 1000000), // Convert from nanoseconds to milliseconds + lastUsed: + domain.data.lastseen === 'n/a' + ? '-' + : new Date( + Math.floor(Number(domain.data.lastseen) / 1000000), + ).toISOString(), // Convert from nanoseconds to milliseconds })); // Rename this to a proper name @@ -468,7 +491,6 @@ export const extractPortAndEndpoint = ( } }; -// Add icons in the below column headers export const getEndPointsColumnsConfig = ( isGroupedByAttribute: boolean, expandedRowKeys: React.Key[], @@ -576,7 +598,7 @@ export const formatEndPointsDataForTable = ( ); return { key: v4(), - endpointName: (endpoint.data['http.url'] as string) || '', + endpointName: (endpoint.data['http.url'] as string) || '-', port, callCount: endpoint.data.A || '-', latency: @@ -593,7 +615,6 @@ export const formatEndPointsDataForTable = ( const groupedByAttributeData = groupBy.map((attribute) => attribute.key); - // TODO: Use tags to show the concatenated attribute values return data?.map((endpoint) => { const newEndpointName = groupedByAttributeData .map((attribute) => endpoint.data[attribute]) @@ -639,7 +660,7 @@ export const createFiltersForSelectedRowData = ( type: null, }, op: '=', - value: groupedByMeta[key], + value: groupedByMeta[key] || '', id: key, })), ); @@ -649,12 +670,10 @@ export const createFiltersForSelectedRowData = ( // First query payload for endpoint metrics // Second query payload for endpoint status code -// Third query payload for endpoint rate over time graph -// Fourth query payload for endpoint latency over time graph -// Fifth query payload for endpoint dropdown selection -// Sixth query payload for endpoint dependant services -// Seventh query payload for endpoint response status count bar chart -// Eighth query payload for endpoint response status code latency bar chart +// Third query payload for endpoint dropdown selection +// Fourth query payload for endpoint dependant services +// Fifth query payload for endpoint response status count bar chart +// Sixth query payload for endpoint response status code latency bar chart export const getEndPointDetailsQueryPayload = ( domainName: string, endPointName: string, @@ -1101,205 +1120,6 @@ export const getEndPointDetailsQueryPayload = ( end, step: 60, }, - { - selectedTime: 'GLOBAL_TIME', - graphType: PANEL_TYPES.TIME_SERIES, - query: { - builder: { - queryData: [ - { - aggregateAttribute: { - dataType: DataTypes.String, - id: '------false', - isColumn: false, - key: '', - type: '', - }, - aggregateOperator: 'rate', - dataSource: DataSource.TRACES, - disabled: false, - expression: 'B', - filters: { - items: [ - { - id: '3c76fe0b', - key: { - dataType: DataTypes.String, - id: 'net.peer.name--string--tag--false', - isColumn: false, - isJSON: false, - key: 'net.peer.name', - type: 'tag', - }, - op: '=', - value: domainName, - }, - { - id: '30710f04', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, - ...filters.items, - ], - op: 'AND', - }, - functions: [], - groupBy: [ - { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - ], - having: [], - legend: '', - limit: null, - orderBy: [], - queryName: 'B', - reduceTo: 'avg', - spaceAggregation: 'sum', - stepInterval: 60, - timeAggregation: 'rate', - }, - ], - queryFormulas: [], - }, - clickhouse_sql: [ - { - disabled: false, - legend: '', - name: 'A', - query: '', - }, - ], - id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', - promql: [ - { - disabled: false, - legend: '', - name: 'A', - query: '', - }, - ], - queryType: EQueryType.QUERY_BUILDER, - }, - variables: {}, - formatForWeb: false, - start, - end, - step: 60, - }, - { - selectedTime: 'GLOBAL_TIME', - graphType: PANEL_TYPES.TIME_SERIES, - query: { - builder: { - queryData: [ - { - aggregateAttribute: { - dataType: DataTypes.Float64, - id: 'duration_nano--float64----true', - isColumn: true, - isJSON: false, - key: 'duration_nano', - type: '', - }, - aggregateOperator: 'p99', - dataSource: DataSource.TRACES, - disabled: false, - expression: 'B', - filters: { - items: [ - { - id: '63adb3ff', - key: { - dataType: DataTypes.String, - id: 'net.peer.name--string--tag--false', - isColumn: false, - isJSON: false, - key: 'net.peer.name', - type: 'tag', - }, - op: '=', - value: domainName, - }, - { - id: '50142500', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, - ...filters.items, - ], - op: 'AND', - }, - functions: [], - groupBy: [ - { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - ], - having: [], - legend: '', - limit: null, - orderBy: [], - queryName: 'B', - reduceTo: 'avg', - spaceAggregation: 'sum', - stepInterval: 60, - timeAggregation: 'p99', - }, - ], - queryFormulas: [], - }, - clickhouse_sql: [ - { - disabled: false, - legend: '', - name: 'A', - query: '', - }, - ], - id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', - promql: [ - { - disabled: false, - legend: '', - name: 'A', - query: '', - }, - ], - queryType: EQueryType.QUERY_BUILDER, - }, - variables: {}, - formatForWeb: false, - start, - end, - step: 60, - }, { selectedTime: 'GLOBAL_TIME', graphType: PANEL_TYPES.TABLE, @@ -1801,7 +1621,7 @@ interface EndPointMetricsData { interface EndPointStatusCodeData { key: string; statusCode: string; - count: number; + count: number | string; p99Latency: number | string; } @@ -1824,8 +1644,8 @@ export const getFormattedEndPointStatusCodeData = ( ): EndPointStatusCodeData[] => data?.map((row) => ({ key: v4(), - statusCode: row.data.response_status_code, - count: row.data.A, + statusCode: row.data.response_status_code || '-', + count: row.data.A || '-', p99Latency: row.data.B === 'n/a' ? '-' : Math.round(Number(row.data.B) / 1000000), // Convert from nanoseconds to milliseconds, })); @@ -1857,11 +1677,6 @@ export const endPointStatusCodeColumns: ColumnType[] = [ }, ]; -export const apiWidgetInfo = [ - { title: 'Rate over time', yAxisUnit: 'ops/s' }, - { title: 'Latency over time', yAxisUnit: 'ns' }, -]; - export const statusCodeWidgetInfo = [ { yAxisUnit: 'calls' }, { yAxisUnit: 'ns' }, @@ -1885,8 +1700,8 @@ export const getFormattedEndPointDropDownData = ( ): EndPointDropDownData[] => data?.map((row) => ({ key: v4(), - label: row.data['http.url'], - value: row.data['http.url'], + label: row.data['http.url'] || '-', + value: row.data['http.url'] || '-', })); interface DependentServicesResponseRow { @@ -1903,6 +1718,7 @@ interface DependentServicesData { percentage: number; } +// Discuss once about type safety of this function export const getFormattedDependentServicesData = ( data: DependentServicesResponseRow[], ): DependentServicesData[] => { @@ -1983,7 +1799,7 @@ export const groupStatusCodes = ( // Track all timestamps series.values.forEach((value) => { - allTimestamps.add(value[0]); + allTimestamps.add(Number(value[0])); }); // Initialize or update the grouped series @@ -2049,8 +1865,114 @@ export const groupStatusCodes = ( }); }); - return Object.values(groupedSeries); + // Define the order of status code ranges + const statusCodeOrder = ['200-299', '300-399', '400-499', '500-599', 'Other']; + + // Return the grouped series in the specified order + return statusCodeOrder + .filter((code) => groupedSeries[code]) // Only include codes that exist in the data + .map((code) => groupedSeries[code]); }; + +export const getStatusCodeBarChartWidgetData = ( + domainName: string, + endPointName: string, + filters: IBuilderQuery['filters'], +): Widgets => ({ + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.String, + id: '------false', + isColumn: false, + key: '', + type: '', + }, + aggregateOperator: 'count', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: 'c6724407', + key: { + dataType: DataTypes.String, + id: 'net.peer.name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'net.peer.name', + type: 'tag', + }, + op: '=', + value: domainName, + }, + { + id: '8b1be6f0', + key: { + dataType: DataTypes.String, + id: 'http.url--string--tag--false', + isColumn: false, + isJSON: false, + key: 'http.url', + type: 'tag', + }, + op: '=', + value: endPointName, + }, + ...filters.items, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: '', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'rate', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + description: '', + id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', + isStacked: false, + panelTypes: PANEL_TYPES.BAR, + title: '', + opacity: '', + nullZeroValues: '', + timePreferance: 'GLOBAL_TIME', + softMin: null, + softMax: null, + selectedLogFields: null, + selectedTracesFields: null, +}); interface EndPointStatusCodePayloadData { data: { result: QueryData[]; @@ -2085,3 +2007,277 @@ export const END_POINT_DETAILS_QUERY_KEYS_ARRAY = [ REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_BAR_CHARTS_DATA, REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_LATENCY_BAR_CHARTS_DATA, ]; + +export const getRateOverTimeWidgetData = ( + domainName: string, + endPointName: string, + filters: IBuilderQuery['filters'], +): Widgets => { + const { endpoint, port } = extractPortAndEndpoint(endPointName); + const legend = `${ + port !== '-' && port !== 'n/a' ? `${port}:` : '' + }${endpoint}`; + return getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Rate Over Time', + description: 'Rate over time.', + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.String, + id: '------false', + isColumn: false, + key: '', + type: '', + }, + aggregateOperator: 'rate', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '3c76fe0b', + key: { + dataType: DataTypes.String, + id: 'net.peer.name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'net.peer.name', + type: 'tag', + }, + op: '=', + value: domainName, + }, + { + id: '30710f04', + key: { + dataType: DataTypes.String, + id: 'http.url--string--tag--false', + isColumn: false, + isJSON: false, + key: 'http.url', + type: 'tag', + }, + op: '=', + value: endPointName, + }, + ...filters.items, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'http.url--string--tag--false', + isColumn: false, + isJSON: false, + key: 'http.url', + type: 'tag', + }, + ], + having: [], + legend, + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'rate', + }, + ], + yAxisUnit: 'ops/s', + }), + ); +}; + +export const getLatencyOverTimeWidgetData = ( + domainName: string, + endPointName: string, + filters: IBuilderQuery['filters'], +): Widgets => { + const { endpoint, port } = extractPortAndEndpoint(endPointName); + const legend = `${port}:${endpoint}`; + return getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Latency Over Time', + description: 'Latency over time.', + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'duration_nano--float64----true', + isColumn: true, + isJSON: false, + key: 'duration_nano', + type: '', + }, + aggregateOperator: 'p99', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '63adb3ff', + key: { + dataType: DataTypes.String, + id: 'net.peer.name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'net.peer.name', + type: 'tag', + }, + op: '=', + value: domainName, + }, + { + id: '50142500', + key: { + dataType: DataTypes.String, + id: 'http.url--string--tag--false', + isColumn: false, + isJSON: false, + key: 'http.url', + type: 'tag', + }, + op: '=', + value: endPointName, + }, + ...filters.items, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'http.url--string--tag--false', + isColumn: false, + isJSON: false, + key: 'http.url', + type: 'tag', + }, + ], + having: [], + legend, + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'p99', + }, + ], + yAxisUnit: 'ns', + }), + ); +}; + +/** + * Helper function to get the start and end status codes from a status code range string + * @param value Status code range string (e.g. '200-299') or boolean + * @returns Tuple of [startStatusCode, endStatusCode] as strings + */ +const getStartAndEndStatusCode = ( + value: string | boolean, +): [string, string] => { + if (!value) { + return ['', '']; + } + + switch (value) { + case '100-199': + return ['100', '199']; + case '200-299': + return ['200', '299']; + case '300-399': + return ['300', '399']; + case '400-499': + return ['400', '499']; + case '500-599': + return ['500', '599']; + default: + return ['', '']; + } +}; + +/** + * Creates filter items for bar chart based on group by fields and request data + * Used specifically for filtering status code ranges in bar charts + * @param groupBy Array of group by fields to create filters for + * @param requestData Data from graph click containing values to filter on + * @returns Array of TagFilterItems with >= and < operators for status code ranges + */ +export const createGroupByFiltersForBarChart = ( + groupBy: BaseAutocompleteData[], + requestData: GraphClickMetaData, +): TagFilterItem[] => + groupBy + .map((gb) => { + const value = requestData[gb.key]; + const [startStatusCode, endStatusCode] = getStartAndEndStatusCode(value); + return value + ? [ + { + id: v4(), + key: gb, + op: '>=', + value: startStatusCode, + }, + { + id: v4(), + key: gb, + op: '<=', + value: endStatusCode, + }, + ] + : []; + }) + .flat(); + +export const getCustomFiltersForBarChart = ( + metric: + | { + [key: string]: string; + } + | undefined, +): TagFilterItem[] => { + if (!metric?.response_status_code) { + return []; + } + const [startStatusCode, endStatusCode] = getStartAndEndStatusCode( + metric.response_status_code, + ); + return [ + { + id: v4(), + key: { + dataType: DataTypes.String, + id: 'response_status_code--string--tag--false', + isColumn: false, + isJSON: false, + key: 'response_status_code', + type: 'tag', + }, + op: '>=', + value: startStatusCode, + }, + { + id: v4(), + key: { + dataType: DataTypes.String, + id: 'response_status_code--string--tag--false', + isColumn: false, + isJSON: false, + key: 'response_status_code', + type: 'tag', + }, + op: '<=', + value: endStatusCode, + }, + ]; +}; diff --git a/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx b/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx index 03e01c01c2..9a87c228c0 100644 --- a/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx @@ -49,6 +49,7 @@ function FullView({ isDependedDataLoaded = false, onToggleModelHandler, onClickHandler, + customOnDragSelect, setCurrentGraphRef, }: FullViewProps): JSX.Element { const { safeNavigate } = useSafeNavigate(); @@ -252,7 +253,7 @@ function FullView({ onToggleModelHandler={onToggleModelHandler} setGraphVisibility={setGraphsVisibilityStates} graphVisibility={graphsVisibilityStates} - onDragSelect={onDragSelect} + onDragSelect={customOnDragSelect ?? onDragSelect} tableProcessedDataRef={tableProcessedDataRef} searchTerm={searchTerm} onClickHandler={onClickHandler} diff --git a/frontend/src/container/GridCardLayout/GridCard/FullView/types.ts b/frontend/src/container/GridCardLayout/GridCard/FullView/types.ts index c75129a275..cc90a02ee0 100644 --- a/frontend/src/container/GridCardLayout/GridCard/FullView/types.ts +++ b/frontend/src/container/GridCardLayout/GridCard/FullView/types.ts @@ -50,6 +50,7 @@ export interface FullViewProps { widget: Widgets; fullViewOptions?: boolean; onClickHandler?: OnClickPluginOpts['onClick']; + customOnDragSelect?: (start: number, end: number) => void; name: string; tableProcessedDataRef: MutableRefObject; version?: string; diff --git a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx index 26446c9e6a..ada8f24b70 100644 --- a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx @@ -50,6 +50,7 @@ function WidgetGraphComponent({ setRequestData, onClickHandler, onDragSelect, + customOnDragSelect, customTooltipElement, openTracesButton, onOpenTraceBtnClick, @@ -327,6 +328,7 @@ function WidgetGraphComponent({ onToggleModelHandler={onToggleModelHandler} tableProcessedDataRef={tableProcessedDataRef} onClickHandler={onClickHandler ?? graphClickHandler} + customOnDragSelect={customOnDragSelect} setCurrentGraphRef={setCurrentGraphRef} /> diff --git a/frontend/src/container/GridCardLayout/GridCard/index.tsx b/frontend/src/container/GridCardLayout/GridCard/index.tsx index edc5b24527..add29fa1b0 100644 --- a/frontend/src/container/GridCardLayout/GridCard/index.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/index.tsx @@ -36,6 +36,7 @@ function GridCardGraph({ version, onClickHandler, onDragSelect, + customOnDragSelect, customTooltipElement, dataAvailable, getGraphData, @@ -272,6 +273,7 @@ function GridCardGraph({ setRequestData={setRequestData} onClickHandler={onClickHandler} onDragSelect={onDragSelect} + customOnDragSelect={customOnDragSelect} customTooltipElement={customTooltipElement} openTracesButton={openTracesButton} onOpenTraceBtnClick={onOpenTraceBtnClick} diff --git a/frontend/src/container/GridCardLayout/GridCard/types.ts b/frontend/src/container/GridCardLayout/GridCard/types.ts index 199c972e6d..c943da6e7a 100644 --- a/frontend/src/container/GridCardLayout/GridCard/types.ts +++ b/frontend/src/container/GridCardLayout/GridCard/types.ts @@ -33,6 +33,7 @@ export interface WidgetGraphComponentProps { setRequestData?: Dispatch>; onClickHandler?: OnClickPluginOpts['onClick']; onDragSelect: (start: number, end: number) => void; + customOnDragSelect?: (start: number, end: number) => void; customTooltipElement?: HTMLDivElement; openTracesButton?: boolean; onOpenTraceBtnClick?: (record: RowData) => void; @@ -49,6 +50,7 @@ export interface GridCardGraphProps { variables?: Dashboard['data']['variables']; version?: string; onDragSelect: (start: number, end: number) => void; + customOnDragSelect?: (start: number, end: number) => void; customTooltipElement?: HTMLDivElement; dataAvailable?: (isDataAvailable: boolean) => void; getGraphData?: (graphData?: MetricRangePayloadProps['data']) => void; diff --git a/frontend/src/container/GridCardLayout/GridCard/utils.ts b/frontend/src/container/GridCardLayout/GridCard/utils.ts index 2d2041b604..48f443451c 100644 --- a/frontend/src/container/GridCardLayout/GridCard/utils.ts +++ b/frontend/src/container/GridCardLayout/GridCard/utils.ts @@ -178,6 +178,7 @@ interface HandleGraphClickParams { navigateToExplorer: (props: NavigateToExplorerProps) => void; notifications: NotificationInstance; graphClick: (props: GraphClickProps) => void; + customFilters?: TagFilterItem[]; } export const handleGraphClick = async ({ @@ -192,6 +193,7 @@ export const handleGraphClick = async ({ navigateToExplorer, notifications, graphClick, + customFilters, }: HandleGraphClickParams): Promise => { const { stepInterval } = widget?.query?.builder?.queryData?.[0] ?? {}; @@ -221,7 +223,7 @@ export const handleGraphClick = async ({ }: ${key}`, onClick: (): void => navigateToExplorer({ - filters: result[key].filters, + filters: [...result[key].filters, ...(customFilters || [])], dataSource: result[key].dataSource as DataSource, startTime: xValue, endTime: xValue + (stepInterval ?? 60), diff --git a/frontend/src/container/GridCardLayout/useNavigateToExplorerPages.ts b/frontend/src/container/GridCardLayout/useNavigateToExplorerPages.ts index 5fbee873e0..f6ee23815d 100644 --- a/frontend/src/container/GridCardLayout/useNavigateToExplorerPages.ts +++ b/frontend/src/container/GridCardLayout/useNavigateToExplorerPages.ts @@ -12,7 +12,7 @@ import { v4 } from 'uuid'; import { extractQueryNamesFromExpression } from './utils'; -type GraphClickMetaData = { +export type GraphClickMetaData = { [key: string]: string | boolean; queryName: string; inFocusOrNot: boolean; diff --git a/frontend/src/container/SideNav/SideNav.tsx b/frontend/src/container/SideNav/SideNav.tsx index 8b3f9c19b0..1bb0b8eec8 100644 --- a/frontend/src/container/SideNav/SideNav.tsx +++ b/frontend/src/container/SideNav/SideNav.tsx @@ -279,6 +279,17 @@ function SideNav(): JSX.Element { let updatedUserManagementItems: UserManagementMenuItems[] = [ manageLicenseMenuItem, ]; + + const isApiMonitoringEnabled = featureFlags?.find( + (flag) => flag.name === FeatureKeys.THIRD_PARTY_API, + )?.active; + + if (!isApiMonitoringEnabled) { + updatedMenuItems = updatedMenuItems.filter( + (item) => item.key !== ROUTES.API_MONITORING, + ); + } + if (isCloudUserVal || isEECloudUserVal) { const isOnboardingEnabled = featureFlags?.find((feature) => feature.name === FeatureKeys.ONBOARDING) diff --git a/frontend/src/container/SideNav/menuItems.tsx b/frontend/src/container/SideNav/menuItems.tsx index f713b0a294..996b7ae8cf 100644 --- a/frontend/src/container/SideNav/menuItems.tsx +++ b/frontend/src/container/SideNav/menuItems.tsx @@ -128,6 +128,7 @@ const menuItems: SidebarItem[] = [ key: ROUTES.API_MONITORING, label: 'API Monitoring', icon: , + isNew: true, }, { key: ROUTES.LIST_ALL_ALERT,