mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 16:19:04 +08:00
feat: tag filtering frontend changes (#2116)
feat: tag filtering frontend changes
This commit is contained in:
parent
ba6818f487
commit
05ce03e67d
@ -12,7 +12,9 @@ const getSpans = async (
|
|||||||
const updatedSelectedTags = props.selectedTags.map((e) => ({
|
const updatedSelectedTags = props.selectedTags.map((e) => ({
|
||||||
Key: e.Key[0],
|
Key: e.Key[0],
|
||||||
Operator: e.Operator,
|
Operator: e.Operator,
|
||||||
Values: e.Values,
|
StringValues: e.StringValues,
|
||||||
|
NumberValues: e.NumberValues,
|
||||||
|
BoolValues: e.BoolValues,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const exclude: string[] = [];
|
const exclude: string[] = [];
|
||||||
|
@ -30,7 +30,9 @@ const getSpanAggregate = async (
|
|||||||
const updatedSelectedTags = props.selectedTags.map((e) => ({
|
const updatedSelectedTags = props.selectedTags.map((e) => ({
|
||||||
Key: e.Key[0],
|
Key: e.Key[0],
|
||||||
Operator: e.Operator,
|
Operator: e.Operator,
|
||||||
Values: e.Values,
|
StringValues: e.StringValues,
|
||||||
|
NumberValues: e.NumberValues,
|
||||||
|
BoolValues: e.BoolValues,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const other = Object.fromEntries(props.selectedFilter);
|
const other = Object.fromEntries(props.selectedFilter);
|
||||||
|
@ -11,9 +11,11 @@ const getTagValue = async (
|
|||||||
const response = await axios.post<PayloadProps>(`/getTagValues`, {
|
const response = await axios.post<PayloadProps>(`/getTagValues`, {
|
||||||
start: props.start.toString(),
|
start: props.start.toString(),
|
||||||
end: props.end.toString(),
|
end: props.end.toString(),
|
||||||
tagKey: props.tagKey,
|
tagKey: {
|
||||||
|
Key: props.tagKey.Key,
|
||||||
|
Type: props.tagKey.Type,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
error: null,
|
error: null,
|
||||||
|
@ -8,11 +8,11 @@ export const OperatorConversions: Array<{
|
|||||||
{
|
{
|
||||||
label: 'IN',
|
label: 'IN',
|
||||||
metricValue: '=~',
|
metricValue: '=~',
|
||||||
traceValue: 'in',
|
traceValue: 'In',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Not IN',
|
label: 'Not IN',
|
||||||
metricValue: '!~',
|
metricValue: '!~',
|
||||||
traceValue: 'not in',
|
traceValue: 'NotIn',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { AutoComplete, AutoCompleteProps, Input, notification } from 'antd';
|
import { AutoComplete, Input } from 'antd';
|
||||||
import getTagFilters from 'api/trace/getTagFilter';
|
import getTagFilters from 'api/trace/getTagFilter';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useQuery } from 'react-query';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import { TraceReducer } from 'types/reducer/trace';
|
import { TraceReducer } from 'types/reducer/trace';
|
||||||
|
|
||||||
|
import { getTagKeyOptions, onTagKeySelect } from './utils';
|
||||||
|
|
||||||
function TagsKey(props: TagsKeysProps): JSX.Element {
|
function TagsKey(props: TagsKeysProps): JSX.Element {
|
||||||
const [selectLoading, setSelectLoading] = useState<boolean>(false);
|
|
||||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
@ -18,64 +20,48 @@ function TagsKey(props: TagsKeysProps): JSX.Element {
|
|||||||
|
|
||||||
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
|
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||||
|
|
||||||
const [options, setOptions] = useState<AutoCompleteProps['options']>([]);
|
const { isLoading, data } = useQuery(
|
||||||
|
[
|
||||||
|
'getTagKeys',
|
||||||
|
globalTime.minTime,
|
||||||
|
globalTime.maxTime,
|
||||||
|
traces.selectedFilter,
|
||||||
|
traces.isFilterExclude,
|
||||||
|
],
|
||||||
|
{
|
||||||
|
queryFn: () =>
|
||||||
|
getTagFilters({
|
||||||
|
start: globalTime.minTime,
|
||||||
|
end: globalTime.maxTime,
|
||||||
|
other: Object.fromEntries(traces.selectedFilter),
|
||||||
|
isFilterExclude: traces.isFilterExclude,
|
||||||
|
}),
|
||||||
|
cacheTime: 120000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const onSearchHandler = useCallback(async () => {
|
const options = useMemo(() => getTagKeyOptions(data?.payload), [data]);
|
||||||
try {
|
|
||||||
setSelectLoading(true);
|
|
||||||
const response = await getTagFilters({
|
|
||||||
start: globalTime.minTime,
|
|
||||||
end: globalTime.maxTime,
|
|
||||||
other: Object.fromEntries(traces.selectedFilter),
|
|
||||||
isFilterExclude: traces.isFilterExclude,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
const onSelectHandler = useCallback(
|
||||||
if (response.payload === null) {
|
(value: unknown) =>
|
||||||
setOptions([
|
onTagKeySelect(
|
||||||
{
|
value,
|
||||||
value: '',
|
options,
|
||||||
label: 'No tags available',
|
setSelectedKey,
|
||||||
},
|
setLocalSelectedTags,
|
||||||
]);
|
index,
|
||||||
} else {
|
tag,
|
||||||
setOptions(
|
),
|
||||||
response.payload.map((e) => ({
|
[index, options, setLocalSelectedTags, tag],
|
||||||
value: e.tagKeys,
|
);
|
||||||
label: e.tagKeys,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
notification.error({
|
|
||||||
message: response.error || 'Something went wrong',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setSelectLoading(false);
|
|
||||||
} catch (error) {
|
|
||||||
notification.error({
|
|
||||||
message: 'Something went wrong',
|
|
||||||
});
|
|
||||||
setSelectLoading(false);
|
|
||||||
}
|
|
||||||
}, [globalTime, traces]);
|
|
||||||
|
|
||||||
const counter = useRef(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (counter.current === 0 && selectedKey.length === 0) {
|
|
||||||
counter.current = 1;
|
|
||||||
onSearchHandler();
|
|
||||||
}
|
|
||||||
}, [onSearchHandler, selectedKey.length]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
dropdownClassName="certain-category-search-dropdown"
|
|
||||||
dropdownMatchSelectWidth={500}
|
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
value={selectedKey}
|
value={selectedKey}
|
||||||
allowClear
|
allowClear
|
||||||
|
disabled={isLoading}
|
||||||
|
notFoundContent="No tags available"
|
||||||
showSearch
|
showSearch
|
||||||
options={options?.map((e) => ({
|
options={options?.map((e) => ({
|
||||||
label: e.label?.toString(),
|
label: e.label?.toString(),
|
||||||
@ -85,27 +71,9 @@ function TagsKey(props: TagsKeysProps): JSX.Element {
|
|||||||
option?.label?.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
option?.label?.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
||||||
}
|
}
|
||||||
onChange={(e): void => setSelectedKey(e)}
|
onChange={(e): void => setSelectedKey(e)}
|
||||||
onSelect={(value: unknown): void => {
|
onSelect={onSelectHandler}
|
||||||
if (
|
|
||||||
typeof value === 'string' &&
|
|
||||||
options &&
|
|
||||||
options.find((option) => option.value === value)
|
|
||||||
) {
|
|
||||||
setSelectedKey(value);
|
|
||||||
|
|
||||||
setLocalSelectedTags((tags) => [
|
|
||||||
...tags.slice(0, index),
|
|
||||||
{
|
|
||||||
Key: [value],
|
|
||||||
Operator: tag.Operator,
|
|
||||||
Values: tag.Values,
|
|
||||||
},
|
|
||||||
...tags.slice(index + 1, tags.length),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Input disabled={selectLoading} placeholder="Please select" />
|
<Input placeholder="Please select" />
|
||||||
</AutoComplete>
|
</AutoComplete>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,81 +1,169 @@
|
|||||||
import { Select } from 'antd';
|
import { Select } from 'antd';
|
||||||
|
import { BaseOptionType } from 'antd/es/select';
|
||||||
import getTagValue from 'api/trace/getTagValue';
|
import getTagValue from 'api/trace/getTagValue';
|
||||||
import React, { useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import { TraceReducer } from 'types/reducer/trace';
|
import { TraceReducer } from 'types/reducer/trace';
|
||||||
|
|
||||||
import { AutoCompleteComponent } from './styles';
|
import { SelectComponent } from './styles';
|
||||||
|
import {
|
||||||
|
disableTagValue,
|
||||||
|
extractTagKey,
|
||||||
|
extractTagType,
|
||||||
|
getInitialLocalValue,
|
||||||
|
getTagValueOptions,
|
||||||
|
onTagValueChange,
|
||||||
|
selectOptions,
|
||||||
|
TagValueTypes,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
function TagValue(props: TagValueProps): JSX.Element {
|
function TagValue(props: TagValueProps): JSX.Element {
|
||||||
const { tag, setLocalSelectedTags, index, tagKey } = props;
|
const { tag, setLocalSelectedTags, index, tagKey } = props;
|
||||||
const {
|
const {
|
||||||
Key: selectedKey,
|
Key: selectedKey,
|
||||||
Operator: selectedOperator,
|
Operator: selectedOperator,
|
||||||
Values: selectedValues,
|
StringValues: selectedStringValues,
|
||||||
|
NumberValues: selectedNumberValues,
|
||||||
|
BoolValues: selectedBoolValues,
|
||||||
} = tag;
|
} = tag;
|
||||||
const [localValue, setLocalValue] = useState<string>(selectedValues[0]);
|
|
||||||
|
const [localTagValue, setLocalTagValue] = useState<TagValueTypes[]>(
|
||||||
|
getInitialLocalValue(
|
||||||
|
selectedNumberValues,
|
||||||
|
selectedBoolValues,
|
||||||
|
selectedStringValues,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const globalReducer = useSelector<AppState, GlobalReducer>(
|
const globalReducer = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const tagType = useMemo(() => extractTagType(tagKey), [tagKey]);
|
||||||
|
|
||||||
const { isLoading, data } = useQuery(
|
const { isLoading, data } = useQuery(
|
||||||
['tagKey', globalReducer.minTime, globalReducer.maxTime, tagKey],
|
['tagKey', globalReducer.minTime, globalReducer.maxTime, tagKey, tagType],
|
||||||
{
|
{
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
getTagValue({
|
getTagValue({
|
||||||
end: globalReducer.maxTime,
|
end: globalReducer.maxTime,
|
||||||
start: globalReducer.minTime,
|
start: globalReducer.minTime,
|
||||||
tagKey,
|
tagKey: {
|
||||||
|
Key: extractTagKey(tagKey),
|
||||||
|
Type: tagType,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
const tagValueDisabled = useMemo(
|
||||||
<AutoCompleteComponent
|
() =>
|
||||||
options={data?.payload?.map((e) => ({
|
disableTagValue(
|
||||||
label: e.tagValues,
|
selectedOperator,
|
||||||
value: e.tagValues,
|
setLocalTagValue,
|
||||||
}))}
|
selectedKey,
|
||||||
allowClear
|
setLocalSelectedTags,
|
||||||
defaultOpen
|
index,
|
||||||
showSearch
|
),
|
||||||
filterOption={(inputValue, option): boolean =>
|
[index, selectedKey, selectedOperator, setLocalSelectedTags],
|
||||||
option?.label.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
);
|
||||||
|
|
||||||
|
const onSetLocalValue = useCallback(() => {
|
||||||
|
setLocalTagValue([]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSelectedHandler = useCallback(
|
||||||
|
(value: unknown) => {
|
||||||
|
if (
|
||||||
|
typeof value === 'number' ||
|
||||||
|
(typeof value === 'string' && !Number.isNaN(Number(value)) && value !== ' ')
|
||||||
|
) {
|
||||||
|
setLocalTagValue([value]);
|
||||||
|
setLocalSelectedTags((tags) => [
|
||||||
|
...tags.slice(0, index),
|
||||||
|
{
|
||||||
|
Key: selectedKey,
|
||||||
|
Operator: selectedOperator,
|
||||||
|
StringValues: [],
|
||||||
|
NumberValues: [Number(value)],
|
||||||
|
BoolValues: [],
|
||||||
|
},
|
||||||
|
...tags.slice(index + 1, tags.length),
|
||||||
|
]);
|
||||||
|
} else if (
|
||||||
|
typeof value === 'boolean' ||
|
||||||
|
value === 'true' ||
|
||||||
|
value === 'false'
|
||||||
|
) {
|
||||||
|
setLocalTagValue([value]);
|
||||||
|
setLocalSelectedTags((tags) => [
|
||||||
|
...tags.slice(0, index),
|
||||||
|
{
|
||||||
|
Key: selectedKey,
|
||||||
|
Operator: selectedOperator,
|
||||||
|
StringValues: [],
|
||||||
|
NumberValues: [],
|
||||||
|
BoolValues: [value === 'true' || value === true],
|
||||||
|
},
|
||||||
|
...tags.slice(index + 1, tags.length),
|
||||||
|
]);
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
setLocalTagValue([value]);
|
||||||
|
setLocalSelectedTags((tags) => [
|
||||||
|
...tags.slice(0, index),
|
||||||
|
{
|
||||||
|
Key: selectedKey,
|
||||||
|
Operator: selectedOperator,
|
||||||
|
StringValues: [value],
|
||||||
|
NumberValues: [],
|
||||||
|
BoolValues: [],
|
||||||
|
},
|
||||||
|
...tags.slice(index + 1, tags.length),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
disabled={isLoading}
|
},
|
||||||
value={localValue}
|
[index, selectedKey, selectedOperator, setLocalSelectedTags],
|
||||||
onChange={(values): void => {
|
);
|
||||||
if (typeof values === 'string') {
|
|
||||||
setLocalValue(values);
|
const onChangeHandler = useCallback(
|
||||||
}
|
(value: unknown) => onTagValueChange(value, setLocalTagValue),
|
||||||
}}
|
[],
|
||||||
onSelect={(value: unknown): void => {
|
);
|
||||||
if (typeof value === 'string') {
|
|
||||||
setLocalValue(value);
|
const getFilterOptions = useCallback(
|
||||||
setLocalSelectedTags((tags) => [
|
(inputValue: string, option?: BaseOptionType): boolean => {
|
||||||
...tags.slice(0, index),
|
if (typeof option?.label === 'string') {
|
||||||
{
|
return option?.label.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1;
|
||||||
Key: selectedKey,
|
}
|
||||||
Operator: selectedOperator,
|
return false;
|
||||||
Values: [value],
|
},
|
||||||
},
|
[],
|
||||||
...tags.slice(index + 1, tags.length),
|
);
|
||||||
]);
|
|
||||||
}
|
return (
|
||||||
}}
|
<SelectComponent
|
||||||
|
loading={isLoading}
|
||||||
|
options={getTagValueOptions(data?.payload, tagType)}
|
||||||
|
mode="tags"
|
||||||
|
allowClear
|
||||||
|
onClear={onSetLocalValue}
|
||||||
|
onDeselect={onSetLocalValue}
|
||||||
|
showSearch
|
||||||
|
filterOption={getFilterOptions}
|
||||||
|
disabled={isLoading || tagValueDisabled}
|
||||||
|
value={localTagValue}
|
||||||
|
onChange={onChangeHandler}
|
||||||
|
onSelect={onSelectedHandler}
|
||||||
>
|
>
|
||||||
{data &&
|
{selectOptions(data?.payload, tagType)?.map((suggestion) => (
|
||||||
data.payload &&
|
<Select.Option key={suggestion.toString()} value={suggestion}>
|
||||||
data.payload.map((suggestion) => (
|
{suggestion}
|
||||||
<Select.Option key={suggestion.tagValues} value={suggestion.tagValues}>
|
</Select.Option>
|
||||||
{suggestion.tagValues}
|
))}
|
||||||
</Select.Option>
|
</SelectComponent>
|
||||||
))}
|
|
||||||
</AutoCompleteComponent>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,24 +6,90 @@ import { TraceReducer } from 'types/reducer/trace';
|
|||||||
import { Container, IconContainer, SelectComponent } from './styles';
|
import { Container, IconContainer, SelectComponent } from './styles';
|
||||||
import TagsKey from './TagKey';
|
import TagsKey from './TagKey';
|
||||||
import TagValue from './TagValue';
|
import TagValue from './TagValue';
|
||||||
|
import { mapOperators } from './utils';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
type Tags = FlatArray<TraceReducer['selectedTags'], 1>['Operator'];
|
type Tags = FlatArray<TraceReducer['selectedTags'], 1>['Operator'];
|
||||||
|
const StringBoolNumber = ['string', 'number', 'bool'];
|
||||||
interface AllMenuProps {
|
const Number = ['number'];
|
||||||
|
const String = ['string'];
|
||||||
|
export interface AllMenuProps {
|
||||||
key: Tags | '';
|
key: Tags | '';
|
||||||
value: string;
|
value: string;
|
||||||
|
supportedTypes: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const AllMenu: AllMenuProps[] = [
|
export const AllMenu: AllMenuProps[] = [
|
||||||
{
|
{
|
||||||
key: 'in',
|
key: 'Equals',
|
||||||
value: 'IN',
|
value: 'EQUALS',
|
||||||
|
supportedTypes: StringBoolNumber,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'not in',
|
key: 'NotEquals',
|
||||||
|
value: 'NOT EQUALS',
|
||||||
|
supportedTypes: StringBoolNumber,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'In',
|
||||||
|
value: 'IN',
|
||||||
|
supportedTypes: String,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'NotIn',
|
||||||
value: 'NOT IN',
|
value: 'NOT IN',
|
||||||
|
supportedTypes: String,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Exists',
|
||||||
|
value: 'EXISTS',
|
||||||
|
supportedTypes: StringBoolNumber,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'NotExists',
|
||||||
|
value: 'NOT EXISTS',
|
||||||
|
supportedTypes: StringBoolNumber,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'GreaterThan',
|
||||||
|
value: 'GREATER THAN',
|
||||||
|
supportedTypes: Number,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'LessThan',
|
||||||
|
value: 'LESS THAN',
|
||||||
|
supportedTypes: Number,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'GreaterThanEquals',
|
||||||
|
value: 'GREATER THAN OR EQUALS',
|
||||||
|
supportedTypes: Number,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'LessThanEquals',
|
||||||
|
value: 'LESS THAN OR EQUALS',
|
||||||
|
supportedTypes: Number,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'StartsWith',
|
||||||
|
value: 'STARTS WITH',
|
||||||
|
supportedTypes: String,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'NotStartsWith',
|
||||||
|
value: 'NOT STARTS WITH',
|
||||||
|
supportedTypes: String,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Contains',
|
||||||
|
value: 'CONTAINS',
|
||||||
|
supportedTypes: String,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'NotContains',
|
||||||
|
value: 'NOT CONTAINS',
|
||||||
|
supportedTypes: String,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -38,7 +104,9 @@ function SingleTags(props: AllTagsProps): JSX.Element {
|
|||||||
const {
|
const {
|
||||||
Key: selectedKey,
|
Key: selectedKey,
|
||||||
Operator: selectedOperator,
|
Operator: selectedOperator,
|
||||||
Values: selectedValues,
|
StringValues: selectedStringValues,
|
||||||
|
NumberValues: selectedNumberValues,
|
||||||
|
BoolValues: selectedBoolValues,
|
||||||
} = tag;
|
} = tag;
|
||||||
|
|
||||||
const onDeleteTagHandler = (index: number): void => {
|
const onDeleteTagHandler = (index: number): void => {
|
||||||
@ -51,7 +119,9 @@ function SingleTags(props: AllTagsProps): JSX.Element {
|
|||||||
...localSelectedTags.slice(0, index),
|
...localSelectedTags.slice(0, index),
|
||||||
{
|
{
|
||||||
Key: selectedKey,
|
Key: selectedKey,
|
||||||
Values: selectedValues,
|
StringValues: selectedStringValues,
|
||||||
|
NumberValues: selectedNumberValues,
|
||||||
|
BoolValues: selectedBoolValues,
|
||||||
Operator: key as Tags,
|
Operator: key as Tags,
|
||||||
},
|
},
|
||||||
...localSelectedTags.slice(index + 1, localSelectedTags.length),
|
...localSelectedTags.slice(index + 1, localSelectedTags.length),
|
||||||
@ -70,11 +140,14 @@ function SingleTags(props: AllTagsProps): JSX.Element {
|
|||||||
onChange={onChangeOperatorHandler}
|
onChange={onChangeOperatorHandler}
|
||||||
value={AllMenu.find((e) => e.key === selectedOperator)?.value || ''}
|
value={AllMenu.find((e) => e.key === selectedOperator)?.value || ''}
|
||||||
>
|
>
|
||||||
{AllMenu.map((e) => (
|
{
|
||||||
<Option key={e.value} value={e.key}>
|
// filter out the operator that does not include supported type of the selected key
|
||||||
{e.value}
|
mapOperators(selectedKey).map((e) => (
|
||||||
</Option>
|
<Option key={e.value} value={e.key}>
|
||||||
))}
|
{e.value}
|
||||||
|
</Option>
|
||||||
|
))
|
||||||
|
}
|
||||||
</SelectComponent>
|
</SelectComponent>
|
||||||
|
|
||||||
{selectedKey[0] ? (
|
{selectedKey[0] ? (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { AutoComplete, Select, Space } from 'antd';
|
import { Select, Space } from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export const SpaceComponent = styled(Space)`
|
export const SpaceComponent = styled(Space)`
|
||||||
@ -7,12 +7,6 @@ export const SpaceComponent = styled(Space)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SelectComponent = styled(Select)`
|
|
||||||
&&& {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const Container = styled(Space)`
|
export const Container = styled(Space)`
|
||||||
&&& {
|
&&& {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -37,7 +31,7 @@ export const IconContainer = styled.div`
|
|||||||
margin-left: 1.125rem;
|
margin-left: 1.125rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const AutoCompleteComponent = styled(AutoComplete)`
|
export const SelectComponent = styled(Select)`
|
||||||
&&& {
|
&&& {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
204
frontend/src/container/Trace/Search/AllTags/Tag/utils.ts
Normal file
204
frontend/src/container/Trace/Search/AllTags/Tag/utils.ts
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import { AutoCompleteProps } from 'antd';
|
||||||
|
import { DefaultOptionType } from 'antd/es/select';
|
||||||
|
import { PayloadProps as TagKeyPayload } from 'types/api/trace/getTagFilters';
|
||||||
|
import { PayloadProps as TagValuePayload } from 'types/api/trace/getTagValue';
|
||||||
|
import { OperatorValues, Tags } from 'types/reducer/trace';
|
||||||
|
|
||||||
|
import { AllMenu, AllMenuProps } from '.';
|
||||||
|
|
||||||
|
export type TagValueTypes = string | number | boolean;
|
||||||
|
/**
|
||||||
|
* @description extract tag filters from payload
|
||||||
|
*/
|
||||||
|
export const extractTagFilters = (
|
||||||
|
payload: TagKeyPayload,
|
||||||
|
): DefaultOptionType[] => {
|
||||||
|
const tagFilters: string[] = [];
|
||||||
|
payload.stringTagKeys.forEach((element) => {
|
||||||
|
tagFilters.push(`${element}.(string)`);
|
||||||
|
});
|
||||||
|
payload.numberTagKeys.forEach((element) => {
|
||||||
|
tagFilters.push(`${element}.(number)`);
|
||||||
|
});
|
||||||
|
payload.boolTagKeys.forEach((element) => {
|
||||||
|
tagFilters.push(`${element}.(bool)`);
|
||||||
|
});
|
||||||
|
return tagFilters.map((e) => ({
|
||||||
|
value: e,
|
||||||
|
label: e,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const extractTagType = (tagKey: string): string => {
|
||||||
|
if (tagKey?.includes('.(string)')) {
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
if (tagKey?.includes('.(number)')) {
|
||||||
|
return 'number';
|
||||||
|
}
|
||||||
|
if (tagKey?.includes('.(bool)')) {
|
||||||
|
return 'bool';
|
||||||
|
}
|
||||||
|
return 'string';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const extractTagKey = (tagKey: string): string => {
|
||||||
|
const tag = tagKey.split('.(');
|
||||||
|
if (tag && tag.length > 0) {
|
||||||
|
return tag[0];
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export function onTagValueChange(
|
||||||
|
values: unknown,
|
||||||
|
setLocalValue: React.Dispatch<React.SetStateAction<TagValueTypes[]>>,
|
||||||
|
): void {
|
||||||
|
if (Array.isArray(values) && values.length > 0) {
|
||||||
|
if (typeof values[0] === 'number' || typeof values[0] === 'boolean') {
|
||||||
|
setLocalValue(values);
|
||||||
|
} else if (typeof values[0] === 'string') {
|
||||||
|
if (values[0] === 'true' || values[0] === 'false') {
|
||||||
|
setLocalValue([values[0] === 'true']);
|
||||||
|
} else if (values[0] !== ' ' && !Number.isNaN(Number(values[0]))) {
|
||||||
|
setLocalValue([Number(values[0])]);
|
||||||
|
} else {
|
||||||
|
setLocalValue([values[0]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function disableTagValue(
|
||||||
|
selectedOperator: OperatorValues,
|
||||||
|
setLocalValue: React.Dispatch<React.SetStateAction<TagValueTypes[]>>,
|
||||||
|
selectedKeys: string[],
|
||||||
|
setLocalSelectedTags: React.Dispatch<React.SetStateAction<Tags[]>>,
|
||||||
|
index: number,
|
||||||
|
): boolean {
|
||||||
|
if (selectedOperator === 'Exists' || selectedOperator === 'NotExists') {
|
||||||
|
setLocalValue([]);
|
||||||
|
setLocalSelectedTags((tags) => [
|
||||||
|
...tags.slice(0, index),
|
||||||
|
{
|
||||||
|
Key: selectedKeys,
|
||||||
|
Operator: selectedOperator,
|
||||||
|
StringValues: [],
|
||||||
|
NumberValues: [],
|
||||||
|
BoolValues: [],
|
||||||
|
},
|
||||||
|
...tags.slice(index + 1, tags.length),
|
||||||
|
]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInitialLocalValue(
|
||||||
|
selectedNumberValues: number[],
|
||||||
|
selectedBoolValues: boolean[],
|
||||||
|
selectedStringValues: string[],
|
||||||
|
): TagValueTypes[] {
|
||||||
|
if (selectedStringValues && selectedStringValues.length > 0) {
|
||||||
|
return selectedStringValues;
|
||||||
|
}
|
||||||
|
if (selectedNumberValues && selectedNumberValues.length > 0) {
|
||||||
|
return selectedNumberValues;
|
||||||
|
}
|
||||||
|
if (selectedBoolValues && selectedBoolValues.length > 0) {
|
||||||
|
return selectedBoolValues;
|
||||||
|
}
|
||||||
|
return selectedStringValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTagValueOptions(
|
||||||
|
payload: TagValuePayload | null | undefined,
|
||||||
|
tagType: string,
|
||||||
|
): Array<{ label: string; value: TagValueTypes }> | undefined {
|
||||||
|
if (tagType === 'string') {
|
||||||
|
return payload?.stringTagValues?.map((e) => ({
|
||||||
|
label: e,
|
||||||
|
value: e,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (tagType === 'number') {
|
||||||
|
return payload?.numberTagValues?.map((e) => ({
|
||||||
|
label: e.toString(),
|
||||||
|
value: e,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (tagType === 'bool') {
|
||||||
|
return payload?.boolTagValues?.map((e) => ({
|
||||||
|
label: e.toString(),
|
||||||
|
value: e,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTagKeyOptions(
|
||||||
|
payload: TagKeyPayload | null | undefined,
|
||||||
|
): DefaultOptionType[] {
|
||||||
|
if (payload === null) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
label: 'No tags available',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (payload != null) {
|
||||||
|
return extractTagFilters(payload);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectOptions(
|
||||||
|
payload: TagValuePayload | null | undefined,
|
||||||
|
tagType: string,
|
||||||
|
): string[] | boolean[] | number[] | undefined {
|
||||||
|
if (tagType === 'string') {
|
||||||
|
return payload?.stringTagValues;
|
||||||
|
}
|
||||||
|
if (tagType === 'number') {
|
||||||
|
return payload?.numberTagValues;
|
||||||
|
}
|
||||||
|
if (tagType === 'bool') {
|
||||||
|
return payload?.boolTagValues;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapOperators(selectedKey: string[]): AllMenuProps[] {
|
||||||
|
return AllMenu.filter((e) =>
|
||||||
|
e?.supportedTypes?.includes(extractTagType(selectedKey[0])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onTagKeySelect(
|
||||||
|
value: unknown,
|
||||||
|
options: AutoCompleteProps['options'],
|
||||||
|
setSelectedKey: React.Dispatch<React.SetStateAction<string>>,
|
||||||
|
setLocalSelectedTags: React.Dispatch<React.SetStateAction<Tags[]>>,
|
||||||
|
index: number,
|
||||||
|
tag: Tags,
|
||||||
|
): void {
|
||||||
|
if (
|
||||||
|
typeof value === 'string' &&
|
||||||
|
options &&
|
||||||
|
options.find((option) => option.value === value)
|
||||||
|
) {
|
||||||
|
setSelectedKey(value);
|
||||||
|
setLocalSelectedTags((tags) => [
|
||||||
|
...tags.slice(0, index),
|
||||||
|
{
|
||||||
|
Key: [value],
|
||||||
|
Operator: tag.Operator,
|
||||||
|
StringValues: tag.StringValues,
|
||||||
|
NumberValues: tag.NumberValues,
|
||||||
|
BoolValues: tag.BoolValues,
|
||||||
|
},
|
||||||
|
...tags.slice(index + 1, tags.length),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -41,8 +41,10 @@ function AllTags({
|
|||||||
...tags,
|
...tags,
|
||||||
{
|
{
|
||||||
Key: [],
|
Key: [],
|
||||||
Operator: 'in',
|
Operator: 'Equals',
|
||||||
Values: [],
|
StringValues: [],
|
||||||
|
NumberValues: [],
|
||||||
|
BoolValues: [],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
@ -110,7 +112,7 @@ function AllTags({
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Text ellipsis>
|
<Text ellipsis>
|
||||||
Results will include spans with ALL the specified tags ( Rows are `anded`
|
Results will include spans with ALL the specified tags ( Rows are `ANDed`
|
||||||
)
|
)
|
||||||
</Text>
|
</Text>
|
||||||
</Space>
|
</Space>
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
import { AllMenu } from 'container/Trace/Search/AllTags/Tag';
|
||||||
import { TraceReducer } from 'types/reducer/trace';
|
import { TraceReducer } from 'types/reducer/trace';
|
||||||
|
|
||||||
|
import { extractTagType, TagValueTypes } from './AllTags/Tag/utils';
|
||||||
|
|
||||||
type Tags = TraceReducer['selectedTags'];
|
type Tags = TraceReducer['selectedTags'];
|
||||||
|
|
||||||
interface PayloadProps<T> {
|
interface PayloadProps<T> {
|
||||||
@ -7,79 +10,144 @@ interface PayloadProps<T> {
|
|||||||
payload: T;
|
payload: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractValues(
|
||||||
|
tagType: string,
|
||||||
|
filters: string[],
|
||||||
|
isError: boolean,
|
||||||
|
): [number[], boolean[], string[], boolean] {
|
||||||
|
const StringValues: string[] = [];
|
||||||
|
const NumberValues: number[] = [];
|
||||||
|
const BoolValues: boolean[] = [];
|
||||||
|
let isErr = isError;
|
||||||
|
if (tagType === 'string') {
|
||||||
|
StringValues.push(...filters);
|
||||||
|
} else if (tagType === 'number') {
|
||||||
|
filters.forEach((element) => {
|
||||||
|
const num = Number(element);
|
||||||
|
isErr = Number.isNaN(num) ? true : isError;
|
||||||
|
NumberValues.push(num);
|
||||||
|
});
|
||||||
|
} else if (tagType === 'bool') {
|
||||||
|
filters.forEach((element) => {
|
||||||
|
if (element === 'true') {
|
||||||
|
BoolValues.push(true);
|
||||||
|
} else if (element === 'false') {
|
||||||
|
BoolValues.push(false);
|
||||||
|
} else {
|
||||||
|
isErr = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [NumberValues, BoolValues, StringValues, isErr];
|
||||||
|
}
|
||||||
|
|
||||||
export const parseQueryToTags = (query: string): PayloadProps<Tags> => {
|
export const parseQueryToTags = (query: string): PayloadProps<Tags> => {
|
||||||
let isError = false;
|
let isError = false;
|
||||||
|
|
||||||
|
// Split the query string by ' AND '
|
||||||
const noOfTags = query.split(' AND ');
|
const noOfTags = query.split(' AND ');
|
||||||
|
|
||||||
|
// Map over each tag
|
||||||
const tags: Tags = noOfTags.map((filter) => {
|
const tags: Tags = noOfTags.map((filter) => {
|
||||||
const isInPresent = filter.includes('in');
|
// Find the operator used in the filter
|
||||||
const isNotInPresent = filter.includes('not in');
|
const operator =
|
||||||
|
AllMenu.find((e) => `${filter} `.includes(` ${e.key} `))?.key || '';
|
||||||
|
|
||||||
if (!isNotInPresent && !isInPresent) {
|
// Split the filter by the operator
|
||||||
isError = true;
|
const [tagName, tagValues] = filter.split(operator).map((e) => e.trim());
|
||||||
|
|
||||||
|
// If the operator is Exists or NotExists, then return the tag object without values
|
||||||
|
if (operator === 'Exists' || operator === 'NotExists') {
|
||||||
|
return {
|
||||||
|
Key: [tagName],
|
||||||
|
StringValues: [],
|
||||||
|
NumberValues: [],
|
||||||
|
BoolValues: [],
|
||||||
|
Operator: operator as FlatArray<Tags, 1>['Operator'],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
// Check for errors in the filter
|
||||||
|
isError = operator.length === 0 || !tagName || !tagValues ? true : isError;
|
||||||
|
|
||||||
const isPresentSplit = isInPresent ? 'in' : '';
|
// Remove the first and last brackets from the tagValues
|
||||||
|
const formattedTagValues = tagValues.slice(1, -1);
|
||||||
|
|
||||||
const splitBy = isNotInPresent ? 'not in' : isPresentSplit;
|
// Split the tagValues by ',' and remove any quotes
|
||||||
|
const filters = formattedTagValues
|
||||||
if (splitBy.length === 0) {
|
|
||||||
isError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredtags = filter.split(splitBy).map((e) => e.trim());
|
|
||||||
|
|
||||||
if (filteredtags.length !== 2) {
|
|
||||||
isError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filterForTags = filteredtags[1];
|
|
||||||
|
|
||||||
if (!filterForTags) {
|
|
||||||
isError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const removingFirstAndLastBrackets = `${filterForTags?.slice(1, -1)}`;
|
|
||||||
|
|
||||||
const noofFilters = removingFirstAndLastBrackets
|
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((e) => e.replaceAll(/"/g, ''));
|
.map((e) => e.replaceAll(/"/g, ''));
|
||||||
|
|
||||||
noofFilters.forEach((e) => {
|
// Check for errors in the filters
|
||||||
|
filters.forEach((e) => {
|
||||||
const firstChar = e.charAt(0);
|
const firstChar = e.charAt(0);
|
||||||
const lastChar = e.charAt(e.length - 1);
|
const lastChar = e.charAt(e.length - 1);
|
||||||
|
isError = firstChar === '"' && lastChar === '"' ? true : isError;
|
||||||
if (firstChar === '"' && lastChar === '"') {
|
|
||||||
isError = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Extract the tag type
|
||||||
|
const tagType = extractTagType(tagName);
|
||||||
|
|
||||||
|
// Extract the values for the tag
|
||||||
|
const [NumberValues, BoolValues, StringValues, isErr] = extractValues(
|
||||||
|
tagType,
|
||||||
|
filters,
|
||||||
|
isError,
|
||||||
|
);
|
||||||
|
isError = isErr;
|
||||||
|
|
||||||
|
// Return the tag object
|
||||||
return {
|
return {
|
||||||
Key: [filteredtags[0]],
|
Key: [tagName],
|
||||||
Values: noofFilters,
|
StringValues,
|
||||||
Operator: splitBy as FlatArray<Tags, 1>['Operator'],
|
NumberValues,
|
||||||
|
BoolValues,
|
||||||
|
Operator: operator as FlatArray<Tags, 1>['Operator'],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isError,
|
isError,
|
||||||
payload: tags,
|
payload: tags,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatValues = (values: TagValueTypes[]): string =>
|
||||||
|
values.map((e) => `"${e.toString().replaceAll(/"/g, '')}"`).join(',');
|
||||||
|
|
||||||
export const parseTagsToQuery = (tags: Tags): PayloadProps<string> => {
|
export const parseTagsToQuery = (tags: Tags): PayloadProps<string> => {
|
||||||
let isError = false;
|
let isError = false;
|
||||||
|
|
||||||
|
// Map over each tag
|
||||||
const payload = tags
|
const payload = tags
|
||||||
.map(({ Values, Key, Operator }) => {
|
.map(({ StringValues, NumberValues, BoolValues, Key, Operator }) => {
|
||||||
if (Key[0] === undefined) {
|
// Check if the key of the tag is undefined
|
||||||
|
if (!Key[0]) {
|
||||||
isError = true;
|
isError = true;
|
||||||
}
|
}
|
||||||
|
if (Operator === 'Exists' || Operator === 'NotExists') {
|
||||||
|
return `${Key[0]} ${Operator}`;
|
||||||
|
}
|
||||||
|
// Check if the tag has string values
|
||||||
|
if (StringValues.length > 0) {
|
||||||
|
// Format the string values and join them with a ','
|
||||||
|
const formattedStringValues = formatValues(StringValues);
|
||||||
|
return `${Key[0]} ${Operator} (${formattedStringValues})`;
|
||||||
|
}
|
||||||
|
|
||||||
return `${Key[0]} ${Operator} (${Values.map(
|
// Check if the tag has number values
|
||||||
(e) => `"${e.replaceAll(/"/g, '')}"`,
|
if (NumberValues.length > 0) {
|
||||||
).join(',')})`;
|
// Format the number values and join them with a ','
|
||||||
|
const formattedNumberValues = formatValues(NumberValues);
|
||||||
|
return `${Key[0]} ${Operator} (${formattedNumberValues})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the tag has boolean values
|
||||||
|
if (BoolValues.length > 0) {
|
||||||
|
// Format the boolean values and join them with a ','
|
||||||
|
const formattedBoolValues = formatValues(BoolValues);
|
||||||
|
return `${Key[0]} ${Operator} (${formattedBoolValues})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
})
|
})
|
||||||
.join(' AND ');
|
.join(' AND ');
|
||||||
|
|
||||||
|
@ -1,70 +1,72 @@
|
|||||||
|
import { DefaultOptionType } from 'antd/es/select';
|
||||||
|
|
||||||
interface Dropdown {
|
interface Dropdown {
|
||||||
key: string;
|
key: string;
|
||||||
displayValue: string;
|
displayValue: string;
|
||||||
yAxisUnit?: string;
|
yAxisUnit?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const groupBy: Dropdown[] = [
|
export const groupBy: DefaultOptionType[] = [
|
||||||
{
|
{
|
||||||
key: '',
|
label: 'None',
|
||||||
displayValue: 'None',
|
value: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'serviceName',
|
label: 'Service Name',
|
||||||
displayValue: 'Service Name',
|
value: 'serviceName',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
displayValue: 'Operation',
|
label: 'Operation',
|
||||||
key: 'operation',
|
value: 'name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayValue: 'HTTP url',
|
label: 'HTTP URL',
|
||||||
key: 'httpUrl',
|
value: 'httpUrl',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayValue: 'HTTP method',
|
label: 'HTTP Method',
|
||||||
key: 'httpMethod',
|
value: 'httpMethod',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayValue: 'HTTP host',
|
label: 'HTTP Host',
|
||||||
key: 'httpHost',
|
value: 'httpHost',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayValue: 'HTTP route',
|
label: 'HTTP Route',
|
||||||
key: 'httpRoute',
|
value: 'httpRoute',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayValue: 'HTTP status code',
|
label: 'RPC Method',
|
||||||
key: 'httpCode',
|
value: 'rpcMethod',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayValue: 'RPC Method',
|
label: 'Status Code',
|
||||||
key: 'rpcMethod',
|
value: 'responseStatusCode',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayValue: 'Status Code',
|
label: 'Database Name',
|
||||||
key: 'responseStatusCode',
|
value: 'dbName',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayValue: 'Database name',
|
label: 'Database System',
|
||||||
key: 'dbName',
|
value: 'dbSystem',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayValue: 'Database operation',
|
label: 'Database Operation',
|
||||||
key: 'dbSystem',
|
value: 'dbOperation',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayValue: 'Messaging System',
|
label: 'Messaging System',
|
||||||
key: 'msgSystem',
|
value: 'msgSystem',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayValue: 'Messaging Operation',
|
label: 'Messaging Operation',
|
||||||
key: 'msgOperation',
|
value: 'msgOperation',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayValue: 'Component',
|
label: 'Component',
|
||||||
key: 'component',
|
value: 'component',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
import { Space } from 'antd';
|
import { AutoComplete, Input, Space } from 'antd';
|
||||||
import React from 'react';
|
import getTagFilters from 'api/trace/getTagFilter';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import React, { useMemo } from 'react';
|
||||||
import { Dispatch } from 'redux';
|
import { useQuery } from 'react-query';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import {
|
|
||||||
UPDATE_SELECTED_FUNCTION,
|
|
||||||
UPDATE_SELECTED_GROUP_BY,
|
|
||||||
} from 'types/actions/trace';
|
|
||||||
import { TraceReducer } from 'types/reducer/trace';
|
import { TraceReducer } from 'types/reducer/trace';
|
||||||
|
|
||||||
import { functions, groupBy } from './config';
|
import { functions } from './config';
|
||||||
import { SelectComponent } from './styles';
|
import { SelectComponent } from './styles';
|
||||||
|
import {
|
||||||
|
getSelectedValue,
|
||||||
|
initOptions,
|
||||||
|
onClickSelectedFunctionHandler,
|
||||||
|
onClickSelectedGroupByHandler,
|
||||||
|
selectedGroupByValue,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
const { Option } = SelectComponent;
|
const { Option } = SelectComponent;
|
||||||
|
|
||||||
@ -20,36 +24,32 @@ function TraceGraphFilter(): JSX.Element {
|
|||||||
AppState,
|
AppState,
|
||||||
TraceReducer
|
TraceReducer
|
||||||
>((state) => state.traces);
|
>((state) => state.traces);
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||||
|
(state) => state.globalTime,
|
||||||
|
);
|
||||||
|
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||||
|
|
||||||
const onClickSelectedFunctionHandler = (ev: unknown): void => {
|
const { isLoading, data } = useQuery(
|
||||||
if (typeof ev === 'string') {
|
[
|
||||||
const selected = functions.find((e) => e.key === ev);
|
'getTagKeys',
|
||||||
if (selected) {
|
globalTime.minTime,
|
||||||
dispatch({
|
globalTime.maxTime,
|
||||||
type: UPDATE_SELECTED_FUNCTION,
|
traces.selectedFilter,
|
||||||
payload: {
|
traces.isFilterExclude,
|
||||||
selectedFunction: selected.key,
|
],
|
||||||
yAxisUnit: selected.yAxisUnit,
|
{
|
||||||
},
|
queryFn: () =>
|
||||||
});
|
getTagFilters({
|
||||||
}
|
start: globalTime.minTime,
|
||||||
}
|
end: globalTime.maxTime,
|
||||||
};
|
other: Object.fromEntries(traces.selectedFilter),
|
||||||
|
isFilterExclude: traces.isFilterExclude,
|
||||||
|
}),
|
||||||
|
cacheTime: 120000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const onClickSelectedGroupByHandler = (ev: unknown): void => {
|
const options = useMemo(() => initOptions(data?.payload), [data?.payload]);
|
||||||
if (typeof ev === 'string') {
|
|
||||||
const selected = groupBy.find((e) => e.key === ev);
|
|
||||||
if (selected) {
|
|
||||||
dispatch({
|
|
||||||
type: UPDATE_SELECTED_GROUP_BY,
|
|
||||||
payload: {
|
|
||||||
selectedGroupBy: selected.key,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space>
|
<Space>
|
||||||
@ -59,7 +59,7 @@ function TraceGraphFilter(): JSX.Element {
|
|||||||
dropdownMatchSelectWidth
|
dropdownMatchSelectWidth
|
||||||
data-testid="selectedFunction"
|
data-testid="selectedFunction"
|
||||||
id="selectedFunction"
|
id="selectedFunction"
|
||||||
value={functions.find((e) => selectedFunction === e.key)?.displayValue}
|
value={getSelectedValue(selectedFunction)}
|
||||||
onChange={onClickSelectedFunctionHandler}
|
onChange={onClickSelectedFunctionHandler}
|
||||||
>
|
>
|
||||||
{functions.map((value) => (
|
{functions.map((value) => (
|
||||||
@ -70,21 +70,16 @@ function TraceGraphFilter(): JSX.Element {
|
|||||||
</SelectComponent>
|
</SelectComponent>
|
||||||
|
|
||||||
<label htmlFor="selectedGroupBy">Group By</label>
|
<label htmlFor="selectedGroupBy">Group By</label>
|
||||||
<SelectComponent
|
<AutoComplete
|
||||||
dropdownMatchSelectWidth
|
dropdownMatchSelectWidth
|
||||||
id="selectedGroupBy"
|
id="selectedGroupBy"
|
||||||
data-testid="selectedGroupBy"
|
data-testid="selectedGroupBy"
|
||||||
value={groupBy.find((e) => selectedGroupBy === e.key)?.displayValue}
|
options={options}
|
||||||
onChange={onClickSelectedGroupByHandler}
|
value={selectedGroupByValue(selectedGroupBy, options)}
|
||||||
|
onChange={onClickSelectedGroupByHandler(options)}
|
||||||
>
|
>
|
||||||
{groupBy.map(
|
<Input disabled={isLoading} placeholder="Please select" />
|
||||||
(value): JSX.Element => (
|
</AutoComplete>
|
||||||
<Option value={value.key} key={value.key}>
|
|
||||||
{value.displayValue}
|
|
||||||
</Option>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</SelectComponent>
|
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
73
frontend/src/container/Trace/TraceGraphFilter/utils.ts
Normal file
73
frontend/src/container/Trace/TraceGraphFilter/utils.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { DefaultOptionType } from 'antd/es/select';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
import store from 'store';
|
||||||
|
import {
|
||||||
|
UPDATE_SELECTED_FUNCTION,
|
||||||
|
UPDATE_SELECTED_GROUP_BY,
|
||||||
|
} from 'types/actions/trace';
|
||||||
|
import { PayloadProps } from 'types/api/trace/getTagFilters';
|
||||||
|
|
||||||
|
import { extractTagFilters } from '../Search/AllTags/Tag/utils';
|
||||||
|
import { functions, groupBy } from './config';
|
||||||
|
|
||||||
|
export function groupByValues(
|
||||||
|
tagFilters: DefaultOptionType[],
|
||||||
|
): DefaultOptionType[] {
|
||||||
|
const result: DefaultOptionType[] = [...groupBy];
|
||||||
|
tagFilters.forEach((e) => {
|
||||||
|
result.push(e);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initOptions(
|
||||||
|
payload: PayloadProps | null | undefined,
|
||||||
|
): DefaultOptionType[] {
|
||||||
|
if (payload) {
|
||||||
|
return groupByValues(extractTagFilters(payload));
|
||||||
|
}
|
||||||
|
return groupBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onClickSelectedGroupByHandler(options: DefaultOptionType[]) {
|
||||||
|
return (ev: unknown): void => {
|
||||||
|
const { dispatch } = store;
|
||||||
|
if (typeof ev === 'string' && options) {
|
||||||
|
const selected = options.find((e) => e.value === ev);
|
||||||
|
if (selected) {
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_SELECTED_GROUP_BY,
|
||||||
|
payload: {
|
||||||
|
selectedGroupBy: selected.value ? selected.value.toString() : '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onClickSelectedFunctionHandler(value: unknown): void {
|
||||||
|
const { dispatch } = store;
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const selected = functions.find((e) => e.key === value);
|
||||||
|
if (selected) {
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_SELECTED_FUNCTION,
|
||||||
|
payload: {
|
||||||
|
selectedFunction: selected.key,
|
||||||
|
yAxisUnit: selected.yAxisUnit,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function selectedGroupByValue(
|
||||||
|
selectedGroupBy: string,
|
||||||
|
options: DefaultOptionType[],
|
||||||
|
): ReactNode {
|
||||||
|
return options.find((e) => selectedGroupBy === e.value)?.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSelectedValue(selectedFunction: string): unknown {
|
||||||
|
return functions.find((e) => selectedFunction === e.key)?.displayValue;
|
||||||
|
}
|
@ -43,7 +43,9 @@ export const convertRawQueriesToTraceSelectedTags = (
|
|||||||
? [convertMetricKeyToTrace(query.tagKey)]
|
? [convertMetricKeyToTrace(query.tagKey)]
|
||||||
: (convertMetricKeyToTrace(query.tagKey) as never),
|
: (convertMetricKeyToTrace(query.tagKey) as never),
|
||||||
Operator: convertOperatorLabelToTraceOperator(query.operator),
|
Operator: convertOperatorLabelToTraceOperator(query.operator),
|
||||||
Values: query.tagValue,
|
StringValues: query.tagValue,
|
||||||
|
NumberValues: [],
|
||||||
|
BoolValues: [],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,8 +9,8 @@ export interface Props {
|
|||||||
isFilterExclude: TraceReducer['isFilterExclude'];
|
isFilterExclude: TraceReducer['isFilterExclude'];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TagsKeys {
|
export interface PayloadProps {
|
||||||
tagKeys: string;
|
stringTagKeys: string[];
|
||||||
|
numberTagKeys: string[];
|
||||||
|
boolTagKeys: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PayloadProps = TagsKeys[];
|
|
||||||
|
@ -3,11 +3,14 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
export interface Props {
|
export interface Props {
|
||||||
start: GlobalReducer['minTime'];
|
start: GlobalReducer['minTime'];
|
||||||
end: GlobalReducer['maxTime'];
|
end: GlobalReducer['maxTime'];
|
||||||
tagKey: string;
|
tagKey: {
|
||||||
|
Key: string;
|
||||||
|
Type: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Value {
|
export interface PayloadProps {
|
||||||
tagValues: string;
|
stringTagValues: string[];
|
||||||
|
numberTagValues: number[];
|
||||||
|
boolTagValues: boolean[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PayloadProps = Value[];
|
|
||||||
|
@ -49,15 +49,33 @@ interface SpansAggregateData {
|
|||||||
export interface Tags {
|
export interface Tags {
|
||||||
Key: string[];
|
Key: string[];
|
||||||
Operator: OperatorValues;
|
Operator: OperatorValues;
|
||||||
Values: string[];
|
StringValues: string[];
|
||||||
|
NumberValues: number[];
|
||||||
|
BoolValues: boolean[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TagsAPI {
|
export interface TagsAPI {
|
||||||
Key: string;
|
Key: string;
|
||||||
Operator: OperatorValues;
|
Operator: OperatorValues;
|
||||||
Values: string[];
|
StringValues: string[];
|
||||||
|
NumberValues: number[];
|
||||||
|
BoolValues: boolean[];
|
||||||
}
|
}
|
||||||
export type OperatorValues = 'not in' | 'in';
|
export type OperatorValues =
|
||||||
|
| 'NotIn'
|
||||||
|
| 'In'
|
||||||
|
| 'Equals'
|
||||||
|
| 'NotEquals'
|
||||||
|
| 'Contains'
|
||||||
|
| 'NotContains'
|
||||||
|
| 'GreaterThan'
|
||||||
|
| 'Exists'
|
||||||
|
| 'NotExists'
|
||||||
|
| 'LessThan'
|
||||||
|
| 'GreaterThanEquals'
|
||||||
|
| 'LessThanEquals'
|
||||||
|
| 'StartsWith'
|
||||||
|
| 'NotStartsWith';
|
||||||
|
|
||||||
export type TraceFilterEnum =
|
export type TraceFilterEnum =
|
||||||
| 'component'
|
| 'component'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user