feat: resource attribute is added in the exception (#2491)

* feat: resource attribute is added in the exception

* fix: build is fixed

* chore: methods is updated to post

* fix: build is fixed

* fix: listErrors, countErrors API request body

* chore: type of the function is updated

* chore: convertRawQueriesToTraceSelectedTags is updated

* fix: resource attribute is updated

* chore: selected tags is updated

* feat: key is updated

---------

Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
This commit is contained in:
Palash Gupta 2023-03-29 14:45:58 +05:30 committed by GitHub
parent 12e56932ee
commit 99ed314fc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 754 additions and 722 deletions

View File

@ -4,6 +4,7 @@ import Spinner from 'components/Spinner';
import AppLayout from 'container/AppLayout';
import { useThemeConfig } from 'hooks/useDarkMode';
import { NotificationProvider } from 'hooks/useNotifications';
import { ResourceProvider } from 'hooks/useResourceAttribute';
import history from 'lib/history';
import { QueryBuilderProvider } from 'providers/QueryBuilder';
import React, { Suspense } from 'react';
@ -17,30 +18,32 @@ function App(): JSX.Element {
return (
<ConfigProvider theme={themeConfig}>
<NotificationProvider>
<Router history={history}>
<Router history={history}>
<NotificationProvider>
<PrivateRoute>
<QueryBuilderProvider>
<AppLayout>
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
<Switch>
{routes.map(({ path, component, exact }) => (
<Route
key={`${path}`}
exact={exact}
path={path}
component={component}
/>
))}
<ResourceProvider>
<QueryBuilderProvider>
<AppLayout>
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
<Switch>
{routes.map(({ path, component, exact }) => (
<Route
key={`${path}`}
exact={exact}
path={path}
component={component}
/>
))}
<Route path="*" component={NotFound} />
</Switch>
</Suspense>
</AppLayout>
</QueryBuilderProvider>
<Route path="*" component={NotFound} />
</Switch>
</Suspense>
</AppLayout>
</QueryBuilderProvider>
</ResourceProvider>
</PrivateRoute>
</Router>
</NotificationProvider>
</NotificationProvider>
</Router>
</ConfigProvider>
);
}

View File

@ -1,7 +1,6 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import createQueryParams from 'lib/createQueryParams';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/errors/getAll';
@ -9,11 +8,17 @@ const getAll = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.get(
`/listErrors?${createQueryParams({
...props,
})}`,
);
const response = await axios.post(`/listErrors`, {
start: `${props.start}`,
end: `${props.end}`,
order: props.order,
orderParam: props.orderParam,
limit: props.limit,
offset: props.offset,
exceptionType: props.exceptionType,
serviceName: props.serviceName,
tags: props.tags,
});
return {
statusCode: 200,

View File

@ -1,7 +1,6 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import createQueryParams from 'lib/createQueryParams';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/errors/getErrorCounts';
@ -9,11 +8,13 @@ const getErrorCounts = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.get(
`/countErrors?${createQueryParams({
...props,
})}`,
);
const response = await axios.post(`/countErrors`, {
start: `${props.start}`,
end: `${props.end}`,
exceptionType: props.exceptionType,
serviceName: props.serviceName,
tags: props.tags,
});
return {
statusCode: 200,

View File

@ -10,7 +10,7 @@ const getSpans = async (
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const updatedSelectedTags = props.selectedTags.map((e) => ({
Key: e.Key,
Key: `${e.Key}.(string)`,
Operator: e.Operator,
StringValues: e.StringValues,
NumberValues: e.NumberValues,

View File

@ -28,7 +28,7 @@ const getSpanAggregate = async (
});
const updatedSelectedTags = props.selectedTags.map((e) => ({
Key: e.Key,
Key: `${e.Key}.(string)`,
Operator: e.Operator,
StringValues: e.StringValues,
NumberValues: e.NumberValues,

View File

@ -18,6 +18,8 @@ import { ResizeTable } from 'components/ResizeTable';
import ROUTES from 'constants/routes';
import dayjs from 'dayjs';
import { useNotifications } from 'hooks/useNotifications';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
import useUrlQuery from 'hooks/useUrlQuery';
import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
@ -93,9 +95,11 @@ function AllErrors(): JSX.Element {
],
);
const { queries } = useResourceAttribute();
const [{ isLoading, data }, errorCountResponse] = useQueries([
{
queryKey: ['getAllErrors', updatedPath, maxTime, minTime],
queryKey: ['getAllErrors', updatedPath, maxTime, minTime, queries],
queryFn: (): Promise<SuccessResponse<PayloadProps> | ErrorResponse> =>
getAll({
end: maxTime,
@ -106,6 +110,7 @@ function AllErrors(): JSX.Element {
orderParam: getUpdatedParams,
exceptionType: getUpdatedExceptionType,
serviceName: getUpdatedServiceName,
tags: convertRawQueriesToTraceSelectedTags(queries),
}),
enabled: !loading,
},
@ -116,6 +121,7 @@ function AllErrors(): JSX.Element {
minTime,
getUpdatedExceptionType,
getUpdatedServiceName,
queries,
],
queryFn: (): Promise<ErrorResponse | SuccessResponse<number>> =>
getErrorCounts({
@ -123,6 +129,7 @@ function AllErrors(): JSX.Element {
start: minTime,
exceptionType: getUpdatedExceptionType,
serviceName: getUpdatedServiceName,
tags: convertRawQueriesToTraceSelectedTags(queries),
}),
enabled: !loading,
},

View File

@ -1,32 +0,0 @@
import { convertMetricKeyToTrace } from 'lib/resourceAttributes';
import React from 'react';
import { QueryChipContainer, QueryChipItem } from './styles';
import { IResourceAttributeQuery } from './types';
interface IQueryChipProps {
queryData: IResourceAttributeQuery;
onClose: (id: string) => void;
disabled: boolean;
}
export default function QueryChip({
queryData,
onClose,
disabled,
}: IQueryChipProps): JSX.Element {
return (
<QueryChipContainer>
<QueryChipItem>{convertMetricKeyToTrace(queryData.tagKey)}</QueryChipItem>
<QueryChipItem>{queryData.operator}</QueryChipItem>
<QueryChipItem
closable={!disabled}
onClose={(): void => {
if (!disabled) onClose(queryData.id);
}}
>
{queryData.tagValue.join(', ')}
</QueryChipItem>
</QueryChipContainer>
);
}

View File

@ -1,61 +0,0 @@
import { createMachine } from 'xstate';
export const ResourceAttributesFilterMachine =
/** @xstate-layout N4IgpgJg5mDOIC5QBECGsAWAjA9qgThAAQDKYBAxhkQIIB2xAYgJYA2ALmPgHQAqqUANJgAngGIAcgFEAGr0SgADjljN2zHHQUgAHogAcAFgAM3AOz6ATAEYAzJdsA2Y4cOWAnABoQIxAFpDR2tuQ319AFYTcKdbFycAX3jvNExcAmIySmp6JjZOHn4hUTFNACFWAFd8bWVVdU1tPQQzY1MXY2tDdzNHM3dHd0NvXwR7biMTa313S0i+63DE5PRsPEJScnwqWgYiFg4uPgFhcQAlKRIpeSQQWrUNLRumx3Czbg8TR0sbS31jfUcw38fW47gBHmm4XCVms3SWIBSq3SGyyO1yBx4AHlFFxUOwcPhJLJrkoVPcGk9ENYFuF3i5YR0wtEHECEAEgiEmV8zH1DLYzHZ4Yi0utMltsrt9vluNjcfjCWVKtUbnd6o9QE1rMYBtxbGFvsZ3NrZj1WdYOfotUZLX0XEFHEKViKMpttjk9nlDrL8HiCWJzpcSbcyWrGoh3NCQj0zK53P1ph1WeFLLqnJZ2s5vmZLA6kginWsXaj3VLDoUAGqoSpgEp0cpVGohh5hhDWDy0sz8zruakzamWVm-Qyg362V5-AZOayO1KFlHitEejFHKCV6v+i5XRt1ZuU1s52zjNOOaZfdOWIY+RDZ0Hc6ZmKEXqyLPPCudit2Sz08ACSEFYNbSHI27kuquiIOEjiONwjJgrM3RWJYZisgEIJgnYPTmuEdi2OaiR5nQOAQHA2hvsiH4Sui0qFCcIGhnuLSmP0YJuJ2xjJsmKELG8XZTK0tjdHG06vgW5GupRS7St6vrKqSO4UhqVL8TBWp8o4eqdl0A5Xmy3G6gK56-B4uERDOSKiuJi6lgUAhrhUYB0buimtrEKZBDYrxaS0OZca8+ltheybOI4hivGZzrzp+VGHH+AGOQp4EIHy+ghNYnawtG4TsbYvk8QKfHGAJfQ9uF76WSW37xWBTSGJ0qXpd0vRZdEKGPqC2YeO2-zfO4+HxEAA */
createMachine({
tsTypes: {} as import('./ResourceAttributesFilter.Machine.typegen').Typegen0,
initial: 'Idle',
states: {
TagKey: {
on: {
NEXT: {
actions: 'onSelectOperator',
target: 'Operator',
},
onBlur: {
actions: 'onBlurPurge',
target: 'Idle',
},
RESET: {
target: 'Idle',
},
},
},
Operator: {
on: {
NEXT: {
actions: 'onSelectTagValue',
target: 'TagValue',
},
onBlur: {
actions: 'onBlurPurge',
target: 'Idle',
},
RESET: {
target: 'Idle',
},
},
},
TagValue: {
on: {
onBlur: {
actions: ['onValidateQuery', 'onBlurPurge'],
target: 'Idle',
},
RESET: {
target: 'Idle',
},
},
},
Idle: {
on: {
NEXT: {
actions: 'onSelectTagKey',
description: 'Select Category',
target: 'TagKey',
},
},
},
},
id: 'Dashboard Search And Filter',
});

View File

@ -1,32 +0,0 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
eventsCausingActions: {
onSelectOperator: 'NEXT';
onBlurPurge: 'onBlur';
onSelectTagValue: 'NEXT';
onValidateQuery: 'onBlur';
onSelectTagKey: 'NEXT';
};
internalEvents: {
'xstate.init': { type: 'xstate.init' };
};
invokeSrcNameMap: {};
missingImplementations: {
actions:
| 'onSelectOperator'
| 'onBlurPurge'
| 'onSelectTagValue'
| 'onValidateQuery'
| 'onSelectTagKey';
services: never;
guards: never;
delays: never;
};
eventsCausingServices: {};
eventsCausingGuards: {};
eventsCausingDelays: {};
matchesStates: 'TagKey' | 'Operator' | 'TagValue' | 'Idle';
tags: never;
}

View File

@ -1,219 +0,0 @@
import { CloseCircleFilled } from '@ant-design/icons';
import { useMachine } from '@xstate/react';
import { Button, Select, Spin } from 'antd';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import { convertMetricKeyToTrace } from 'lib/resourceAttributes';
import { map } from 'lodash-es';
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { ResetInitialData } from 'store/actions/metrics/resetInitialData';
import { SetResourceAttributeQueries } from 'store/actions/metrics/setResourceAttributeQueries';
import { AppState } from 'store/reducers';
import MetricReducer from 'types/reducer/metrics';
import { v4 as uuid } from 'uuid';
import QueryChip from './QueryChip';
import { ResourceAttributesFilterMachine } from './ResourceAttributesFilter.Machine';
import { QueryChipItem, SearchContainer } from './styles';
import { IOption, IResourceAttributeQuery } from './types';
import { createQuery, GetTagKeys, GetTagValues, OperatorSchema } from './utils';
function ResourceAttributesFilter(): JSX.Element | null {
const dispatch = useDispatch();
const [disabled, setDisabled] = useState(
!(history.location.pathname === ROUTES.APPLICATION),
);
useEffect(() => {
const unListen = history.listen(({ pathname }) => {
setDisabled(!(pathname === ROUTES.APPLICATION));
});
return (): void => {
if (!history.location.pathname.startsWith(`${ROUTES.APPLICATION}/`)) {
dispatch(ResetInitialData());
}
unListen();
};
}, [dispatch]);
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
(state) => state.metrics,
);
const [loading, setLoading] = useState(true);
const [selectedValues, setSelectedValues] = useState<string[]>([]);
const [staging, setStaging] = useState<string[]>([]);
const [queries, setQueries] = useState<IResourceAttributeQuery[]>([]);
const [optionsData, setOptionsData] = useState<{
mode: undefined | 'tags' | 'multiple';
options: IOption[];
}>({
mode: undefined,
options: [],
});
const dispatchQueries = (updatedQueries: IResourceAttributeQuery[]): void => {
dispatch(SetResourceAttributeQueries(updatedQueries));
};
const handleLoading = (isLoading: boolean): void => {
setLoading(isLoading);
if (isLoading) {
setOptionsData({ mode: undefined, options: [] });
}
};
const [state, send] = useMachine(ResourceAttributesFilterMachine, {
actions: {
onSelectTagKey: () => {
handleLoading(true);
GetTagKeys()
.then((tagKeys) => setOptionsData({ options: tagKeys, mode: undefined }))
.finally(() => {
handleLoading(false);
});
},
onSelectOperator: () => {
setOptionsData({ options: OperatorSchema, mode: undefined });
},
onSelectTagValue: () => {
handleLoading(true);
GetTagValues(staging[0])
.then((tagValuesOptions) =>
setOptionsData({ options: tagValuesOptions, mode: 'multiple' }),
)
.finally(() => {
handleLoading(false);
});
},
onBlurPurge: () => {
setSelectedValues([]);
setStaging([]);
},
onValidateQuery: (): void => {
if (staging.length < 2 || selectedValues.length === 0) {
return;
}
const generatedQuery = createQuery([...staging, selectedValues]);
if (generatedQuery) {
dispatchQueries([...queries, generatedQuery]);
}
},
},
});
useEffect(() => {
setQueries(resourceAttributeQueries);
}, [resourceAttributeQueries]);
const handleFocus = (): void => {
if (state.value === 'Idle') {
send('NEXT');
}
};
const handleBlur = (): void => {
send('onBlur');
};
const handleChange = (value: never): void => {
if (!optionsData.mode) {
setStaging((prevStaging) => [...prevStaging, value]);
setSelectedValues([]);
send('NEXT');
return;
}
setSelectedValues([...value]);
};
const handleClose = (id: string): void => {
dispatchQueries(queries.filter((queryData) => queryData.id !== id));
};
const handleClearAll = (): void => {
send('RESET');
dispatchQueries([]);
setStaging([]);
setSelectedValues([]);
};
const disabledAndEmpty = !!(
!queries.length &&
!staging.length &&
!selectedValues.length &&
disabled
);
const disabledOrEmpty = !!(
queries.length ||
staging.length ||
selectedValues.length ||
disabled
);
if (disabledAndEmpty) {
return null;
}
return (
<SearchContainer disabled={disabled}>
<div
style={{
maxWidth: disabled ? '100%' : '70%',
display: 'flex',
overflowX: 'auto',
}}
>
{map(
queries,
(query): JSX.Element => (
<QueryChip
disabled={disabled}
key={query.id}
queryData={query}
onClose={handleClose}
/>
),
)}
{map(staging, (item, idx) => (
<QueryChipItem key={uuid()}>
{idx === 0 ? convertMetricKeyToTrace(item) : item}
</QueryChipItem>
))}
</div>
{!disabled && (
<Select
placeholder={
disabledOrEmpty ? '' : 'Search and Filter based on resource attributes.'
}
disabled={disabled}
onChange={handleChange}
bordered={false}
value={selectedValues as never}
style={{ flex: 1 }}
options={optionsData.options}
mode={optionsData?.mode}
showArrow={false}
onFocus={handleFocus}
onBlur={handleBlur}
notFoundContent={
loading ? (
<span>
<Spin size="small" /> Loading...{' '}
</span>
) : (
<span>
No resource attributes available to filter. Please refer docs to send
attributes.
</span>
)
}
/>
)}
{(queries.length || staging.length || selectedValues.length) && !disabled ? (
<Button onClick={handleClearAll} icon={<CloseCircleFilled />} type="text" />
) : null}
</SearchContainer>
);
}
export default ResourceAttributesFilter;

View File

@ -1,11 +0,0 @@
export interface IOption {
label: string;
value: string;
}
export interface IResourceAttributeQuery {
id: string;
tagKey: string;
operator: string;
tagValue: string[];
}

View File

@ -1,64 +0,0 @@
import {
getResourceAttributesTagKeys,
getResourceAttributesTagValues,
} from 'api/metrics/getResourceAttributes';
import { OperatorConversions } from 'constants/resourceAttributes';
import { convertMetricKeyToTrace } from 'lib/resourceAttributes';
import { v4 as uuid } from 'uuid';
import { IOption, IResourceAttributeQuery } from './types';
export const OperatorSchema: IOption[] = OperatorConversions.map(
(operator) => ({
label: operator.label,
value: operator.label,
}),
);
export const GetTagKeys = async (): Promise<IOption[]> => {
// if (TagKeysCache) {
// return new Promise((resolve) => {
// resolve(TagKeysCache);
// });
// }
const { payload } = await getResourceAttributesTagKeys({
metricName: 'signoz_calls_total',
match: 'resource_',
});
if (!payload || !payload?.data) {
return [];
}
return payload.data.map((tagKey: string) => ({
label: convertMetricKeyToTrace(tagKey),
value: tagKey,
}));
};
export const GetTagValues = async (tagKey: string): Promise<IOption[]> => {
const { payload } = await getResourceAttributesTagValues({
tagKey,
metricName: 'signoz_calls_total',
});
if (!payload || !payload?.data) {
return [];
}
return payload.data.map((tagValue: string) => ({
label: tagValue,
value: tagValue,
}));
};
export const createQuery = (
selectedItems: Array<string | string[]> = [],
): IResourceAttributeQuery | null => {
if (selectedItems.length === 3) {
return {
id: uuid().slice(0, 8),
tagKey: selectedItems[0] as string,
operator: selectedItems[1] as string,
tagValue: selectedItems[2] as string[],
};
}
return null;
};

View File

@ -4,16 +4,14 @@ import {
databaseCallsAvgDuration,
databaseCallsRPS,
} from 'container/MetricsApplication/MetricsPageQueries/DBCallQueries';
import useResourceAttribute from 'hooks/useResourceAttribute';
import {
convertRawQueriesToTraceSelectedTags,
resourceAttributesToTagFilterItems,
} from 'lib/resourceAttributes';
} from 'hooks/useResourceAttribute/utils';
import React, { useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
import MetricReducer from 'types/reducer/metrics';
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
import { Button } from './styles';
@ -26,22 +24,19 @@ import {
function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
const { servicename } = useParams<{ servicename?: string }>();
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
(state) => state.metrics,
);
const { queries } = useResourceAttribute();
const tagFilterItems = useMemo(
() => resourceAttributesToTagFilterItems(resourceAttributeQueries) || [],
[resourceAttributeQueries],
() => resourceAttributesToTagFilterItems(queries) || [],
[queries],
);
const selectedTraceTags: string = useMemo(
() =>
JSON.stringify(
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries).concat(
...dbSystemTags,
) || [],
convertRawQueriesToTraceSelectedTags(queries).concat(...dbSystemTags) || [],
),
[resourceAttributeQueries],
[queries],
);
const legend = '{{db_system}}';
const databaseCallsRPSWidget = useMemo(

View File

@ -6,16 +6,14 @@ import {
externalCallErrorPercent,
externalCallRpsByAddress,
} from 'container/MetricsApplication/MetricsPageQueries/ExternalQueries';
import useResourceAttribute from 'hooks/useResourceAttribute';
import {
convertRawQueriesToTraceSelectedTags,
resourceAttributesToTagFilterItems,
} from 'lib/resourceAttributes';
} from 'hooks/useResourceAttribute/utils';
import React, { useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
import MetricReducer from 'types/reducer/metrics';
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
import { legend } from './constant';
@ -26,13 +24,11 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const { servicename } = useParams<{ servicename?: string }>();
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
(state) => state.metrics,
);
const { queries } = useResourceAttribute();
const tagFilterItems = useMemo(
() => resourceAttributesToTagFilterItems(resourceAttributeQueries) || [],
[resourceAttributeQueries],
() => resourceAttributesToTagFilterItems(queries) || [],
[queries],
);
const externalCallErrorWidget = useMemo(
@ -51,11 +47,8 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
);
const selectedTraceTags = useMemo(
() =>
JSON.stringify(
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) || [],
),
[resourceAttributeQueries],
() => JSON.stringify(convertRawQueriesToTraceSelectedTags(queries) || []),
[queries],
);
const externalCallDurationWidget = useMemo(

View File

@ -3,13 +3,14 @@ import Graph from 'components/Graph';
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
import ROUTES from 'constants/routes';
import FullView from 'container/GridGraphLayout/Graph/FullView/index.metricsBuilder';
import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond';
import { colors } from 'lib/getRandomColor';
import history from 'lib/history';
import useResourceAttribute from 'hooks/useResourceAttribute';
import {
convertRawQueriesToTraceSelectedTags,
resourceAttributesToTagFilterItems,
} from 'lib/resourceAttributes';
} from 'hooks/useResourceAttribute/utils';
import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond';
import { colors } from 'lib/getRandomColor';
import history from 'lib/history';
import React, { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
@ -54,20 +55,20 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
[handleSetTimeStamp],
);
const {
topOperations,
serviceOverview,
resourceAttributeQueries,
topLevelOperations,
} = useSelector<AppState, MetricReducer>((state) => state.metrics);
const { topOperations, serviceOverview, topLevelOperations } = useSelector<
AppState,
MetricReducer
>((state) => state.metrics);
const { queries } = useResourceAttribute();
const selectedTraceTags: string = JSON.stringify(
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) || [],
convertRawQueriesToTraceSelectedTags(queries) || [],
);
const tagFilterItems = useMemo(
() => resourceAttributesToTagFilterItems(resourceAttributeQueries) || [],
[resourceAttributeQueries],
() => resourceAttributesToTagFilterItems(queries) || [],
[queries],
);
const operationPerSecWidget = useMemo(

View File

@ -3,25 +3,23 @@ import { ColumnsType } from 'antd/lib/table';
import { ResizeTable } from 'components/ResizeTable';
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
import ROUTES from 'constants/routes';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
import history from 'lib/history';
import { convertRawQueriesToTraceSelectedTags } from 'lib/resourceAttributes';
import React from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import MetricReducer from 'types/reducer/metrics';
function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
(state) => state.metrics,
);
const { queries } = useResourceAttribute();
const selectedTraceTags: string = JSON.stringify(
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) || [],
convertRawQueriesToTraceSelectedTags(queries) || [],
);
const { data } = props;

View File

@ -1,11 +1,11 @@
import RouteTab from 'components/RouteTab';
import ROUTES from 'constants/routes';
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
import React, { memo } from 'react';
import { generatePath, useParams } from 'react-router-dom';
import { useLocation } from 'react-use';
import { getWidgetQueryBuilder } from './MetricsApplication.factory';
import ResourceAttributesFilter from './ResourceAttributesFilter';
import DBCall from './Tabs/DBCall';
import External from './Tabs/External';
import Overview from './Tabs/Overview';

View File

@ -2,31 +2,31 @@
export interface Typegen0 {
'@@xstate/typegen': true;
eventsCausingActions: {
onSelectOperator: 'NEXT';
onBlurPurge: 'onBlur';
onSelectTagValue: 'NEXT';
onValidateQuery: 'onBlur';
onSelectTagKey: 'NEXT';
};
internalEvents: {
'xstate.init': { type: 'xstate.init' };
};
invokeSrcNameMap: {};
missingImplementations: {
actions:
| 'onSelectOperator'
| 'onBlurPurge'
| 'onSelectOperator'
| 'onSelectTagKey'
| 'onSelectTagValue'
| 'onValidateQuery'
| 'onSelectTagKey';
services: never;
guards: never;
| 'onValidateQuery';
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
onBlurPurge: 'onBlur';
onSelectOperator: 'NEXT';
onSelectTagKey: 'NEXT';
onSelectTagValue: 'NEXT';
onValidateQuery: 'onBlur';
};
eventsCausingServices: {};
eventsCausingGuards: {};
eventsCausingDelays: {};
matchesStates: 'TagKey' | 'Operator' | 'TagValue' | 'Idle';
eventsCausingGuards: {};
eventsCausingServices: {};
matchesStates: 'Idle' | 'Operator' | 'TagKey' | 'TagValue';
tags: never;
}

View File

@ -0,0 +1,77 @@
import { CloseCircleFilled } from '@ant-design/icons';
import { Button, Select, Spin } from 'antd';
import useResourceAttribute, {
isResourceEmpty,
} from 'hooks/useResourceAttribute';
import { convertMetricKeyToTrace } from 'hooks/useResourceAttribute/utils';
import React, { useMemo } from 'react';
import { v4 as uuid } from 'uuid';
import QueryChip from './components/QueryChip';
import { QueryChipItem, SearchContainer } from './styles';
function ResourceAttributesFilter(): JSX.Element | null {
const {
queries,
staging,
handleClose,
handleBlur,
handleClearAll,
handleFocus,
handleChange,
selectedQuery,
optionsData,
loading,
} = useResourceAttribute();
const isEmpty = useMemo(
() => isResourceEmpty(queries, staging, selectedQuery),
[queries, selectedQuery, staging],
);
return (
<SearchContainer>
<div>
{queries.map((query) => (
<QueryChip key={query.id} queryData={query} onClose={handleClose} />
))}
{staging.map((query, idx) => (
<QueryChipItem key={uuid()}>
{idx === 0 ? convertMetricKeyToTrace(query) : query}
</QueryChipItem>
))}
</div>
<Select
placeholder={!isEmpty && 'Search and Filter based on resource attributes.'}
onChange={handleChange}
bordered={false}
value={selectedQuery as never}
style={{ flex: 1 }}
options={optionsData.options}
mode={optionsData?.mode}
showArrow={false}
onClick={handleFocus}
onBlur={handleBlur}
onClear={handleClearAll}
notFoundContent={
loading ? (
<span>
<Spin size="small" /> Loading...{' '}
</span>
) : (
<span>
No resource attributes available to filter. Please refer docs to send
attributes.
</span>
)
}
/>
{queries.length || staging.length || selectedQuery.length ? (
<Button onClick={handleClearAll} icon={<CloseCircleFilled />} type="text" />
) : null}
</SearchContainer>
);
}
export default ResourceAttributesFilter;

View File

@ -0,0 +1,23 @@
import { convertMetricKeyToTrace } from 'hooks/useResourceAttribute/utils';
import React from 'react';
import { QueryChipContainer, QueryChipItem } from '../../styles';
import { IQueryChipProps } from './types';
function QueryChip({ queryData, onClose }: IQueryChipProps): JSX.Element {
const onCloseHandler = (): void => {
onClose(queryData.id);
};
return (
<QueryChipContainer>
<QueryChipItem>{convertMetricKeyToTrace(queryData.tagKey)}</QueryChipItem>
<QueryChipItem>{queryData.operator}</QueryChipItem>
<QueryChipItem closable onClose={onCloseHandler}>
{queryData.tagValue.join(', ')}
</QueryChipItem>
</QueryChipContainer>
);
}
export default QueryChip;

View File

@ -0,0 +1,3 @@
import QueryChip from './QueryChip';
export default QueryChip;

View File

@ -0,0 +1,6 @@
import { IResourceAttribute } from 'hooks/useResourceAttribute/types';
export interface IQueryChipProps {
queryData: IResourceAttribute;
onClose: (id: string) => void;
}

View File

@ -0,0 +1,3 @@
import ResourceAttributesFilter from './ResourceAttributesFilter';
export default ResourceAttributesFilter;

View File

@ -2,9 +2,7 @@ import { grey } from '@ant-design/colors';
import { Tag } from 'antd';
import styled from 'styled-components';
export const SearchContainer = styled.div<{
disabled: boolean;
}>`
export const SearchContainer = styled.div`
width: 100%;
display: flex;
align-items: center;
@ -12,8 +10,8 @@ export const SearchContainer = styled.div<{
padding: 0.2rem;
margin: 1rem 0;
border: 1px solid #ccc5;
${({ disabled }): string => (disabled ? `cursor: not-allowed;` : '')}
`;
export const QueryChipContainer = styled.span`
display: flex;
align-items: center;

View File

@ -29,6 +29,7 @@ function SideNav(): JSX.Element {
const [collapsed, setCollapsed] = useState<boolean>(
getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
);
const { search } = useLocation();
const { currentVersion, latestVersion, isCurrentVersionError } = useSelector<
AppState,
AppReducer
@ -47,11 +48,15 @@ function SideNav(): JSX.Element {
const onClickHandler = useCallback(
(to: string) => {
const queryParams = new URLSearchParams(search);
const url = queryParams.toString();
if (pathname !== to) {
history.push(to);
history.push(`${to}?${url}`);
}
},
[pathname],
[pathname, search],
);
const onClickSlackHandler = (): void => {

View File

@ -0,0 +1,181 @@
import { useMachine } from '@xstate/react';
import { encode } from 'js-base64';
import history from 'lib/history';
import React, { useCallback, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { ResourceContext } from './context';
import { ResourceAttributesFilterMachine } from './machine';
import {
IResourceAttribute,
IResourceAttributeProps,
OptionsData,
} from './types';
import {
createQuery,
getResourceAttributeQueriesFromURL,
GetTagKeys,
GetTagValues,
OperatorSchema,
resourceAttributesQueryToPromQL,
} from './utils';
function ResourceProvider({ children }: Props): JSX.Element {
const { pathname } = useLocation();
const [loading, setLoading] = useState(true);
const [selectedQuery, setSelectedQueries] = useState<string[]>([]);
const [staging, setStaging] = useState<string[]>([]);
const [queries, setQueries] = useState<IResourceAttribute[]>(
getResourceAttributeQueriesFromURL(),
);
const [promQLQuery, setpromQLQuery] = useState<string>(
resourceAttributesQueryToPromQL(queries),
);
const [optionsData, setOptionsData] = useState<OptionsData>({
mode: undefined,
options: [],
});
const handleLoading = (isLoading: boolean): void => {
setLoading(isLoading);
if (isLoading) {
setOptionsData({ mode: undefined, options: [] });
}
};
const dispatchQueries = useCallback(
(queries: IResourceAttribute[]): void => {
history.replace({
pathname,
search:
queries && queries.length
? `?resourceAttribute=${encode(JSON.stringify(queries))}`
: '',
});
setQueries(queries);
setpromQLQuery(resourceAttributesQueryToPromQL(queries));
},
[pathname],
);
const [state, send] = useMachine(ResourceAttributesFilterMachine, {
actions: {
onSelectTagKey: () => {
handleLoading(true);
GetTagKeys()
.then((tagKeys) => setOptionsData({ options: tagKeys, mode: undefined }))
.finally(() => {
handleLoading(false);
});
},
onSelectOperator: () => {
setOptionsData({ options: OperatorSchema, mode: undefined });
},
onSelectTagValue: () => {
handleLoading(true);
GetTagValues(staging[0])
.then((tagValuesOptions) =>
setOptionsData({ options: tagValuesOptions, mode: 'multiple' }),
)
.finally(() => {
handleLoading(false);
});
},
onBlurPurge: () => {
setSelectedQueries([]);
setStaging([]);
},
onValidateQuery: (): void => {
if (staging.length < 2 || selectedQuery.length === 0) {
return;
}
const generatedQuery = createQuery([...staging, selectedQuery]);
if (generatedQuery) {
dispatchQueries([...queries, generatedQuery]);
}
},
},
});
const handleFocus = useCallback((): void => {
if (state.value === 'Idle') {
send('NEXT');
}
}, [send, state.value]);
const handleBlur = useCallback((): void => {
send('onBlur');
}, [send]);
const handleChange = useCallback(
(value: string): void => {
if (!optionsData.mode) {
setStaging((prevStaging) => [...prevStaging, value]);
setSelectedQueries([]);
send('NEXT');
return;
}
setSelectedQueries([...value]);
},
[optionsData.mode, send],
);
const handleClose = useCallback(
(id: string): void => {
dispatchQueries(queries.filter((queryData) => queryData.id !== id));
},
[dispatchQueries, queries],
);
const handleClearAll = useCallback(() => {
send('RESET');
dispatchQueries([]);
setStaging([]);
setQueries([]);
setOptionsData({ mode: undefined, options: [] });
}, [dispatchQueries, send]);
const value: IResourceAttributeProps = useMemo(
() => ({
queries,
staging,
promQLQuery,
handleClearAll,
handleClose,
handleBlur,
handleFocus,
loading,
handleChange,
selectedQuery,
optionsData,
}),
[
handleBlur,
handleChange,
handleClearAll,
handleClose,
handleFocus,
loading,
promQLQuery,
queries,
staging,
selectedQuery,
optionsData,
],
);
return (
<ResourceContext.Provider value={value}>{children}</ResourceContext.Provider>
);
}
interface Props {
children: React.ReactNode;
}
export default ResourceProvider;

View File

@ -0,0 +1,7 @@
import { createContext } from 'react';
import { IResourceAttributeProps } from './types';
export const ResourceContext = createContext<IResourceAttributeProps>(
{} as IResourceAttributeProps,
);

View File

@ -0,0 +1,7 @@
import ResourceProvider from './ResourceProvider';
import useResourceAttribute from './useResourceAttribute';
import { convertMetricKeyToTrace, isResourceEmpty } from './utils';
export default useResourceAttribute;
export { convertMetricKeyToTrace, isResourceEmpty, ResourceProvider };

View File

@ -0,0 +1,61 @@
import { createMachine } from 'xstate';
export const ResourceAttributesFilterMachine =
/** @xstate-layout N4IgpgJg5mDOIC5QBECGsAWAjA9qgThAAQDKYBAxhkQIIB2xAYgJYA2ALmPgHQAqqUANJgAngGIAcgFEAGrwDaABgC6iUAAccsZu2Y46akAA9EATkUB2bgEYAbBYsBWWwA5HAFkW3F7gDQgRRABaU3duFwsXAGZbWwAmF3co01jTAF80-zRMXAJiMkpqeiY2Th5+IVExfQAhVgBXfCVVJBBNbV19QxMEcys7B2c3T28-AOC4xUduKItrSbiEuNMo6zcMrPRsPEJScnwqWgYiFg4uPgFhcQAlKRIpBRVDdp09A1aevpt7J1cPLx8-kCCCCcUcURmcwWSxWa0cGxA2W2eT2hSOJTOPAA8uouKh2Dh8JJZI8WhotK8uh9EPM4tYZl4IrZHNY1rZrEDgqFwpEoi43HEnMt3NYEUjcrsCgcisdTmVuDi8QSibUGk0nq0Xp13qAerT6VFGRZmayXOzOSDJtNZrT3I44t5bHaLGKthL8vtDsUTqVzor8PjCWJbvdSc8KdrujTFgajSa2RzxpbwZDbfbHc7XTkdh60d65ecKgA1VANMDVOh1RrNcMdN6GYFBayOKw2xZ2h1eZ3+PX2+mxFzWEWmFymBxRLPIyWemUY+XF0v1cshh41zUR+vUhDNuncAdD6wjscWKIW0FTVPt9NdluT92o6Xon2Y7gASQgrHL0jka-JdapuqIPEcTcIoihxHyTh2Pa-JntyETRO4ngig6yTuBkmQgHQOAQHAhjijmD5erKvr4LWlI6sYiDJIo3Aiieh7Gk4UynkmQRRJ44TARYijJC4AJRBOmEESiUrEXOhaXKI5GRluPG0SkI7uIKhr2vaZ7Nq2cxrGByQWKYpiisJbqEWJs7PvK-qBmR67-pReq6aB1g+DEkEcaYcQaS2l7gTCqzrMZ2aiTOT4FuUAglmWMmboB258hCESmNeLgQR4jheVp8y+SlsIBZsQXTnmJEvu+n7RQBVEIEkLh0dYDFjvYjgsRlqY6bxY4GUZGRAA */
createMachine({
tsTypes: {} as import('./machine.typegen').Typegen0,
initial: 'Idle',
states: {
TagKey: {
on: {
NEXT: {
actions: 'onSelectOperator',
target: 'Operator',
},
onBlur: {
actions: 'onBlurPurge',
target: 'Idle',
},
RESET: {
target: 'Idle',
},
},
},
Operator: {
on: {
NEXT: {
actions: 'onSelectTagValue',
target: 'TagValue',
},
onBlur: {
actions: 'onBlurPurge',
target: 'Idle',
},
RESET: {
target: 'Idle',
},
},
},
TagValue: {
on: {
onBlur: {
actions: ['onValidateQuery', 'onBlurPurge'],
target: 'Idle',
},
RESET: {
target: 'Idle',
},
},
},
Idle: {
on: {
NEXT: {
actions: 'onSelectTagKey',
description: 'Select Category',
target: 'TagKey',
},
},
},
},
id: 'ResourceAttributesFilterMachine',
});

View File

@ -0,0 +1,37 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
"xstate.init": { type: "xstate.init" };
};
invokeSrcNameMap: {
};
missingImplementations: {
actions: "onBlurPurge" | "onSelectOperator" | "onSelectTagKey" | "onSelectTagValue" | "onValidateQuery";
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
"onBlurPurge": "onBlur";
"onSelectOperator": "NEXT";
"onSelectTagKey": "NEXT";
"onSelectTagValue": "NEXT";
"onValidateQuery": "onBlur";
};
eventsCausingDelays: {
};
eventsCausingGuards: {
};
eventsCausingServices: {
};
matchesStates: "Idle" | "Operator" | "TagKey" | "TagValue";
tags: never;
}

View File

@ -0,0 +1,32 @@
export interface IResourceAttribute {
id: string;
tagKey: string;
operator: string;
tagValue: string[];
}
export interface IOption {
label: string;
value: string;
}
type Modes = 'tags' | 'multiple';
export interface OptionsData {
mode?: Modes;
options: IOption[];
}
export interface IResourceAttributeProps {
queries: IResourceAttribute[];
staging: string[];
promQLQuery: string;
handleClearAll: VoidFunction;
handleClose: (id: string) => void;
handleBlur: VoidFunction;
handleFocus: VoidFunction;
loading: boolean;
handleChange: (value: string) => void;
selectedQuery: string[];
optionsData: OptionsData;
}

View File

@ -0,0 +1,9 @@
import { useContext } from 'react';
import { ResourceContext } from './context';
import { IResourceAttributeProps } from './types';
const useResourceAttribute = (): IResourceAttributeProps =>
useContext(ResourceContext);
export default useResourceAttribute;

View File

@ -0,0 +1,161 @@
import {
getResourceAttributesTagKeys,
getResourceAttributesTagValues,
} from 'api/metrics/getResourceAttributes';
import { OperatorConversions } from 'constants/resourceAttributes';
import {
IOption,
IResourceAttribute,
IResourceAttributeProps,
} from 'hooks/useResourceAttribute/types';
import { decode } from 'js-base64';
import history from 'lib/history';
import { IQueryBuilderTagFilterItems } from 'types/api/dashboard/getAll';
import { OperatorValues, Tags } from 'types/reducer/trace';
import { v4 as uuid } from 'uuid';
/**
* resource_x_y -> x.y
*/
export const convertMetricKeyToTrace = (key: string): string => {
const splittedKey = key.split('_');
if (splittedKey.length <= 1) {
return '';
}
return splittedKey.splice(1).join('.');
};
/**
* x.y -> resource_x_y
*/
export const convertTraceKeyToMetric = (key: string): string => {
const splittedKey = key.split('.');
return `resource_${splittedKey.join('_')}`;
};
export const convertOperatorLabelToMetricOperator = (label: string): string =>
OperatorConversions.find((operator) => operator.label === label)
?.metricValue || '';
export const convertOperatorLabelToTraceOperator = (
label: string,
): OperatorValues =>
OperatorConversions.find((operator) => operator.label === label)
?.traceValue as OperatorValues;
export const convertRawQueriesToTraceSelectedTags = (
queries: IResourceAttribute[],
tagType = 'ResourceAttribute',
): Tags[] =>
queries.map((query) => ({
Key: convertMetricKeyToTrace(query.tagKey),
Operator: convertOperatorLabelToTraceOperator(query.operator),
StringValues: query.tagValue,
NumberValues: [],
BoolValues: [],
TagType: tagType,
}));
/**
* Converts Resource Attribute Queries to PromQL query string
*/
export const resourceAttributesQueryToPromQL = (
queries: IResourceAttribute[],
): string => {
let parsedQueryString = '';
if (Array.isArray(queries))
queries.forEach((query) => {
parsedQueryString += `, ${
query.tagKey
}${convertOperatorLabelToMetricOperator(
query.operator,
)}"${query.tagValue.join('|')}"`;
});
return parsedQueryString;
};
/* Convert resource attributes to tagFilter items for queryBuilder */
export const resourceAttributesToTagFilterItems = (
queries: IResourceAttribute[],
): IQueryBuilderTagFilterItems[] =>
queries.map((res) => ({
id: `${res.id}`,
key: `${res.tagKey}`,
op: `${res.operator}`,
value: `${res.tagValue}`.split(','),
}));
export const OperatorSchema: IOption[] = OperatorConversions.map(
(operator) => ({
label: operator.label,
value: operator.label,
}),
);
export const GetTagKeys = async (): Promise<IOption[]> => {
const { payload } = await getResourceAttributesTagKeys({
metricName: 'signoz_calls_total',
match: 'resource_',
});
if (!payload || !payload?.data) {
return [];
}
return payload.data.map((tagKey: string) => ({
label: convertMetricKeyToTrace(tagKey),
value: tagKey,
}));
};
export const GetTagValues = async (tagKey: string): Promise<IOption[]> => {
const { payload } = await getResourceAttributesTagValues({
tagKey,
metricName: 'signoz_calls_total',
});
if (!payload || !payload?.data) {
return [];
}
return payload.data.map((tagValue: string) => ({
label: tagValue,
value: tagValue,
}));
};
export const createQuery = (
selectedItems: Array<string | string[]> = [],
): IResourceAttribute | null => {
if (selectedItems.length === 3) {
return {
id: uuid().slice(0, 8),
tagKey: selectedItems[0] as string,
operator: selectedItems[1] as string,
tagValue: selectedItems[2] as string[],
};
}
return null;
};
export function getResourceAttributeQueriesFromURL(): IResourceAttribute[] {
const resourceAttributeQuery = new URLSearchParams(
history.location.search,
).get('resourceAttribute');
try {
if (resourceAttributeQuery) {
return JSON.parse(decode(resourceAttributeQuery)) as IResourceAttribute[];
}
} catch (error) {
console.error(error);
}
return [];
}
export const isResourceEmpty = (
queries: IResourceAttributeProps['queries'],
staging: IResourceAttributeProps['staging'],
selectedQuery: IResourceAttributeProps['selectedQuery'],
): boolean => !!(queries.length || staging.length || selectedQuery.length);

View File

@ -1,76 +0,0 @@
import { OperatorConversions } from 'constants/resourceAttributes';
import { IResourceAttributeQuery } from 'container/MetricsApplication/ResourceAttributesFilter/types';
import { IQueryBuilderTagFilterItems } from 'types/api/dashboard/getAll';
import { OperatorValues, Tags } from 'types/reducer/trace';
/**
* resource_x_y -> x.y
*/
export const convertMetricKeyToTrace = (key: string): string => {
const splittedKey = key.split('_');
if (splittedKey.length <= 1) {
return '';
}
return splittedKey.splice(1).join('.');
};
/**
* x.y -> resource_x_y
*/
export const convertTraceKeyToMetric = (key: string): string => {
const splittedKey = key.split('.');
return `resource_${splittedKey.join('_')}`;
};
export const convertOperatorLabelToMetricOperator = (label: string): string =>
OperatorConversions.find((operator) => operator.label === label)
?.metricValue || '';
export const convertOperatorLabelToTraceOperator = (
label: string,
): OperatorValues =>
OperatorConversions.find((operator) => operator.label === label)
?.traceValue as OperatorValues;
export const convertRawQueriesToTraceSelectedTags = (
queries: IResourceAttributeQuery[],
): Tags[] =>
queries.map((query) => ({
Key: convertMetricKeyToTrace(query.tagKey),
Operator: convertOperatorLabelToTraceOperator(query.operator),
StringValues: query.tagValue,
NumberValues: [],
BoolValues: [],
}));
/**
* Converts Resource Attribute Queries to PromQL query string
*/
export const resourceAttributesQueryToPromQL = (
queries: IResourceAttributeQuery[],
): string => {
let parsedQueryString = '';
if (Array.isArray(queries))
queries.forEach((query) => {
parsedQueryString += `, ${
query.tagKey
}${convertOperatorLabelToMetricOperator(
query.operator,
)}"${query.tagValue.join('|')}"`;
});
return parsedQueryString;
};
/* Convert resource attributes to tagFilter items for queryBuilder */
export const resourceAttributesToTagFilterItems = (
queries: IResourceAttributeQuery[],
): IQueryBuilderTagFilterItems[] =>
queries.map((res) => ({
id: `${res.id}`,
key: `${res.tagKey}`,
op: `${res.operator}`,
value: `${res.tagValue}`.split(','),
}));

View File

@ -1,6 +1,7 @@
import RouteTab from 'components/RouteTab';
import ROUTES from 'constants/routes';
import AllErrorsContainer from 'container/AllError';
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
import React from 'react';
import { useTranslation } from 'react-i18next';
@ -8,18 +9,21 @@ function AllErrors(): JSX.Element {
const { t } = useTranslation();
return (
<RouteTab
{...{
routes: [
{
Component: AllErrorsContainer,
name: t('routes.all_errors'),
route: ROUTES.ALL_ERROR,
},
],
activeKey: t('routes.all_errors'),
}}
/>
<>
<ResourceAttributesFilter />
<RouteTab
{...{
routes: [
{
Component: AllErrorsContainer,
name: t('routes.all_errors'),
route: ROUTES.ALL_ERROR,
},
],
activeKey: t('routes.all_errors'),
}}
/>
</>
);
}

View File

@ -1,7 +1,8 @@
import { Typography } from 'antd';
import Spinner from 'components/Spinner';
import MetricsApplicationContainer from 'container/MetricsApplication';
import { convertRawQueriesToTraceSelectedTags } from 'lib/resourceAttributes';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
import React, { useEffect, useMemo } from 'react';
import { connect, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
@ -27,16 +28,11 @@ function MetricsApplication({ getInitialData }: MetricsProps): JSX.Element {
>((state) => state.metrics);
const { servicename } = useParams<ServiceProps>();
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
(state) => state.metrics,
);
const { queries } = useResourceAttribute();
const selectedTags = useMemo(
() =>
(convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) as Tags[]) ||
[],
[resourceAttributeQueries],
() => (convertRawQueriesToTraceSelectedTags(queries) as Tags[]) || [],
[queries],
);
useEffect(() => {

View File

@ -3,10 +3,11 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
import ReleaseNote from 'components/ReleaseNote';
import Spinner from 'components/Spinner';
import { SKIP_ONBOARDING } from 'constants/onboarding';
import ResourceAttributesFilter from 'container/MetricsApplication/ResourceAttributesFilter';
import MetricTable from 'container/MetricsTable';
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
import { useNotifications } from 'hooks/useNotifications';
import { convertRawQueriesToTraceSelectedTags } from 'lib/resourceAttributes';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
import React, { useEffect, useMemo } from 'react';
import { connect, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
@ -25,12 +26,9 @@ function Metrics({ getService }: MetricsProps): JSX.Element {
GlobalReducer
>((state) => state.globalTime);
const location = useLocation();
const {
services,
resourceAttributeQueries,
error,
errorMessage,
} = useSelector<AppState, MetricReducer>((state) => state.metrics);
const { services, error, errorMessage } = useSelector<AppState, MetricReducer>(
(state) => state.metrics,
);
const { notifications } = useNotifications();
useEffect(() => {
@ -41,12 +39,13 @@ function Metrics({ getService }: MetricsProps): JSX.Element {
}
}, [error, errorMessage, notifications]);
const { queries } = useResourceAttribute();
const selectedTags = useMemo(
() =>
(convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) as Tags[]) ||
[],
[resourceAttributeQueries],
() => (convertRawQueriesToTraceSelectedTags(queries, '') as Tags[]) || [],
[queries],
);
const isSkipped = getLocalStorageKey(SKIP_ONBOARDING) === 'true';
useEffect(() => {

View File

@ -1,55 +0,0 @@
import { IResourceAttributeQuery } from 'container/MetricsApplication/ResourceAttributesFilter/types';
import { decode, encode } from 'js-base64';
import history from 'lib/history';
import { resourceAttributesQueryToPromQL } from 'lib/resourceAttributes';
import { SET_RESOURCE_ATTRIBUTE_QUERIES } from 'types/actions/metrics';
export function GetResourceAttributeQueriesFromURL():
| IResourceAttributeQuery[]
| null {
const resourceAttributeQuery = new URLSearchParams(
history.location.search,
).get('resourceAttribute');
try {
if (resourceAttributeQuery) {
return JSON.parse(
decode(resourceAttributeQuery),
) as IResourceAttributeQuery[];
}
} catch (error) {
console.error(error);
}
return null;
}
export const SetResourceAttributeQueriesFromURL = (
queries: IResourceAttributeQuery[],
): void => {
history.push({
pathname: history.location.pathname,
search:
queries && queries.length
? `?resourceAttribute=${encode(JSON.stringify(queries))}`
: '',
});
};
export const SetResourceAttributeQueries = (
queries: IResourceAttributeQuery[],
): {
type: typeof SET_RESOURCE_ATTRIBUTE_QUERIES;
payload: {
queries: IResourceAttributeQuery[];
promQLQuery: string;
};
} => {
SetResourceAttributeQueriesFromURL(queries);
return {
type: SET_RESOURCE_ATTRIBUTE_QUERIES,
payload: {
queries,
promQLQuery: resourceAttributesQueryToPromQL(queries),
},
};
};

View File

@ -1,5 +1,3 @@
import { resourceAttributesQueryToPromQL } from 'lib/resourceAttributes';
import { GetResourceAttributeQueriesFromURL } from 'store/actions/metrics/setResourceAttributeQueries';
import {
GET_INITIAL_APPLICATION_ERROR,
GET_INITIAL_APPLICATION_LOADING,
@ -9,7 +7,6 @@ import {
GET_SERVICE_LIST_SUCCESS,
MetricsActions,
RESET_INITIAL_APPLICATION_DATA,
SET_RESOURCE_ATTRIBUTE_QUERIES,
} from 'types/actions/metrics';
import InitialValueTypes from 'types/reducer/metrics';
@ -25,10 +22,6 @@ const InitialValue: InitialValueTypes = {
externalAverageDuration: [],
externalError: [],
serviceOverview: [],
resourceAttributeQueries: GetResourceAttributeQueriesFromURL() || [],
resourceAttributePromQLQuery: resourceAttributesQueryToPromQL(
GetResourceAttributeQueriesFromURL() || [],
),
topLevelOperations: [],
};
@ -110,15 +103,6 @@ const metrics = (
};
}
case SET_RESOURCE_ATTRIBUTE_QUERIES: {
const { queries, promQLQuery } = action.payload;
return {
...state,
resourceAttributeQueries: queries,
resourceAttributePromQLQuery: promQLQuery,
};
}
default:
return state;
}

View File

@ -1,8 +1,3 @@
// import { DBOverView } from 'types/api/metrics/getDBOverview';
// import { ExternalAverageDuration } from 'types/api/metrics/getExternalAverageDuration';
// import { ExternalError } from 'types/api/metrics/getExternalError';
// import { ExternalService } from 'types/api/metrics/getExternalService';
import { IResourceAttributeQuery } from 'container/MetricsApplication/ResourceAttributesFilter/types';
import { ServicesList } from 'types/api/metrics/getService';
import { ServiceOverview } from 'types/api/metrics/getServiceOverview';
import { TopOperations } from 'types/api/metrics/getTopOperations';
@ -15,7 +10,6 @@ export const GET_INITIAL_APPLICATION_LOADING =
export const GET_INITIAL_APPLICATION_ERROR = 'GET_INITIAL_APPLICATION_ERROR';
export const GET_INTIAL_APPLICATION_DATA = 'GET_INTIAL_APPLICATION_DATA';
export const RESET_INITIAL_APPLICATION_DATA = 'RESET_INITIAL_APPLICATION_DATA';
export const SET_RESOURCE_ATTRIBUTE_QUERIES = 'SET_RESOURCE_ATTRIBUTE_QUERIES';
export interface GetServiceList {
type: typeof GET_SERVICE_LIST_SUCCESS;
@ -52,18 +46,9 @@ export interface ResetInitialApplicationData {
type: typeof RESET_INITIAL_APPLICATION_DATA;
}
export interface SetResourceAttributeQueries {
type: typeof SET_RESOURCE_ATTRIBUTE_QUERIES;
payload: {
queries: IResourceAttributeQuery[];
promQLQuery: string;
};
}
export type MetricsActions =
| GetServiceListError
| GetServiceListLoading
| GetServiceList
| GetInitialApplicationData
| ResetInitialApplicationData
| SetResourceAttributeQueries;
| ResetInitialApplicationData;

View File

@ -1,4 +1,5 @@
import { GlobalTime } from 'types/actions/globalTime';
import { Tags } from 'types/reducer/trace';
export type Order = 'ascending' | 'descending';
export type OrderBy =
@ -17,6 +18,7 @@ export interface Props {
offset?: number;
exceptionType?: string;
serviceName?: string;
tags?: Tags[];
}
export interface Exception {

View File

@ -1,10 +1,12 @@
import { GlobalTime } from 'types/actions/globalTime';
import { Tags } from 'types/reducer/trace';
export type Props = {
start: GlobalTime['minTime'];
end: GlobalTime['minTime'];
exceptionType: string;
serviceName: string;
tags: Tags[];
};
export type PayloadProps = number;

View File

@ -1,4 +1,3 @@
import { IResourceAttributeQuery } from 'container/MetricsApplication/ResourceAttributesFilter/types';
import { DBOverView } from 'types/api/metrics/getDBOverview';
import { ExternalAverageDuration } from 'types/api/metrics/getExternalAverageDuration';
import { ExternalError } from 'types/api/metrics/getExternalError';
@ -19,8 +18,6 @@ interface MetricReducer {
externalAverageDuration: ExternalAverageDuration[];
externalError: ExternalError[];
serviceOverview: ServiceOverview[];
resourceAttributeQueries: IResourceAttributeQuery[];
resourceAttributePromQLQuery: string;
topLevelOperations: string[];
}