mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-11 16:29:01 +08:00
feat: resource attribute query is shared in the app navigation from sidebar (#2553)
This commit is contained in:
parent
6c11c6d4da
commit
6949c659af
@ -1,5 +1,4 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
export enum QueryParams {
|
||||||
export enum METRICS_PAGE_QUERY_PARAM {
|
|
||||||
interval = 'interval',
|
interval = 'interval',
|
||||||
startTime = 'startTime',
|
startTime = 'startTime',
|
||||||
endTime = 'endTime',
|
endTime = 'endTime',
|
||||||
@ -12,4 +11,5 @@ export enum METRICS_PAGE_QUERY_PARAM {
|
|||||||
selectedTags = 'selectedTags',
|
selectedTags = 'selectedTags',
|
||||||
aggregationOption = 'aggregationOption',
|
aggregationOption = 'aggregationOption',
|
||||||
entity = 'entity',
|
entity = 'entity',
|
||||||
|
resourceAttributes = 'resourceAttribute',
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
|
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
|
||||||
import Graph from 'components/Graph';
|
import Graph from 'components/Graph';
|
||||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import FullView from 'container/GridGraphLayout/Graph/FullView/index.metricsBuilder';
|
import FullView from 'container/GridGraphLayout/Graph/FullView/index.metricsBuilder';
|
||||||
|
import { routeConfig } from 'container/SideNav/config';
|
||||||
|
import { getQueryString } from 'container/SideNav/helper';
|
||||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||||
import {
|
import {
|
||||||
convertRawQueriesToTraceSelectedTags,
|
convertRawQueriesToTraceSelectedTags,
|
||||||
@ -13,7 +15,7 @@ import { colors } from 'lib/getRandomColor';
|
|||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useLocation, useParams } 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 { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
@ -35,6 +37,7 @@ import {
|
|||||||
function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
||||||
const { servicename } = useParams<{ servicename?: string }>();
|
const { servicename } = useParams<{ servicename?: string }>();
|
||||||
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
|
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
|
||||||
|
const { search } = useLocation();
|
||||||
|
|
||||||
const handleSetTimeStamp = useCallback((selectTime: number) => {
|
const handleSetTimeStamp = useCallback((selectTime: number) => {
|
||||||
setSelectedTimeStamp(selectTime);
|
setSelectedTimeStamp(selectTime);
|
||||||
@ -122,14 +125,19 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
|||||||
const currentTime = timestamp;
|
const currentTime = timestamp;
|
||||||
const tPlusOne = timestamp + 60 * 1000;
|
const tPlusOne = timestamp + 60 * 1000;
|
||||||
|
|
||||||
const urlParams = new URLSearchParams();
|
const urlParams = new URLSearchParams(search);
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
urlParams.set(QueryParams.startTime, currentTime.toString());
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
|
urlParams.set(QueryParams.endTime, tPlusOne.toString());
|
||||||
|
|
||||||
|
const avialableParams = routeConfig[ROUTES.TRACE];
|
||||||
|
const queryString = getQueryString(avialableParams, urlParams);
|
||||||
|
|
||||||
history.replace(
|
history.replace(
|
||||||
`${
|
`${
|
||||||
ROUTES.TRACE
|
ROUTES.TRACE
|
||||||
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&isFilterExclude={"serviceName":false,"status":false}&userSelectedFilter={"serviceName":["${servicename}"],"status":["error"]}&spanAggregateCurrentPage=1`,
|
}?selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&isFilterExclude={"serviceName":false,"status":false}&userSelectedFilter={"serviceName":["${servicename}"],"status":["error"]}&spanAggregateCurrentPage=1&${queryString.join(
|
||||||
|
'',
|
||||||
|
)}`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
|
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
|
||||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
|
import { routeConfig } from 'container/SideNav/config';
|
||||||
|
import { getQueryString } from 'container/SideNav/helper';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { IQueryBuilderTagFilterItems } from 'types/api/dashboard/getAll';
|
import { IQueryBuilderTagFilterItems } from 'types/api/dashboard/getAll';
|
||||||
import { Tags } from 'types/reducer/trace';
|
import { Tags } from 'types/reducer/trace';
|
||||||
@ -31,16 +33,18 @@ export function onViewTracePopupClick({
|
|||||||
const currentTime = timestamp;
|
const currentTime = timestamp;
|
||||||
const tPlusOne = timestamp + 60 * 1000;
|
const tPlusOne = timestamp + 60 * 1000;
|
||||||
|
|
||||||
const urlParams = new URLSearchParams();
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
urlParams.set(QueryParams.startTime, currentTime.toString());
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
|
urlParams.set(QueryParams.endTime, tPlusOne.toString());
|
||||||
|
const avialableParams = routeConfig[ROUTES.TRACE];
|
||||||
|
const queryString = getQueryString(avialableParams, urlParams);
|
||||||
|
|
||||||
history.replace(
|
history.replace(
|
||||||
`${
|
`${
|
||||||
ROUTES.TRACE
|
ROUTES.TRACE
|
||||||
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1${
|
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1${
|
||||||
isExternalCall ? '&spanKind=3' : ''
|
isExternalCall ? '&spanKind=3' : ''
|
||||||
}`,
|
}&${queryString.join('&')}`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Tooltip, Typography } from 'antd';
|
import { Tooltip, Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||||
@ -29,14 +29,8 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
|
|||||||
const handleOnClick = (operation: string): void => {
|
const handleOnClick = (operation: string): void => {
|
||||||
const urlParams = new URLSearchParams();
|
const urlParams = new URLSearchParams();
|
||||||
const { servicename } = params;
|
const { servicename } = params;
|
||||||
urlParams.set(
|
urlParams.set(QueryParams.startTime, (minTime / 1000000).toString());
|
||||||
METRICS_PAGE_QUERY_PARAM.startTime,
|
urlParams.set(QueryParams.endTime, (maxTime / 1000000).toString());
|
||||||
(minTime / 1000000).toString(),
|
|
||||||
);
|
|
||||||
urlParams.set(
|
|
||||||
METRICS_PAGE_QUERY_PARAM.endTime,
|
|
||||||
(maxTime / 1000000).toString(),
|
|
||||||
);
|
|
||||||
|
|
||||||
history.push(
|
history.push(
|
||||||
`${
|
`${
|
||||||
|
@ -11,6 +11,8 @@ import localStorageSet from 'api/browser/localstorage/set';
|
|||||||
import { ResizeTable } from 'components/ResizeTable';
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import { SKIP_ONBOARDING } from 'constants/onboarding';
|
import { SKIP_ONBOARDING } from 'constants/onboarding';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
|
import { routeConfig } from 'container/SideNav/config';
|
||||||
|
import { getQueryString } from 'container/SideNav/helper';
|
||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
@ -88,11 +90,17 @@ function Metrics(): JSX.Element {
|
|||||||
.toString()
|
.toString()
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(value.toString().toLowerCase()),
|
.includes(value.toString().toLowerCase()),
|
||||||
render: (text: string): JSX.Element => (
|
render: (metrics: string): JSX.Element => {
|
||||||
<Link to={`${ROUTES.APPLICATION}/${text}${search}`}>
|
const urlParams = new URLSearchParams(search);
|
||||||
<Name>{text}</Name>
|
const avialableParams = routeConfig[ROUTES.SERVICE_METRICS];
|
||||||
</Link>
|
const queryString = getQueryString(avialableParams, urlParams);
|
||||||
),
|
|
||||||
|
return (
|
||||||
|
<Link to={`${ROUTES.APPLICATION}/${metrics}?${queryString.join('')}`}>
|
||||||
|
<Name>{metrics}</Name>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
[filterDropdown, FilterIcon, search],
|
[filterDropdown, FilterIcon, search],
|
||||||
);
|
);
|
||||||
|
@ -1 +1,38 @@
|
|||||||
|
import { QueryParams } from 'constants/query';
|
||||||
|
import ROUTES from 'constants/routes';
|
||||||
|
|
||||||
export const styles = { background: '#1f1f1f' };
|
export const styles = { background: '#1f1f1f' };
|
||||||
|
|
||||||
|
export const routeConfig: Record<string, QueryParams[]> = {
|
||||||
|
[ROUTES.SERVICE_METRICS]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.SERVICE_MAP]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.ALL_ERROR]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.ALERTS_NEW]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.ALL_CHANNELS]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.ALL_DASHBOARD]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.APPLICATION]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.CHANNELS_EDIT]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.CHANNELS_NEW]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.DASHBOARD]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.DASHBOARD_WIDGET]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.EDIT_ALERTS]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.ERROR_DETAIL]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.HOME_PAGE]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.INSTRUMENTATION]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.LIST_ALL_ALERT]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.LIST_LICENSES]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.LOGIN]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.LOGS]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.MY_SETTINGS]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.NOT_FOUND]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.ORG_SETTINGS]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.PASSWORD_RESET]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.SETTINGS]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.SIGN_UP]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.SOMETHING_WENT_WRONG]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.TRACE]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.TRACE_DETAIL]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.UN_AUTHORIZED]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.USAGE_EXPLORER]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.VERSION]: [QueryParams.resourceAttributes],
|
||||||
|
};
|
||||||
|
34
frontend/src/container/SideNav/helper.test.ts
Normal file
34
frontend/src/container/SideNav/helper.test.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { getQueryString } from './helper';
|
||||||
|
|
||||||
|
describe('getQueryString', () => {
|
||||||
|
it('returns an array of query strings for given available parameters and URLSearchParams', () => {
|
||||||
|
const availableParams = ['param1', 'param2', 'param3'];
|
||||||
|
const params = new URLSearchParams(
|
||||||
|
'param1=value1¶m2=value2¶m4=value4',
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = getQueryString(availableParams, params);
|
||||||
|
|
||||||
|
expect(result).toEqual(['param1=value1', 'param2=value2', '']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an array of empty strings if no matching parameters are found', () => {
|
||||||
|
const availableParams = ['param1', 'param2', 'param3'];
|
||||||
|
const params = new URLSearchParams('param4=value4¶m5=value5');
|
||||||
|
|
||||||
|
const result = getQueryString(availableParams, params);
|
||||||
|
|
||||||
|
expect(result).toEqual(['', '', '']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an empty array if the available parameters list is empty', () => {
|
||||||
|
const availableParams: string[] = [];
|
||||||
|
const params = new URLSearchParams(
|
||||||
|
'param1=value1¶m2=value2¶m3=value3',
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = getQueryString(availableParams, params);
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
10
frontend/src/container/SideNav/helper.ts
Normal file
10
frontend/src/container/SideNav/helper.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export const getQueryString = (
|
||||||
|
avialableParams: string[],
|
||||||
|
params: URLSearchParams,
|
||||||
|
): string[] =>
|
||||||
|
avialableParams.map((param) => {
|
||||||
|
if (params.has(param)) {
|
||||||
|
return `${param}=${params.get(param)}`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
});
|
@ -12,7 +12,8 @@ import { SideBarCollapse } from 'store/actions/app';
|
|||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
|
|
||||||
import { styles } from './config';
|
import { routeConfig, styles } from './config';
|
||||||
|
import { getQueryString } from './helper';
|
||||||
import menus from './menuItems';
|
import menus from './menuItems';
|
||||||
import Slack from './Slack';
|
import Slack from './Slack';
|
||||||
import {
|
import {
|
||||||
@ -34,7 +35,7 @@ function SideNav(): JSX.Element {
|
|||||||
AppReducer
|
AppReducer
|
||||||
>((state) => state.app);
|
>((state) => state.app);
|
||||||
|
|
||||||
const { pathname } = useLocation();
|
const { pathname, search } = useLocation();
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation('');
|
||||||
|
|
||||||
const onCollapse = useCallback(() => {
|
const onCollapse = useCallback(() => {
|
||||||
@ -47,11 +48,16 @@ function SideNav(): JSX.Element {
|
|||||||
|
|
||||||
const onClickHandler = useCallback(
|
const onClickHandler = useCallback(
|
||||||
(to: string) => {
|
(to: string) => {
|
||||||
|
const params = new URLSearchParams(search);
|
||||||
|
const avialableParams = routeConfig[to];
|
||||||
|
|
||||||
|
const queryString = getQueryString(avialableParams, params);
|
||||||
|
|
||||||
if (pathname !== to) {
|
if (pathname !== to) {
|
||||||
history.push(`${to}`);
|
history.push(`${to}?${queryString.join('&')}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[pathname],
|
[pathname, search],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onClickSlackHandler = (): void => {
|
const onClickSlackHandler = (): void => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user