mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-08 12:58:59 +08:00
Merge branch 'develop' into trace-filter-toolip
This commit is contained in:
commit
31848c488d
@ -1,12 +1,19 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Button, Input } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { INITIAL_FILTER_VALUE } from 'store/reducers/trace';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_SPAN_UPDATE_FILTER_DISPLAY_VALUE } from 'types/actions/trace';
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import CheckBoxComponent from '../Common/Checkbox';
|
||||
|
||||
const { Search } = Input;
|
||||
|
||||
function CommonCheckBox(props: CommonCheckBoxProps): JSX.Element {
|
||||
const { filter } = useSelector<AppState, TraceReducer>(
|
||||
const { filter, filterDisplayValue } = useSelector<AppState, TraceReducer>(
|
||||
(state) => state.traces,
|
||||
);
|
||||
|
||||
@ -15,9 +22,40 @@ function CommonCheckBox(props: CommonCheckBoxProps): JSX.Element {
|
||||
const status = filter.get(name) || {};
|
||||
|
||||
const statusObj = Object.keys(status);
|
||||
const numberOfFilters = filterDisplayValue.get(name) || 0;
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
const [searchFilter, setSearchFilter] = useState<string>('');
|
||||
|
||||
const onClickMoreHandler = (): void => {
|
||||
const newFilterDisplayValue = new Map(filterDisplayValue);
|
||||
const preValue =
|
||||
(newFilterDisplayValue.get(name) || 0) + INITIAL_FILTER_VALUE;
|
||||
|
||||
newFilterDisplayValue.set(name, preValue);
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_SPAN_UPDATE_FILTER_DISPLAY_VALUE,
|
||||
payload: newFilterDisplayValue,
|
||||
});
|
||||
};
|
||||
|
||||
const isMoreButtonAvilable = Boolean(
|
||||
numberOfFilters && statusObj.length > numberOfFilters,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{statusObj.length > 0 && (
|
||||
<Search
|
||||
value={searchFilter}
|
||||
onChange={(e): void => setSearchFilter(e.target.value)}
|
||||
style={{
|
||||
padding: '0 3%',
|
||||
}}
|
||||
placeholder="Filter Values"
|
||||
/>
|
||||
)}
|
||||
|
||||
{statusObj
|
||||
.sort((a, b) => {
|
||||
const countA = +status[a];
|
||||
@ -28,6 +66,15 @@ function CommonCheckBox(props: CommonCheckBoxProps): JSX.Element {
|
||||
}
|
||||
return countA - countB;
|
||||
})
|
||||
.filter((filter) => {
|
||||
if (searchFilter.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return filter
|
||||
.toLocaleLowerCase()
|
||||
.includes(searchFilter.toLocaleLowerCase());
|
||||
})
|
||||
.filter((_, index) => index < numberOfFilters)
|
||||
.map((e) => (
|
||||
<CheckBoxComponent
|
||||
key={e}
|
||||
@ -38,6 +85,12 @@ function CommonCheckBox(props: CommonCheckBoxProps): JSX.Element {
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{isMoreButtonAvilable && (
|
||||
<Button onClick={onClickMoreHandler} type="link">
|
||||
More
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
/* eslint-disable react/no-unstable-nested-components */
|
||||
import { Input, Slider } from 'antd';
|
||||
import { Slider } from 'antd';
|
||||
import { SliderRangeProps } from 'antd/lib/slider';
|
||||
import getFilters from 'api/trace/getFilters';
|
||||
import dayjs from 'dayjs';
|
||||
import durationPlugin from 'dayjs/plugin/duration';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { getFilter, updateURL } from 'store/actions/trace/util';
|
||||
@ -15,19 +18,8 @@ import { UPDATE_ALL_FILTERS } from 'types/actions/trace';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import { Container, InputContainer, Text } from './styles';
|
||||
|
||||
dayjs.extend(durationPlugin);
|
||||
|
||||
const getMs = (value: string): string => {
|
||||
return parseFloat(
|
||||
dayjs
|
||||
.duration({
|
||||
milliseconds: parseInt(value, 10) / 1000000,
|
||||
})
|
||||
.format('SSS'),
|
||||
).toFixed(2);
|
||||
};
|
||||
import { Container, InputComponent, InputContainer, Text } from './styles';
|
||||
import { getMs } from './util';
|
||||
|
||||
function Duration(): JSX.Element {
|
||||
const {
|
||||
@ -77,17 +69,18 @@ function Duration(): JSX.Element {
|
||||
preLocalMinDuration.current = parseFloat(minDuration);
|
||||
}
|
||||
|
||||
setPreMax(maxDuration);
|
||||
setPreMin(minDuration);
|
||||
setPreMax(getMs(maxDuration));
|
||||
setPreMin(getMs(minDuration));
|
||||
}, [getDuration]);
|
||||
|
||||
const defaultValue = [parseFloat(preMin), parseFloat(preMax)];
|
||||
|
||||
const updatedUrl = async (min: number, max: number): Promise<void> => {
|
||||
const preSelectedFilter = new Map(selectedFilter);
|
||||
const preUserSelected = new Map(userSelectedFilter);
|
||||
|
||||
preSelectedFilter.set('duration', [String(max), String(min)]);
|
||||
preSelectedFilter.set('duration', [
|
||||
String(max * 1000000),
|
||||
String(min * 1000000),
|
||||
]);
|
||||
|
||||
const response = await getFilters({
|
||||
end: String(globalTime.maxTime),
|
||||
@ -137,18 +130,18 @@ function Duration(): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const onRangeSliderHandler = (number: [number, number]): void => {
|
||||
const onRangeSliderHandler = (number: [string, string]): void => {
|
||||
const [min, max] = number;
|
||||
|
||||
setPreMin(min.toString());
|
||||
setPreMax(max.toString());
|
||||
setPreMin(min);
|
||||
setPreMax(max);
|
||||
};
|
||||
|
||||
const debouncedFunction = useDebouncedFn(
|
||||
(min, max) => {
|
||||
updatedUrl(min as number, max as number);
|
||||
},
|
||||
500,
|
||||
1500,
|
||||
undefined,
|
||||
);
|
||||
|
||||
@ -156,8 +149,8 @@ function Duration(): JSX.Element {
|
||||
event,
|
||||
) => {
|
||||
const { value } = event.target;
|
||||
const min = parseFloat(preMin);
|
||||
const max = parseFloat(value) * 1000000;
|
||||
const min = preMin;
|
||||
const max = value;
|
||||
|
||||
onRangeSliderHandler([min, max]);
|
||||
debouncedFunction(min, max);
|
||||
@ -167,8 +160,9 @@ function Duration(): JSX.Element {
|
||||
event,
|
||||
) => {
|
||||
const { value } = event.target;
|
||||
const min = parseFloat(value) * 1000000;
|
||||
const max = parseFloat(preMax);
|
||||
const min = value;
|
||||
const max = preMax;
|
||||
|
||||
onRangeSliderHandler([min, max]);
|
||||
debouncedFunction(min, max);
|
||||
};
|
||||
@ -177,45 +171,48 @@ function Duration(): JSX.Element {
|
||||
updatedUrl(min, max);
|
||||
};
|
||||
|
||||
const TipComponent = useCallback((value) => {
|
||||
if (value === undefined) {
|
||||
return <div />;
|
||||
}
|
||||
return <div>{`${getMs(value?.toString())}ms`}</div>;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Container>
|
||||
<InputContainer>
|
||||
<Text>Min</Text>
|
||||
</InputContainer>
|
||||
<Input
|
||||
<InputComponent
|
||||
addonAfter="ms"
|
||||
type="number"
|
||||
onChange={onChangeMinHandler}
|
||||
value={getMs(preMin)}
|
||||
value={preMin}
|
||||
/>
|
||||
|
||||
<InputContainer>
|
||||
<Text>Max</Text>
|
||||
</InputContainer>
|
||||
<Input
|
||||
<InputComponent
|
||||
addonAfter="ms"
|
||||
type="number"
|
||||
onChange={onChangeMaxHandler}
|
||||
value={getMs(preMax)}
|
||||
value={preMax}
|
||||
/>
|
||||
</Container>
|
||||
|
||||
<Container>
|
||||
<Slider
|
||||
defaultValue={[defaultValue[0], defaultValue[1]]}
|
||||
min={parseFloat((preLocalMinDuration.current || 0).toString())}
|
||||
max={parseFloat((preLocalMaxDuration.current || 0).toString())}
|
||||
min={Number(getMs(String(preLocalMinDuration.current || 0)))}
|
||||
max={Number(getMs(String(preLocalMaxDuration.current || 0)))}
|
||||
range
|
||||
tipFormatter={(value): JSX.Element => {
|
||||
if (value === undefined) {
|
||||
return <div />;
|
||||
}
|
||||
return <div>{`${getMs(value?.toString())}ms`}</div>;
|
||||
}}
|
||||
tipFormatter={TipComponent}
|
||||
onChange={([min, max]): void => {
|
||||
onRangeSliderHandler([min, max]);
|
||||
onRangeSliderHandler([String(min), String(max)]);
|
||||
}}
|
||||
onAfterChange={onRangeHandler}
|
||||
value={[parseFloat(preMin), parseFloat(preMax)]}
|
||||
value={[Number(preMin), Number(preMax)]}
|
||||
/>
|
||||
</Container>
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Typography } from 'antd';
|
||||
import { Input, Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const DurationText = styled.div`
|
||||
@ -9,6 +9,19 @@ export const DurationText = styled.div`
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export const InputComponent = styled(Input)`
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type='number'] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
`;
|
||||
|
||||
export const InputContainer = styled.div`
|
||||
width: 100%;
|
||||
margin-top: 0.5rem;
|
||||
|
@ -0,0 +1,13 @@
|
||||
import dayjs from 'dayjs';
|
||||
import durationPlugin from 'dayjs/plugin/duration';
|
||||
|
||||
dayjs.extend(durationPlugin);
|
||||
|
||||
export const getMs = (value: string): string =>
|
||||
parseFloat(
|
||||
dayjs
|
||||
.duration({
|
||||
milliseconds: parseInt(value, 10) / 1000000,
|
||||
})
|
||||
.format('SSS'),
|
||||
).toFixed(2);
|
@ -73,11 +73,24 @@ function TagsKey(props: TagsKeysProps): JSX.Element {
|
||||
<AutoComplete
|
||||
dropdownClassName="certain-category-search-dropdown"
|
||||
dropdownMatchSelectWidth={500}
|
||||
style={{ width: 300 }}
|
||||
options={options}
|
||||
style={{ width: '100%' }}
|
||||
value={selectedKey}
|
||||
onChange={(value): void => {
|
||||
if (options && options.find((option) => option.value === value)) {
|
||||
allowClear
|
||||
showSearch
|
||||
options={options?.map((e) => ({
|
||||
label: e.label?.toString(),
|
||||
value: e.value,
|
||||
}))}
|
||||
filterOption={(inputValue, option): boolean =>
|
||||
option?.label?.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
||||
}
|
||||
onChange={(e): void => setSelectedKey(e)}
|
||||
onSelect={(value: unknown): void => {
|
||||
if (
|
||||
typeof value === 'string' &&
|
||||
options &&
|
||||
options.find((option) => option.value === value)
|
||||
) {
|
||||
setSelectedKey(value);
|
||||
|
||||
setLocalSelectedTags((tags) => [
|
||||
@ -89,8 +102,6 @@ function TagsKey(props: TagsKeysProps): JSX.Element {
|
||||
},
|
||||
...tags.slice(index + 1, tags.length),
|
||||
]);
|
||||
} else {
|
||||
setSelectedKey('');
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { Select } from 'antd';
|
||||
import getTagValue from 'api/trace/getTagValue';
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import { SelectComponent } from './styles';
|
||||
import { AutoCompleteComponent } from './styles';
|
||||
|
||||
function TagValue(props: TagValueProps): JSX.Element {
|
||||
const { tag, setLocalSelectedTags, index, tagKey } = props;
|
||||
@ -16,6 +16,7 @@ function TagValue(props: TagValueProps): JSX.Element {
|
||||
Operator: selectedOperator,
|
||||
Values: selectedValues,
|
||||
} = tag;
|
||||
const [localValue, setLocalValue] = useState<string>(selectedValues[0]);
|
||||
|
||||
const globalReducer = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
@ -34,22 +35,38 @@ function TagValue(props: TagValueProps): JSX.Element {
|
||||
);
|
||||
|
||||
return (
|
||||
<SelectComponent
|
||||
value={selectedValues[0]}
|
||||
<AutoCompleteComponent
|
||||
options={data?.payload?.map((e) => ({
|
||||
label: e.tagValues,
|
||||
value: e.tagValues,
|
||||
}))}
|
||||
allowClear
|
||||
defaultOpen
|
||||
showSearch
|
||||
filterOption={(inputValue, option): boolean =>
|
||||
option?.label.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
||||
}
|
||||
disabled={isLoading}
|
||||
value={localValue}
|
||||
onChange={(values): void => {
|
||||
if (typeof values === 'string') {
|
||||
setLocalValue(values);
|
||||
}
|
||||
}}
|
||||
onSelect={(value: unknown): void => {
|
||||
if (typeof value === 'string') {
|
||||
setLocalValue(value);
|
||||
setLocalSelectedTags((tags) => [
|
||||
...tags.slice(0, index),
|
||||
{
|
||||
Key: selectedKey,
|
||||
Operator: selectedOperator,
|
||||
Values: [...selectedValues, value],
|
||||
Values: [value],
|
||||
},
|
||||
...tags.slice(index + 1, tags.length),
|
||||
]);
|
||||
}
|
||||
}}
|
||||
loading={isLoading || false}
|
||||
>
|
||||
{data &&
|
||||
data.payload &&
|
||||
@ -58,7 +75,7 @@ function TagValue(props: TagValueProps): JSX.Element {
|
||||
{suggestion.tagValues}
|
||||
</Select.Option>
|
||||
))}
|
||||
</SelectComponent>
|
||||
</AutoCompleteComponent>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Select, Space } from 'antd';
|
||||
import { AutoComplete, Select, Space } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SpaceComponent = styled(Space)`
|
||||
@ -9,18 +9,23 @@ export const SpaceComponent = styled(Space)`
|
||||
|
||||
export const SelectComponent = styled(Select)`
|
||||
&&& {
|
||||
min-width: 170px;
|
||||
margin-right: 21.91px;
|
||||
margin-left: 21.92px;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
export const Container = styled(Space)`
|
||||
&&& {
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.ant-space-item:not(:last-child, :nth-child(2)) {
|
||||
width: 100%;
|
||||
}
|
||||
.ant-space-item:nth-child(2) {
|
||||
width: 50%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const IconContainer = styled.div`
|
||||
@ -31,3 +36,9 @@ export const IconContainer = styled.div`
|
||||
|
||||
margin-left: 1.125rem;
|
||||
`;
|
||||
|
||||
export const AutoCompleteComponent = styled(AutoComplete)`
|
||||
&&& {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { applyMiddleware, compose, createStore } from 'redux';
|
||||
import {
|
||||
applyMiddleware,
|
||||
compose,
|
||||
legacy_createStore as createStore,
|
||||
} from 'redux';
|
||||
import thunk, { ThunkMiddleware } from 'redux-thunk';
|
||||
import AppActions from 'types/actions';
|
||||
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
UPDATE_SELECTED_TAGS,
|
||||
UPDATE_SPAN_ORDER,
|
||||
UPDATE_SPAN_ORDER_PARAMS,
|
||||
UPDATE_SPAN_UPDATE_FILTER_DISPLAY_VALUE,
|
||||
UPDATE_SPANS_AGGREGATE,
|
||||
UPDATE_SPANS_AGGREGATE_PAGE_NUMBER,
|
||||
UPDATE_SPANS_AGGREGATE_PAGE_SIZE,
|
||||
@ -23,6 +24,8 @@ import {
|
||||
} from 'types/actions/trace';
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
export const INITIAL_FILTER_VALUE = 8;
|
||||
|
||||
const initialValue: TraceReducer = {
|
||||
filter: new Map(),
|
||||
filterToFetchData: ['duration', 'status', 'serviceName'],
|
||||
@ -53,6 +56,17 @@ const initialValue: TraceReducer = {
|
||||
loading: true,
|
||||
payload: { items: {} },
|
||||
},
|
||||
filterDisplayValue: new Map<TraceFilterEnum, number>([
|
||||
['component', INITIAL_FILTER_VALUE],
|
||||
['duration', INITIAL_FILTER_VALUE],
|
||||
['httpCode', INITIAL_FILTER_VALUE],
|
||||
['httpHost', INITIAL_FILTER_VALUE],
|
||||
['httpMethod', INITIAL_FILTER_VALUE],
|
||||
['httpUrl', INITIAL_FILTER_VALUE],
|
||||
['operation', INITIAL_FILTER_VALUE],
|
||||
['serviceName', INITIAL_FILTER_VALUE],
|
||||
['status', INITIAL_FILTER_VALUE],
|
||||
]),
|
||||
};
|
||||
|
||||
const traceReducer = (
|
||||
@ -251,6 +265,13 @@ const traceReducer = (
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_SPAN_UPDATE_FILTER_DISPLAY_VALUE: {
|
||||
return {
|
||||
...state,
|
||||
filterDisplayValue: action.payload,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -31,6 +31,8 @@ export const UPDATE_SPANS_AGGREGATE_PAGE_NUMBER =
|
||||
export const UPDATE_SPANS_AGGREGATE_PAGE_SIZE =
|
||||
'UPDATE_SPANS_AGGREGATE_PAGE_SIZE';
|
||||
export const UPDATE_SPAN_ORDER_PARAMS = 'UPDATE_SPAN_ORDER_PARAMS';
|
||||
export const UPDATE_SPAN_UPDATE_FILTER_DISPLAY_VALUE =
|
||||
'UPDATE_SPAN_UPDATE_FILTER_DISPLAY_VALUE';
|
||||
|
||||
export interface UpdateFilter {
|
||||
type: typeof UPDATE_TRACE_FILTER;
|
||||
@ -187,6 +189,11 @@ export interface UpdateSpanParams {
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateTraceFilterDisplayValue {
|
||||
type: typeof UPDATE_SPAN_UPDATE_FILTER_DISPLAY_VALUE;
|
||||
payload: TraceReducer['filterDisplayValue'];
|
||||
}
|
||||
|
||||
export type TraceActions =
|
||||
| UpdateFilter
|
||||
| GetTraceFilter
|
||||
@ -208,4 +215,5 @@ export type TraceActions =
|
||||
| UpdateSpanOrder
|
||||
| UpdateSpansAggregatePageNumber
|
||||
| UpdateSpanSize
|
||||
| UpdateSpanParams;
|
||||
| UpdateSpanParams
|
||||
| UpdateTraceFilterDisplayValue;
|
||||
|
@ -32,6 +32,7 @@ export interface TraceReducer {
|
||||
payload: PayloadProps;
|
||||
};
|
||||
yAxisUnit: string | undefined;
|
||||
filterDisplayValue: Map<TraceFilterEnum, number>;
|
||||
}
|
||||
|
||||
interface SpansAggregateData {
|
||||
|
Loading…
x
Reference in New Issue
Block a user