mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 20:38:59 +08:00
feat: added context redirection from panels to explorer pages (#7141)
* feat: added context redirection from panels to explorer pages * feat: added graph coordinate - context redirection * feat: fixed tooltip overlapping the button * feat: code fix * feat: removed unneccesary comment * feat: added logic to resolve variables * feat: added better logic to handle specific and panel redirection using query * feat: added multi query support by datasource to panels redirction * feat: fixing createbutton display logic * feat: added logic and ui for specific line redirection * feat: added logic to compute query with groupby * feat: code fix and added aysnc await * feat: added context redirection to fullview and edit view panels (#7252) * feat: added context redirection to fullview and edit view panels * feat: restricted redirection query to have only one query * feat: added is buttonEnabled logic of graphs * feat: code cleanup * feat: for one query removed the queryname from onclick button * feat: removed redirection option from action menu * feat: redesign the format api flow to avoid delay in clickbutton appearance * feat: updated the create filter logic for groupBys * feat: handled the error on format api
This commit is contained in:
parent
e04e58d8b3
commit
0320285a25
@ -10,10 +10,11 @@ import { X } from 'lucide-react';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import CeleryTaskGraph from '../CeleryTaskGraph/CeleryTaskGraph';
|
import CeleryTaskGraph from '../CeleryTaskGraph/CeleryTaskGraph';
|
||||||
import { createFiltersFromData } from '../CeleryUtils';
|
import { createFiltersFromData } from '../CeleryUtils';
|
||||||
import { useNavigateToTraces } from '../useNavigateToTraces';
|
import { useNavigateToExplorer } from '../useNavigateToExplorer';
|
||||||
|
|
||||||
export type CeleryTaskData = {
|
export type CeleryTaskData = {
|
||||||
entity: string;
|
entity: string;
|
||||||
@ -55,7 +56,7 @@ export default function CeleryTaskDetail({
|
|||||||
const startTime = taskData.timeRange[0];
|
const startTime = taskData.timeRange[0];
|
||||||
const endTime = taskData.timeRange[1];
|
const endTime = taskData.timeRange[1];
|
||||||
|
|
||||||
const navigateToTrace = useNavigateToTraces();
|
const navigateToExplorer = useNavigateToExplorer();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
@ -105,7 +106,12 @@ export default function CeleryTaskDetail({
|
|||||||
endTime,
|
endTime,
|
||||||
source: widgetData.title,
|
source: widgetData.title,
|
||||||
});
|
});
|
||||||
navigateToTrace(filters, startTime, endTime);
|
navigateToExplorer({
|
||||||
|
filters,
|
||||||
|
dataSource: DataSource.TRACES,
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
start={startTime}
|
start={startTime}
|
||||||
end={endTime}
|
end={endTime}
|
||||||
|
@ -185,8 +185,15 @@ function CeleryTaskBar({
|
|||||||
headerMenuList={[...ViewMenuAction]}
|
headerMenuList={[...ViewMenuAction]}
|
||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
isQueryEnabled={queryEnabled}
|
isQueryEnabled={queryEnabled}
|
||||||
onClickHandler={(...args): void =>
|
onClickHandler={(xValue, yValue, mouseX, mouseY, data): void =>
|
||||||
onGraphClick(celerySlowestTasksTableWidgetData, ...args)
|
onGraphClick(
|
||||||
|
celerySlowestTasksTableWidgetData,
|
||||||
|
xValue,
|
||||||
|
yValue,
|
||||||
|
mouseX,
|
||||||
|
mouseY,
|
||||||
|
data,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
customSeries={getCustomSeries}
|
customSeries={getCustomSeries}
|
||||||
dataAvailable={checkIfDataExists}
|
dataAvailable={checkIfDataExists}
|
||||||
@ -198,8 +205,15 @@ function CeleryTaskBar({
|
|||||||
headerMenuList={[...ViewMenuAction]}
|
headerMenuList={[...ViewMenuAction]}
|
||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
isQueryEnabled={queryEnabled}
|
isQueryEnabled={queryEnabled}
|
||||||
onClickHandler={(...args): void =>
|
onClickHandler={(xValue, yValue, mouseX, mouseY, data): void =>
|
||||||
onGraphClick(celeryFailedTasksTableWidgetData, ...args)
|
onGraphClick(
|
||||||
|
celeryFailedTasksTableWidgetData,
|
||||||
|
xValue,
|
||||||
|
yValue,
|
||||||
|
mouseX,
|
||||||
|
mouseY,
|
||||||
|
data,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
customSeries={getCustomSeries}
|
customSeries={getCustomSeries}
|
||||||
/>
|
/>
|
||||||
@ -210,8 +224,15 @@ function CeleryTaskBar({
|
|||||||
headerMenuList={[...ViewMenuAction]}
|
headerMenuList={[...ViewMenuAction]}
|
||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
isQueryEnabled={queryEnabled}
|
isQueryEnabled={queryEnabled}
|
||||||
onClickHandler={(...args): void =>
|
onClickHandler={(xValue, yValue, mouseX, mouseY, data): void =>
|
||||||
onGraphClick(celeryRetryTasksTableWidgetData, ...args)
|
onGraphClick(
|
||||||
|
celeryRetryTasksTableWidgetData,
|
||||||
|
xValue,
|
||||||
|
yValue,
|
||||||
|
mouseX,
|
||||||
|
mouseY,
|
||||||
|
data,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
customSeries={getCustomSeries}
|
customSeries={getCustomSeries}
|
||||||
/>
|
/>
|
||||||
@ -222,8 +243,15 @@ function CeleryTaskBar({
|
|||||||
headerMenuList={[...ViewMenuAction]}
|
headerMenuList={[...ViewMenuAction]}
|
||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
isQueryEnabled={queryEnabled}
|
isQueryEnabled={queryEnabled}
|
||||||
onClickHandler={(...args): void =>
|
onClickHandler={(xValue, yValue, mouseX, mouseY, data): void =>
|
||||||
onGraphClick(celerySuccessTasksTableWidgetData, ...args)
|
onGraphClick(
|
||||||
|
celerySuccessTasksTableWidgetData,
|
||||||
|
xValue,
|
||||||
|
yValue,
|
||||||
|
mouseX,
|
||||||
|
mouseY,
|
||||||
|
data,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
customSeries={getCustomSeries}
|
customSeries={getCustomSeries}
|
||||||
/>
|
/>
|
||||||
|
@ -18,6 +18,7 @@ import { useDispatch, useSelector } from 'react-redux';
|
|||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
import { UpdateTimeInterval } from 'store/actions';
|
import { UpdateTimeInterval } from 'store/actions';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -25,7 +26,7 @@ import {
|
|||||||
createFiltersFromData,
|
createFiltersFromData,
|
||||||
getFiltersFromQueryParams,
|
getFiltersFromQueryParams,
|
||||||
} from '../CeleryUtils';
|
} from '../CeleryUtils';
|
||||||
import { useNavigateToTraces } from '../useNavigateToTraces';
|
import { useNavigateToExplorer } from '../useNavigateToExplorer';
|
||||||
import { celeryTaskLatencyWidgetData } from './CeleryTaskGraphUtils';
|
import { celeryTaskLatencyWidgetData } from './CeleryTaskGraphUtils';
|
||||||
|
|
||||||
interface TabData {
|
interface TabData {
|
||||||
@ -145,15 +146,21 @@ function CeleryTaskLatencyGraph({
|
|||||||
[handleSetTimeStamp],
|
[handleSetTimeStamp],
|
||||||
);
|
);
|
||||||
|
|
||||||
const navigateToTraces = useNavigateToTraces();
|
const navigateToExplorer = useNavigateToExplorer();
|
||||||
|
|
||||||
const goToTraces = useCallback(() => {
|
const goToTraces = useCallback(() => {
|
||||||
const { start, end } = getStartAndEndTimesInMilliseconds(selectedTimeStamp);
|
const { start, end } = getStartAndEndTimesInMilliseconds(selectedTimeStamp);
|
||||||
const filters = createFiltersFromData({
|
const filters = createFiltersFromData({
|
||||||
[entityData?.entity as string]: entityData?.value,
|
[entityData?.entity as string]: entityData?.value,
|
||||||
});
|
});
|
||||||
navigateToTraces(filters, start, end, true);
|
navigateToExplorer({
|
||||||
}, [entityData, navigateToTraces, selectedTimeStamp]);
|
filters,
|
||||||
|
dataSource: DataSource.TRACES,
|
||||||
|
startTime: start,
|
||||||
|
endTime: end,
|
||||||
|
sameTab: true,
|
||||||
|
});
|
||||||
|
}, [entityData, navigateToExplorer, selectedTimeStamp]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
|
121
frontend/src/components/CeleryTask/useNavigateToExplorer.ts
Normal file
121
frontend/src/components/CeleryTask/useNavigateToExplorer.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import { QueryParams } from 'constants/query';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import ROUTES from 'constants/routes';
|
||||||
|
import useUpdatedQuery from 'container/GridCardLayout/useResolveQuery';
|
||||||
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
|
export interface NavigateToExplorerProps {
|
||||||
|
filters: TagFilterItem[];
|
||||||
|
dataSource: DataSource;
|
||||||
|
startTime?: number;
|
||||||
|
endTime?: number;
|
||||||
|
sameTab?: boolean;
|
||||||
|
shouldResolveQuery?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useNavigateToExplorer(): (
|
||||||
|
props: NavigateToExplorerProps,
|
||||||
|
) => void {
|
||||||
|
const { currentQuery } = useQueryBuilder();
|
||||||
|
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||||
|
(state) => state.globalTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
const prepareQuery = useCallback(
|
||||||
|
(selectedFilters: TagFilterItem[], dataSource: DataSource): Query => ({
|
||||||
|
...currentQuery,
|
||||||
|
builder: {
|
||||||
|
...currentQuery.builder,
|
||||||
|
queryData: currentQuery.builder.queryData
|
||||||
|
.map((item) => ({
|
||||||
|
...item,
|
||||||
|
dataSource,
|
||||||
|
aggregateOperator: MetricAggregateOperator.NOOP,
|
||||||
|
filters: {
|
||||||
|
...item.filters,
|
||||||
|
items: selectedFilters,
|
||||||
|
},
|
||||||
|
groupBy: [],
|
||||||
|
disabled: false,
|
||||||
|
}))
|
||||||
|
.slice(0, 1),
|
||||||
|
queryFormulas: [],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[currentQuery],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getUpdatedQuery } = useUpdatedQuery();
|
||||||
|
const { selectedDashboard } = useDashboard();
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async (props: NavigateToExplorerProps): Promise<void> => {
|
||||||
|
const {
|
||||||
|
filters,
|
||||||
|
dataSource,
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
sameTab,
|
||||||
|
shouldResolveQuery,
|
||||||
|
} = props;
|
||||||
|
const urlParams = new URLSearchParams();
|
||||||
|
if (startTime && endTime) {
|
||||||
|
urlParams.set(QueryParams.startTime, startTime.toString());
|
||||||
|
urlParams.set(QueryParams.endTime, endTime.toString());
|
||||||
|
} else {
|
||||||
|
urlParams.set(QueryParams.startTime, (minTime / 1000000).toString());
|
||||||
|
urlParams.set(QueryParams.endTime, (maxTime / 1000000).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
let preparedQuery = prepareQuery(filters, dataSource);
|
||||||
|
|
||||||
|
if (shouldResolveQuery) {
|
||||||
|
await getUpdatedQuery({
|
||||||
|
widgetConfig: {
|
||||||
|
query: preparedQuery,
|
||||||
|
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||||
|
timePreferance: 'GLOBAL_TIME',
|
||||||
|
},
|
||||||
|
selectedDashboard,
|
||||||
|
})
|
||||||
|
.then((query) => {
|
||||||
|
preparedQuery = query;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
notifications.error({
|
||||||
|
message: 'Unable to resolve variables',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const JSONCompositeQuery = encodeURIComponent(JSON.stringify(preparedQuery));
|
||||||
|
|
||||||
|
const basePath =
|
||||||
|
dataSource === DataSource.TRACES
|
||||||
|
? ROUTES.TRACES_EXPLORER
|
||||||
|
: ROUTES.LOGS_EXPLORER;
|
||||||
|
const newExplorerPath = `${basePath}?${urlParams.toString()}&${
|
||||||
|
QueryParams.compositeQuery
|
||||||
|
}=${JSONCompositeQuery}`;
|
||||||
|
|
||||||
|
window.open(newExplorerPath, sameTab ? '_self' : '_blank');
|
||||||
|
},
|
||||||
|
[
|
||||||
|
prepareQuery,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
getUpdatedQuery,
|
||||||
|
selectedDashboard,
|
||||||
|
notifications,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
@ -1,71 +0,0 @@
|
|||||||
import { QueryParams } from 'constants/query';
|
|
||||||
import ROUTES from 'constants/routes';
|
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
|
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
|
||||||
|
|
||||||
export function useNavigateToTraces(): (
|
|
||||||
filters: TagFilterItem[],
|
|
||||||
startTime?: number,
|
|
||||||
endTime?: number,
|
|
||||||
sameTab?: boolean,
|
|
||||||
) => void {
|
|
||||||
const { currentQuery } = useQueryBuilder();
|
|
||||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
|
||||||
(state) => state.globalTime,
|
|
||||||
);
|
|
||||||
|
|
||||||
const prepareQuery = useCallback(
|
|
||||||
(selectedFilters: TagFilterItem[]): Query => ({
|
|
||||||
...currentQuery,
|
|
||||||
builder: {
|
|
||||||
...currentQuery.builder,
|
|
||||||
queryData: currentQuery.builder.queryData.map((item) => ({
|
|
||||||
...item,
|
|
||||||
dataSource: DataSource.TRACES,
|
|
||||||
aggregateOperator: MetricAggregateOperator.NOOP,
|
|
||||||
filters: {
|
|
||||||
...item.filters,
|
|
||||||
items: selectedFilters,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[currentQuery],
|
|
||||||
);
|
|
||||||
|
|
||||||
return useCallback(
|
|
||||||
(
|
|
||||||
filters: TagFilterItem[],
|
|
||||||
startTime?: number,
|
|
||||||
endTime?: number,
|
|
||||||
sameTab?: boolean,
|
|
||||||
): void => {
|
|
||||||
const urlParams = new URLSearchParams();
|
|
||||||
if (startTime && endTime) {
|
|
||||||
urlParams.set(QueryParams.startTime, startTime.toString());
|
|
||||||
urlParams.set(QueryParams.endTime, endTime.toString());
|
|
||||||
} else {
|
|
||||||
urlParams.set(QueryParams.startTime, (minTime / 1000000).toString());
|
|
||||||
urlParams.set(QueryParams.endTime, (maxTime / 1000000).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
const JSONCompositeQuery = encodeURIComponent(
|
|
||||||
JSON.stringify(prepareQuery(filters)),
|
|
||||||
);
|
|
||||||
|
|
||||||
const newTraceExplorerPath = `${
|
|
||||||
ROUTES.TRACES_EXPLORER
|
|
||||||
}?${urlParams.toString()}&${
|
|
||||||
QueryParams.compositeQuery
|
|
||||||
}=${JSONCompositeQuery}`;
|
|
||||||
|
|
||||||
window.open(newTraceExplorerPath, sameTab ? '_self' : '_blank');
|
|
||||||
},
|
|
||||||
[minTime, maxTime, prepareQuery],
|
|
||||||
);
|
|
||||||
}
|
|
@ -48,6 +48,8 @@ function FullView({
|
|||||||
tableProcessedDataRef,
|
tableProcessedDataRef,
|
||||||
isDependedDataLoaded = false,
|
isDependedDataLoaded = false,
|
||||||
onToggleModelHandler,
|
onToggleModelHandler,
|
||||||
|
onClickHandler,
|
||||||
|
setCurrentGraphRef,
|
||||||
}: FullViewProps): JSX.Element {
|
}: FullViewProps): JSX.Element {
|
||||||
const { safeNavigate } = useSafeNavigate();
|
const { safeNavigate } = useSafeNavigate();
|
||||||
const { selectedTime: globalSelectedTime } = useSelector<
|
const { selectedTime: globalSelectedTime } = useSelector<
|
||||||
@ -60,6 +62,10 @@ function FullView({
|
|||||||
|
|
||||||
const fullViewRef = useRef<HTMLDivElement>(null);
|
const fullViewRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentGraphRef(fullViewRef);
|
||||||
|
}, [setCurrentGraphRef]);
|
||||||
|
|
||||||
const { selectedDashboard, isDashboardLocked } = useDashboard();
|
const { selectedDashboard, isDashboardLocked } = useDashboard();
|
||||||
|
|
||||||
const getSelectedTime = useCallback(
|
const getSelectedTime = useCallback(
|
||||||
@ -249,6 +255,7 @@ function FullView({
|
|||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
tableProcessedDataRef={tableProcessedDataRef}
|
tableProcessedDataRef={tableProcessedDataRef}
|
||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
|
onClickHandler={onClickHandler}
|
||||||
/>
|
/>
|
||||||
</GraphContainer>
|
</GraphContainer>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,7 @@ import { UplotProps } from 'components/Uplot/Uplot';
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||||
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||||
import { Dispatch, MutableRefObject, SetStateAction } from 'react';
|
import { Dispatch, MutableRefObject, RefObject, SetStateAction } from 'react';
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
import uPlot from 'uplot';
|
import uPlot from 'uplot';
|
||||||
|
|
||||||
@ -57,6 +57,7 @@ export interface FullViewProps {
|
|||||||
yAxisUnit?: string;
|
yAxisUnit?: string;
|
||||||
isDependedDataLoaded?: boolean;
|
isDependedDataLoaded?: boolean;
|
||||||
onToggleModelHandler?: GraphManagerProps['onToggleModelHandler'];
|
onToggleModelHandler?: GraphManagerProps['onToggleModelHandler'];
|
||||||
|
setCurrentGraphRef: Dispatch<SetStateAction<RefObject<HTMLDivElement> | null>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GraphManagerProps extends UplotProps {
|
export interface GraphManagerProps extends UplotProps {
|
||||||
|
@ -2,6 +2,7 @@ import '../GridCardLayout.styles.scss';
|
|||||||
|
|
||||||
import { Skeleton, Typography } from 'antd';
|
import { Skeleton, Typography } from 'antd';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
|
||||||
import { ToggleGraphProps } from 'components/Graph/types';
|
import { ToggleGraphProps } from 'components/Graph/types';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
@ -17,6 +18,7 @@ import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
|||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
import {
|
import {
|
||||||
Dispatch,
|
Dispatch,
|
||||||
|
RefObject,
|
||||||
SetStateAction,
|
SetStateAction,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
@ -25,13 +27,16 @@ import {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import { useGraphClickToShowButton } from '../useGraphClickToShowButton';
|
||||||
|
import useNavigateToExplorerPages from '../useNavigateToExplorerPages';
|
||||||
import WidgetHeader from '../WidgetHeader';
|
import WidgetHeader from '../WidgetHeader';
|
||||||
import FullView from './FullView';
|
import FullView from './FullView';
|
||||||
import { Modal } from './styles';
|
import { Modal } from './styles';
|
||||||
import { WidgetGraphComponentProps } from './types';
|
import { WidgetGraphComponentProps } from './types';
|
||||||
import { getLocalStorageGraphVisibilityState } from './utils';
|
import { getLocalStorageGraphVisibilityState, handleGraphClick } from './utils';
|
||||||
|
|
||||||
function WidgetGraphComponent({
|
function WidgetGraphComponent({
|
||||||
widget,
|
widget,
|
||||||
@ -67,6 +72,11 @@ function WidgetGraphComponent({
|
|||||||
);
|
);
|
||||||
const graphRef = useRef<HTMLDivElement>(null);
|
const graphRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const [
|
||||||
|
currentGraphRef,
|
||||||
|
setCurrentGraphRef,
|
||||||
|
] = useState<RefObject<HTMLDivElement> | null>(graphRef);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!lineChartRef.current) return;
|
if (!lineChartRef.current) return;
|
||||||
|
|
||||||
@ -78,6 +88,8 @@ function WidgetGraphComponent({
|
|||||||
|
|
||||||
const tableProcessedDataRef = useRef<RowData[]>([]);
|
const tableProcessedDataRef = useRef<RowData[]>([]);
|
||||||
|
|
||||||
|
const navigateToExplorerPages = useNavigateToExplorerPages();
|
||||||
|
|
||||||
const { setLayouts, selectedDashboard, setSelectedDashboard } = useDashboard();
|
const { setLayouts, selectedDashboard, setSelectedDashboard } = useDashboard();
|
||||||
|
|
||||||
const onToggleModal = useCallback(
|
const onToggleModal = useCallback(
|
||||||
@ -230,6 +242,40 @@ function WidgetGraphComponent({
|
|||||||
|
|
||||||
const [searchTerm, setSearchTerm] = useState<string>('');
|
const [searchTerm, setSearchTerm] = useState<string>('');
|
||||||
|
|
||||||
|
const graphClick = useGraphClickToShowButton({
|
||||||
|
graphRef: currentGraphRef?.current ? currentGraphRef : graphRef,
|
||||||
|
isButtonEnabled: (widget?.query?.builder?.queryData ?? []).some(
|
||||||
|
(q) =>
|
||||||
|
q.dataSource === DataSource.TRACES || q.dataSource === DataSource.LOGS,
|
||||||
|
),
|
||||||
|
buttonClassName: 'view-onclick-show-button',
|
||||||
|
});
|
||||||
|
|
||||||
|
const navigateToExplorer = useNavigateToExplorer();
|
||||||
|
|
||||||
|
const graphClickHandler = (
|
||||||
|
xValue: number,
|
||||||
|
yValue: number,
|
||||||
|
mouseX: number,
|
||||||
|
mouseY: number,
|
||||||
|
metric?: { [key: string]: string },
|
||||||
|
queryData?: { queryName: string; inFocusOrNot: boolean },
|
||||||
|
): void => {
|
||||||
|
handleGraphClick({
|
||||||
|
xValue,
|
||||||
|
yValue,
|
||||||
|
mouseX,
|
||||||
|
mouseY,
|
||||||
|
metric,
|
||||||
|
queryData,
|
||||||
|
widget,
|
||||||
|
navigateToExplorerPages,
|
||||||
|
navigateToExplorer,
|
||||||
|
notifications,
|
||||||
|
graphClick,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -280,6 +326,8 @@ function WidgetGraphComponent({
|
|||||||
yAxisUnit={widget.yAxisUnit}
|
yAxisUnit={widget.yAxisUnit}
|
||||||
onToggleModelHandler={onToggleModelHandler}
|
onToggleModelHandler={onToggleModelHandler}
|
||||||
tableProcessedDataRef={tableProcessedDataRef}
|
tableProcessedDataRef={tableProcessedDataRef}
|
||||||
|
onClickHandler={onClickHandler ?? graphClickHandler}
|
||||||
|
setCurrentGraphRef={setCurrentGraphRef}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
@ -322,7 +370,7 @@ function WidgetGraphComponent({
|
|||||||
setRequestData={setRequestData}
|
setRequestData={setRequestData}
|
||||||
setGraphVisibility={setGraphVisibility}
|
setGraphVisibility={setGraphVisibility}
|
||||||
graphVisibility={graphVisibility}
|
graphVisibility={graphVisibility}
|
||||||
onClickHandler={onClickHandler}
|
onClickHandler={onClickHandler ?? graphClickHandler}
|
||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
tableProcessedDataRef={tableProcessedDataRef}
|
tableProcessedDataRef={tableProcessedDataRef}
|
||||||
customTooltipElement={customTooltipElement}
|
customTooltipElement={customTooltipElement}
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
/* eslint-disable sonarjs/cognitive-complexity */
|
/* eslint-disable sonarjs/cognitive-complexity */
|
||||||
|
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||||
|
import { NavigateToExplorerProps } from 'components/CeleryTask/useNavigateToExplorer';
|
||||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import getLabelName from 'lib/getLabelName';
|
import getLabelName from 'lib/getLabelName';
|
||||||
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
|
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { QueryData } from 'types/api/widgets/getQuery';
|
import { QueryData } from 'types/api/widgets/getQuery';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import { GraphClickProps } from '../useGraphClickToShowButton';
|
||||||
|
import { NavigateToExplorerPagesProps } from '../useNavigateToExplorerPages';
|
||||||
import { LegendEntryProps } from './FullView/types';
|
import { LegendEntryProps } from './FullView/types';
|
||||||
import {
|
import {
|
||||||
showAllDataSet,
|
showAllDataSet,
|
||||||
@ -151,3 +158,83 @@ export const isDataAvailableByPanelType = (
|
|||||||
|
|
||||||
return Boolean(getPanelData()?.length);
|
return Boolean(getPanelData()?.length);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface HandleGraphClickParams {
|
||||||
|
xValue: number;
|
||||||
|
yValue: number;
|
||||||
|
mouseX: number;
|
||||||
|
mouseY: number;
|
||||||
|
metric?: { [key: string]: string };
|
||||||
|
queryData?: { queryName: string; inFocusOrNot: boolean };
|
||||||
|
widget: Widgets;
|
||||||
|
navigateToExplorerPages: (
|
||||||
|
props: NavigateToExplorerPagesProps,
|
||||||
|
) => Promise<{
|
||||||
|
[queryName: string]: {
|
||||||
|
filters: TagFilterItem[];
|
||||||
|
dataSource?: string;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
navigateToExplorer: (props: NavigateToExplorerProps) => void;
|
||||||
|
notifications: NotificationInstance;
|
||||||
|
graphClick: (props: GraphClickProps) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handleGraphClick = async ({
|
||||||
|
xValue,
|
||||||
|
yValue,
|
||||||
|
mouseX,
|
||||||
|
mouseY,
|
||||||
|
metric,
|
||||||
|
queryData,
|
||||||
|
widget,
|
||||||
|
navigateToExplorerPages,
|
||||||
|
navigateToExplorer,
|
||||||
|
notifications,
|
||||||
|
graphClick,
|
||||||
|
}: HandleGraphClickParams): Promise<void> => {
|
||||||
|
const { stepInterval } = widget?.query?.builder?.queryData?.[0] ?? {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await navigateToExplorerPages({
|
||||||
|
widget,
|
||||||
|
requestData: {
|
||||||
|
...metric,
|
||||||
|
queryName: queryData?.queryName || '',
|
||||||
|
inFocusOrNot: queryData?.inFocusOrNot || false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const keys = Object.keys(result);
|
||||||
|
const menuItems = keys.map((key) => ({
|
||||||
|
text:
|
||||||
|
keys.length === 1
|
||||||
|
? `View ${
|
||||||
|
(result[key].dataSource as DataSource) === DataSource.TRACES
|
||||||
|
? 'Traces'
|
||||||
|
: 'Logs'
|
||||||
|
}`
|
||||||
|
: `View ${
|
||||||
|
(result[key].dataSource as DataSource) === DataSource.TRACES
|
||||||
|
? 'Traces'
|
||||||
|
: 'Logs'
|
||||||
|
}: ${key}`,
|
||||||
|
onClick: (): void =>
|
||||||
|
navigateToExplorer({
|
||||||
|
filters: result[key].filters,
|
||||||
|
dataSource: result[key].dataSource as DataSource,
|
||||||
|
startTime: xValue,
|
||||||
|
endTime: xValue + (stepInterval ?? 60),
|
||||||
|
shouldResolveQuery: true,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
graphClick({ xValue, yValue, mouseX, mouseY, metric, queryData, menuItems });
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error({
|
||||||
|
message: 'Failed to process graph click',
|
||||||
|
description:
|
||||||
|
error instanceof Error ? error.message : 'Unknown error occurred',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -291,6 +291,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.view-onclick-show-button {
|
||||||
|
position: absolute;
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
border: 2px solid var(--bg-vanilla-300);
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
box-shadow: none;
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 4px;
|
||||||
|
color: var(--bg-ink-100);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
overflow: auto;
|
||||||
|
max-height: 150px;
|
||||||
|
width: max-content;
|
||||||
|
max-width: 200px;
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
padding-right: 12px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--bg-vanilla-200);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.lightMode {
|
.lightMode {
|
||||||
.fullscreen-grid-container {
|
.fullscreen-grid-container {
|
||||||
.react-grid-layout {
|
.react-grid-layout {
|
||||||
@ -374,4 +406,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.view-onclick-show-button {
|
||||||
|
background: var(--bg-ink-400);
|
||||||
|
border-color: var(--bg-ink-300);
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--bg-ink-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,191 @@
|
|||||||
|
import './GridCardLayout.styles.scss';
|
||||||
|
|
||||||
|
import { isUndefined } from 'lodash-es';
|
||||||
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
interface ClickToShowButtonProps {
|
||||||
|
graphRef: React.RefObject<HTMLDivElement>;
|
||||||
|
buttonClassName?: string;
|
||||||
|
isButtonEnabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GraphClickProps {
|
||||||
|
xValue: number;
|
||||||
|
yValue: number;
|
||||||
|
mouseX: number;
|
||||||
|
mouseY: number;
|
||||||
|
metric?: { [key: string]: string };
|
||||||
|
queryData?: { queryName: string; inFocusOrNot: boolean };
|
||||||
|
menuItems?: Array<{
|
||||||
|
text: string;
|
||||||
|
onClick: (
|
||||||
|
xValue: number,
|
||||||
|
yValue: number,
|
||||||
|
mouseX: number,
|
||||||
|
mouseY: number,
|
||||||
|
metric?: { [key: string]: string },
|
||||||
|
queryData?: { queryName: string; inFocusOrNot: boolean },
|
||||||
|
) => void;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGraphClickToShowButton = ({
|
||||||
|
graphRef,
|
||||||
|
buttonClassName = 'view-onclick-show-button',
|
||||||
|
isButtonEnabled = true,
|
||||||
|
}: ClickToShowButtonProps): ((props: GraphClickProps) => void) => {
|
||||||
|
const activeButtonRef = useRef<HTMLButtonElement | HTMLUListElement | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hideTooltips = (): void => {
|
||||||
|
const elements = [
|
||||||
|
{ id: 'overlay', selector: '#overlay' },
|
||||||
|
{ className: 'uplot-tooltip', selector: '.uplot-tooltip' },
|
||||||
|
];
|
||||||
|
|
||||||
|
elements.forEach(({ selector }) => {
|
||||||
|
const element = document.querySelector(selector) as HTMLElement;
|
||||||
|
if (element) {
|
||||||
|
element.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanup = useCallback((): void => {
|
||||||
|
if (activeButtonRef.current) {
|
||||||
|
activeButtonRef.current.remove();
|
||||||
|
activeButtonRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore tooltips
|
||||||
|
['#overlay', '.uplot-tooltip'].forEach((selector) => {
|
||||||
|
const element = document.querySelector(selector) as HTMLElement;
|
||||||
|
if (element) {
|
||||||
|
element.style.display = 'block';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const createMenu = (
|
||||||
|
xValue: number,
|
||||||
|
yValue: number,
|
||||||
|
mouseX: number,
|
||||||
|
mouseY: number,
|
||||||
|
menuItems: Array<{
|
||||||
|
text: string;
|
||||||
|
onClick: (
|
||||||
|
xValue: number,
|
||||||
|
yValue: number,
|
||||||
|
mouseX: number,
|
||||||
|
mouseY: number,
|
||||||
|
metric?: { [key: string]: string },
|
||||||
|
queryData?: { queryName: string; inFocusOrNot: boolean },
|
||||||
|
) => void;
|
||||||
|
}>,
|
||||||
|
metric?: { [key: string]: string },
|
||||||
|
queryData?: { queryName: string; inFocusOrNot: boolean },
|
||||||
|
): void => {
|
||||||
|
const menuList = document.createElement('ul');
|
||||||
|
menuList.className = buttonClassName;
|
||||||
|
menuList.style.position = 'absolute';
|
||||||
|
menuList.style.zIndex = '9999';
|
||||||
|
|
||||||
|
const graphBounds = graphRef.current?.getBoundingClientRect();
|
||||||
|
if (!graphBounds) return;
|
||||||
|
|
||||||
|
graphRef.current?.appendChild(menuList);
|
||||||
|
|
||||||
|
// After appending, get menu dimensions and adjust if needed so it stays within the graph boundaries
|
||||||
|
const menuBounds = menuList.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Calculate position considering menu dimensions
|
||||||
|
let finalLeft = mouseX;
|
||||||
|
let finalTop = mouseY;
|
||||||
|
|
||||||
|
// Adjust horizontal position if menu would overflow
|
||||||
|
if (mouseX + menuBounds.width > graphBounds.width) {
|
||||||
|
finalLeft = mouseX - menuBounds.width;
|
||||||
|
}
|
||||||
|
// Ensure menu doesn't go off the left edge
|
||||||
|
finalLeft = Math.max(0, finalLeft);
|
||||||
|
|
||||||
|
// Adjust vertical position if menu would overflow
|
||||||
|
if (mouseY + menuBounds.height > graphBounds.height) {
|
||||||
|
finalTop = mouseY - menuBounds.height;
|
||||||
|
}
|
||||||
|
// Ensure menu doesn't go off the top edge
|
||||||
|
finalTop = Math.max(0, finalTop);
|
||||||
|
|
||||||
|
menuList.style.left = `${finalLeft}px`;
|
||||||
|
menuList.style.top = `${finalTop}px`;
|
||||||
|
|
||||||
|
// Create a list item for each menu option provided in props
|
||||||
|
menuItems.forEach((item) => {
|
||||||
|
const listItem = document.createElement('li');
|
||||||
|
listItem.textContent = item.text;
|
||||||
|
listItem.className = 'menu-item';
|
||||||
|
// Style the list item as needed (padding, cursor, etc.)
|
||||||
|
listItem.onclick = (e: MouseEvent): void => {
|
||||||
|
e.stopPropagation();
|
||||||
|
// Execute the provided onClick handler for this menu item
|
||||||
|
item.onClick(xValue, yValue, mouseX, mouseY, metric, queryData);
|
||||||
|
cleanup();
|
||||||
|
};
|
||||||
|
menuList.appendChild(listItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
activeButtonRef.current = menuList;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleOutsideClick = (e: MouseEvent): void => {
|
||||||
|
if (!graphRef.current?.contains(e.target as Node)) {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('click', handleOutsideClick);
|
||||||
|
return (): void => {
|
||||||
|
document.removeEventListener('click', handleOutsideClick);
|
||||||
|
cleanup();
|
||||||
|
};
|
||||||
|
}, [cleanup, graphRef]);
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
(props: GraphClickProps) => {
|
||||||
|
cleanup();
|
||||||
|
const {
|
||||||
|
xValue,
|
||||||
|
yValue,
|
||||||
|
mouseX,
|
||||||
|
mouseY,
|
||||||
|
metric,
|
||||||
|
queryData,
|
||||||
|
menuItems,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
if (
|
||||||
|
isButtonEnabled &&
|
||||||
|
!isUndefined(props.xValue) &&
|
||||||
|
props.queryData &&
|
||||||
|
queryData?.inFocusOrNot &&
|
||||||
|
Object.keys(queryData).length > 0
|
||||||
|
) {
|
||||||
|
hideTooltips();
|
||||||
|
// createButton(xValue, yValue, mouseX, mouseY, metric, queryData);
|
||||||
|
createMenu(
|
||||||
|
xValue,
|
||||||
|
yValue,
|
||||||
|
mouseX,
|
||||||
|
mouseY,
|
||||||
|
menuItems || [],
|
||||||
|
metric,
|
||||||
|
queryData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[buttonClassName, graphRef, isButtonEnabled, cleanup],
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,148 @@
|
|||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
import {
|
||||||
|
IBuilderQuery,
|
||||||
|
Query,
|
||||||
|
TagFilterItem,
|
||||||
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import { extractQueryNamesFromExpression } from './utils';
|
||||||
|
|
||||||
|
type GraphClickMetaData = {
|
||||||
|
[key: string]: string | boolean;
|
||||||
|
queryName: string;
|
||||||
|
inFocusOrNot: boolean;
|
||||||
|
};
|
||||||
|
export interface NavigateToExplorerPagesProps {
|
||||||
|
widget: Widgets;
|
||||||
|
requestData?: GraphClickMetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to create group by filters from request data
|
||||||
|
const createGroupByFilters = (
|
||||||
|
groupBy: BaseAutocompleteData[],
|
||||||
|
requestData: GraphClickMetaData,
|
||||||
|
): TagFilterItem[] =>
|
||||||
|
groupBy
|
||||||
|
.map((gb) => {
|
||||||
|
const value = requestData[gb.key];
|
||||||
|
return value
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: v4(),
|
||||||
|
key: gb,
|
||||||
|
op: '=',
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
|
||||||
|
// Helper to build filters for a single query, give priority to group by filters
|
||||||
|
const buildQueryFilters = (
|
||||||
|
queryData: IBuilderQuery,
|
||||||
|
groupByFilters: TagFilterItem[],
|
||||||
|
): { filters: TagFilterItem[]; dataSource?: string } => {
|
||||||
|
const existingFilters = queryData.filters?.items || [];
|
||||||
|
const uniqueFilters = existingFilters.filter(
|
||||||
|
(filter) =>
|
||||||
|
!groupByFilters.some(
|
||||||
|
(groupFilter) => groupFilter.key?.key === filter?.key?.key,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
filters: [...uniqueFilters, ...groupByFilters],
|
||||||
|
dataSource: queryData.dataSource,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Main function to build filters
|
||||||
|
export const buildFilters = (
|
||||||
|
query: Query,
|
||||||
|
requestData?: GraphClickMetaData,
|
||||||
|
): {
|
||||||
|
[queryName: string]: { filters: TagFilterItem[]; dataSource?: string };
|
||||||
|
} => {
|
||||||
|
// Handle specific query navigation
|
||||||
|
if (requestData?.queryName) {
|
||||||
|
const queryData = query.builder.queryData.find(
|
||||||
|
(q) => q.queryName === requestData.queryName,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Direct query match
|
||||||
|
if (queryData) {
|
||||||
|
const groupByFilters = createGroupByFilters(queryData.groupBy, requestData);
|
||||||
|
return {
|
||||||
|
[requestData.queryName]: buildQueryFilters(queryData, groupByFilters),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formula query handling
|
||||||
|
const formulaQuery = query.builder.queryFormulas.find(
|
||||||
|
(q) => q.queryName === requestData.queryName,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!formulaQuery) return {};
|
||||||
|
|
||||||
|
const queryNames = extractQueryNamesFromExpression(formulaQuery.expression);
|
||||||
|
const filteredQueryData = query.builder.queryData.filter((q) =>
|
||||||
|
queryNames.includes(q.queryName),
|
||||||
|
);
|
||||||
|
|
||||||
|
const returnObject: {
|
||||||
|
[queryName: string]: { filters: TagFilterItem[]; dataSource?: string };
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
filteredQueryData.forEach((q) => {
|
||||||
|
const groupByFilters = createGroupByFilters(q.groupBy, requestData);
|
||||||
|
returnObject[q.queryName] = buildQueryFilters(q, groupByFilters);
|
||||||
|
});
|
||||||
|
|
||||||
|
return returnObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook for handling navigation to explorer pages with query data
|
||||||
|
* @returns A function to handle navigation with query processing
|
||||||
|
*/
|
||||||
|
function useNavigateToExplorerPages(): (
|
||||||
|
props: NavigateToExplorerPagesProps,
|
||||||
|
) => Promise<{
|
||||||
|
[queryName: string]: { filters: TagFilterItem[]; dataSource?: string };
|
||||||
|
}> {
|
||||||
|
const { selectedDashboard } = useDashboard();
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async ({ widget, requestData }: NavigateToExplorerPagesProps) => {
|
||||||
|
try {
|
||||||
|
// Return the finalFilters
|
||||||
|
return buildFilters(
|
||||||
|
widget.query,
|
||||||
|
requestData ?? { queryName: '', inFocusOrNot: false },
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error navigating to explorer',
|
||||||
|
description:
|
||||||
|
error instanceof Error ? error.message : 'Unknown error occurred',
|
||||||
|
});
|
||||||
|
// Return empty object in case of error
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[selectedDashboard, notifications],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useNavigateToExplorerPages;
|
66
frontend/src/container/GridCardLayout/useResolveQuery.ts
Normal file
66
frontend/src/container/GridCardLayout/useResolveQuery.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { getQueryRangeFormat } from 'api/dashboard/queryRangeFormat';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
||||||
|
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
||||||
|
import { prepareQueryRangePayload } from 'lib/dashboard/prepareQueryRangePayload';
|
||||||
|
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useMutation } from 'react-query';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
import { getGraphType } from 'utils/getGraphType';
|
||||||
|
|
||||||
|
interface UseUpdatedQueryOptions {
|
||||||
|
widgetConfig: {
|
||||||
|
query: Query;
|
||||||
|
panelTypes: PANEL_TYPES;
|
||||||
|
timePreferance: timePreferenceType;
|
||||||
|
};
|
||||||
|
selectedDashboard?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseUpdatedQueryResult {
|
||||||
|
getUpdatedQuery: (options: UseUpdatedQueryOptions) => Promise<Query>;
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useUpdatedQuery(): UseUpdatedQueryResult {
|
||||||
|
const { selectedTime: globalSelectedInterval } = useSelector<
|
||||||
|
AppState,
|
||||||
|
GlobalReducer
|
||||||
|
>((state) => state.globalTime);
|
||||||
|
|
||||||
|
const queryRangeMutation = useMutation(getQueryRangeFormat);
|
||||||
|
|
||||||
|
const getUpdatedQuery = useCallback(
|
||||||
|
async ({
|
||||||
|
widgetConfig,
|
||||||
|
selectedDashboard,
|
||||||
|
}: UseUpdatedQueryOptions): Promise<Query> => {
|
||||||
|
// Prepare query payload with resolved variables
|
||||||
|
const { queryPayload } = prepareQueryRangePayload({
|
||||||
|
query: widgetConfig.query,
|
||||||
|
graphType: getGraphType(widgetConfig.panelTypes),
|
||||||
|
selectedTime: widgetConfig.timePreferance,
|
||||||
|
globalSelectedInterval,
|
||||||
|
variables: getDashboardVariables(selectedDashboard?.data?.variables),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Execute query and process results
|
||||||
|
const queryResult = await queryRangeMutation.mutateAsync(queryPayload);
|
||||||
|
|
||||||
|
// Map query data from API response
|
||||||
|
return mapQueryDataFromApi(queryResult.compositeQuery, widgetConfig?.query);
|
||||||
|
},
|
||||||
|
[globalSelectedInterval, queryRangeMutation],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
getUpdatedQuery,
|
||||||
|
isLoading: queryRangeMutation.isLoading,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useUpdatedQuery;
|
@ -1,3 +1,4 @@
|
|||||||
|
import { FORMULA_REGEXP } from 'constants/regExp';
|
||||||
import { Layout } from 'react-grid-layout';
|
import { Layout } from 'react-grid-layout';
|
||||||
|
|
||||||
export const removeUndefinedValuesFromLayout = (layout: Layout[]): Layout[] =>
|
export const removeUndefinedValuesFromLayout = (layout: Layout[]): Layout[] =>
|
||||||
@ -6,3 +7,21 @@ export const removeUndefinedValuesFromLayout = (layout: Layout[]): Layout[] =>
|
|||||||
Object.entries(obj).filter(([, value]) => value !== undefined),
|
Object.entries(obj).filter(([, value]) => value !== undefined),
|
||||||
),
|
),
|
||||||
) as Layout[];
|
) as Layout[];
|
||||||
|
|
||||||
|
export const isFormula = (queryName: string): boolean =>
|
||||||
|
FORMULA_REGEXP.test(queryName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts query names from a formula expression
|
||||||
|
* Specifically targets capital letters A-Z as query names, as after Z we dont have any query names
|
||||||
|
*/
|
||||||
|
export function extractQueryNamesFromExpression(expression: string): string[] {
|
||||||
|
if (!expression) return [];
|
||||||
|
|
||||||
|
// Use regex to match standalone capital letters
|
||||||
|
// Uses word boundaries to ensure we only get standalone letters
|
||||||
|
const queryNameRegex = /\b[A-Z]\b/g;
|
||||||
|
|
||||||
|
// Extract matches and deduplicate
|
||||||
|
return [...new Set(expression.match(queryNameRegex) || [])];
|
||||||
|
}
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
|
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { handleGraphClick } from 'container/GridCardLayout/GridCard/utils';
|
||||||
|
import { useGraphClickToShowButton } from 'container/GridCardLayout/useGraphClickToShowButton';
|
||||||
|
import useNavigateToExplorerPages from 'container/GridCardLayout/useNavigateToExplorerPages';
|
||||||
import PanelWrapper from 'container/PanelWrapper/PanelWrapper';
|
import PanelWrapper from 'container/PanelWrapper/PanelWrapper';
|
||||||
import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/config';
|
import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/config';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||||
@ -22,6 +27,7 @@ import { UpdateTimeInterval } from 'store/actions';
|
|||||||
import { SuccessResponse } from 'types/api';
|
import { SuccessResponse } from 'types/api';
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
function WidgetGraph({
|
function WidgetGraph({
|
||||||
selectedWidget,
|
selectedWidget,
|
||||||
@ -88,6 +94,43 @@ function WidgetGraph({
|
|||||||
|
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
|
// context redirection to explorer pages
|
||||||
|
const graphClick = useGraphClickToShowButton({
|
||||||
|
graphRef,
|
||||||
|
isButtonEnabled: (selectedWidget?.query?.builder?.queryData ?? []).some(
|
||||||
|
(q) =>
|
||||||
|
q.dataSource === DataSource.TRACES || q.dataSource === DataSource.LOGS,
|
||||||
|
),
|
||||||
|
buttonClassName: 'view-onclick-show-button',
|
||||||
|
});
|
||||||
|
|
||||||
|
const navigateToExplorer = useNavigateToExplorer();
|
||||||
|
const navigateToExplorerPages = useNavigateToExplorerPages();
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
|
const graphClickHandler = (
|
||||||
|
xValue: number,
|
||||||
|
yValue: number,
|
||||||
|
mouseX: number,
|
||||||
|
mouseY: number,
|
||||||
|
metric?: { [key: string]: string },
|
||||||
|
queryData?: { queryName: string; inFocusOrNot: boolean },
|
||||||
|
): void => {
|
||||||
|
handleGraphClick({
|
||||||
|
xValue,
|
||||||
|
yValue,
|
||||||
|
mouseX,
|
||||||
|
mouseY,
|
||||||
|
metric,
|
||||||
|
queryData,
|
||||||
|
widget: selectedWidget,
|
||||||
|
navigateToExplorerPages,
|
||||||
|
navigateToExplorer,
|
||||||
|
notifications,
|
||||||
|
graphClick,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={graphRef}
|
ref={graphRef}
|
||||||
@ -110,6 +153,7 @@ function WidgetGraph({
|
|||||||
setRequestData={setRequestData}
|
setRequestData={setRequestData}
|
||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
selectedGraph={selectedGraph}
|
selectedGraph={selectedGraph}
|
||||||
|
onClickHandler={graphClickHandler}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -9,6 +9,10 @@ export interface OnClickPluginOpts {
|
|||||||
data?: {
|
data?: {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
},
|
},
|
||||||
|
queryData?: {
|
||||||
|
queryName: string;
|
||||||
|
inFocusOrNot: boolean;
|
||||||
|
},
|
||||||
) => void;
|
) => void;
|
||||||
apiResponse?: MetricRangePayloadProps;
|
apiResponse?: MetricRangePayloadProps;
|
||||||
}
|
}
|
||||||
@ -31,6 +35,10 @@ function onClickPlugin(opts: OnClickPluginOpts): uPlot.Plugin {
|
|||||||
let metric = {};
|
let metric = {};
|
||||||
const { series } = u;
|
const { series } = u;
|
||||||
const apiResult = opts.apiResponse?.data?.result || [];
|
const apiResult = opts.apiResponse?.data?.result || [];
|
||||||
|
const outputMetric = {
|
||||||
|
queryName: '',
|
||||||
|
inFocusOrNot: false,
|
||||||
|
};
|
||||||
|
|
||||||
// this is to get the metric value of the focused series
|
// this is to get the metric value of the focused series
|
||||||
if (Array.isArray(series) && series.length > 0) {
|
if (Array.isArray(series) && series.length > 0) {
|
||||||
@ -38,13 +46,15 @@ function onClickPlugin(opts: OnClickPluginOpts): uPlot.Plugin {
|
|||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (item?.show && item?._focus) {
|
if (item?.show && item?._focus) {
|
||||||
const { metric: focusedMetric } = apiResult[index - 1] || [];
|
const { metric: focusedMetric, queryName } = apiResult[index - 1] || [];
|
||||||
metric = focusedMetric;
|
metric = focusedMetric;
|
||||||
|
outputMetric.queryName = queryName;
|
||||||
|
outputMetric.inFocusOrNot = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.onClick(xValue, yValue, mouseX, mouseY, metric);
|
opts.onClick(xValue, yValue, mouseX, mouseY, metric, outputMetric);
|
||||||
};
|
};
|
||||||
u.over.addEventListener('click', handleClick);
|
u.over.addEventListener('click', handleClick);
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,7 @@ import { Color } from '@signozhq/design-tokens';
|
|||||||
import { Card } from 'antd';
|
import { Card } from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import { useGetGraphCustomSeries } from 'components/CeleryTask/useGetGraphCustomSeries';
|
import { useGetGraphCustomSeries } from 'components/CeleryTask/useGetGraphCustomSeries';
|
||||||
import { useNavigateToTraces } from 'components/CeleryTask/useNavigateToTraces';
|
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import { ViewMenuAction } from 'container/GridCardLayout/config';
|
import { ViewMenuAction } from 'container/GridCardLayout/config';
|
||||||
import GridCard from 'container/GridCardLayout/GridCard';
|
import GridCard from 'container/GridCardLayout/GridCard';
|
||||||
@ -18,6 +18,7 @@ import { AppState } from 'store/reducers';
|
|||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -82,7 +83,7 @@ export default function OverviewRightPanelGraph({
|
|||||||
setSelectedTimeStamp(selectTime);
|
setSelectedTimeStamp(selectTime);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const navigateToTraces = useNavigateToTraces();
|
const navigateToExplorer = useNavigateToExplorer();
|
||||||
|
|
||||||
const onGraphClickHandler = useGraphClickHandler(handleSetTimeStamp);
|
const onGraphClickHandler = useGraphClickHandler(handleSetTimeStamp);
|
||||||
|
|
||||||
@ -100,13 +101,14 @@ export default function OverviewRightPanelGraph({
|
|||||||
const goToTraces = useCallback(
|
const goToTraces = useCallback(
|
||||||
(widget: Widgets) => {
|
(widget: Widgets) => {
|
||||||
const { stepInterval } = widget?.query?.builder?.queryData?.[0] ?? {};
|
const { stepInterval } = widget?.query?.builder?.queryData?.[0] ?? {};
|
||||||
navigateToTraces(
|
navigateToExplorer({
|
||||||
filters ?? [],
|
filters: filters ?? [],
|
||||||
selectedTimeStamp,
|
dataSource: DataSource.TRACES,
|
||||||
selectedTimeStamp + (stepInterval ?? 60),
|
startTime: selectedTimeStamp,
|
||||||
);
|
endTime: selectedTimeStamp + (stepInterval ?? 60),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[navigateToTraces, filters, selectedTimeStamp],
|
[navigateToExplorer, filters, selectedTimeStamp],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { getCustomSeries } = useGetGraphCustomSeries({
|
const { getCustomSeries } = useGetGraphCustomSeries({
|
||||||
|
@ -3,7 +3,7 @@ import './ValueInfo.styles.scss';
|
|||||||
import { FileSearchOutlined } from '@ant-design/icons';
|
import { FileSearchOutlined } from '@ant-design/icons';
|
||||||
import { Button, Card, Col, Row } from 'antd';
|
import { Button, Card, Col, Row } from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import { useNavigateToTraces } from 'components/CeleryTask/useNavigateToTraces';
|
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
|
||||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||||
@ -15,6 +15,7 @@ import { SuccessResponse } from 'types/api';
|
|||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
@ -82,7 +83,7 @@ export default function ValueInfo({
|
|||||||
[isLoading, getValues],
|
[isLoading, getValues],
|
||||||
);
|
);
|
||||||
|
|
||||||
const navigateToTrace = useNavigateToTraces();
|
const navigateToExplorer = useNavigateToExplorer();
|
||||||
|
|
||||||
const avgLatencyInMs = useMemo(() => {
|
const avgLatencyInMs = useMemo(() => {
|
||||||
if (avgLatency === 'NaN') return 'NaN';
|
if (avgLatency === 'NaN') return 'NaN';
|
||||||
@ -144,7 +145,12 @@ export default function ValueInfo({
|
|||||||
maxTime,
|
maxTime,
|
||||||
source: 'request rate',
|
source: 'request rate',
|
||||||
});
|
});
|
||||||
navigateToTrace(filters ?? []);
|
navigateToExplorer({
|
||||||
|
filters: filters ?? [],
|
||||||
|
dataSource: DataSource.TRACES,
|
||||||
|
startTime: minTime,
|
||||||
|
endTime: maxTime,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
View Traces
|
View Traces
|
||||||
@ -174,22 +180,27 @@ export default function ValueInfo({
|
|||||||
maxTime,
|
maxTime,
|
||||||
source: 'error rate',
|
source: 'error rate',
|
||||||
});
|
});
|
||||||
navigateToTrace([
|
navigateToExplorer({
|
||||||
...(filters ?? []),
|
filters: [
|
||||||
{
|
...(filters ?? []),
|
||||||
id: uuidv4(),
|
{
|
||||||
key: {
|
id: uuidv4(),
|
||||||
dataType: DataTypes.bool,
|
key: {
|
||||||
id: 'has_error--bool----true',
|
dataType: DataTypes.bool,
|
||||||
isColumn: true,
|
id: 'has_error--bool----true',
|
||||||
isJSON: false,
|
isColumn: true,
|
||||||
key: 'has_error',
|
isJSON: false,
|
||||||
type: '',
|
key: 'has_error',
|
||||||
|
type: '',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: 'true',
|
||||||
},
|
},
|
||||||
op: '=',
|
],
|
||||||
value: 'true',
|
dataSource: DataSource.TRACES,
|
||||||
},
|
startTime: minTime,
|
||||||
]);
|
endTime: maxTime,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
View Traces
|
View Traces
|
||||||
@ -219,7 +230,12 @@ export default function ValueInfo({
|
|||||||
maxTime,
|
maxTime,
|
||||||
source: 'average latency',
|
source: 'average latency',
|
||||||
});
|
});
|
||||||
navigateToTrace(filters ?? []);
|
navigateToExplorer({
|
||||||
|
filters: filters ?? [],
|
||||||
|
dataSource: DataSource.TRACES,
|
||||||
|
startTime: minTime,
|
||||||
|
endTime: maxTime,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
View Traces
|
View Traces
|
||||||
|
@ -143,7 +143,7 @@ body {
|
|||||||
// =================================================================
|
// =================================================================
|
||||||
// AntD style overrides
|
// AntD style overrides
|
||||||
.ant-dropdown-menu {
|
.ant-dropdown-menu {
|
||||||
margin-top: 2px !important;
|
margin-top: 2px;
|
||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@ -180,6 +180,14 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// these are default styles but are overridden by above dropdown styles
|
||||||
|
.ant-dropdown-menu-submenu-popup {
|
||||||
|
padding: 0 !important;
|
||||||
|
z-index: 1050 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/ant-design/ant-design/issues/41307
|
// https://github.com/ant-design/ant-design/issues/41307
|
||||||
.ant-picker-panels > *:first-child button.ant-picker-header-next-btn {
|
.ant-picker-panels > *:first-child button.ant-picker-header-next-btn {
|
||||||
visibility: visible !important;
|
visibility: visible !important;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user