bug: Trace filter page fixes (#846)

* order is added in the url
* local min max duration is kept in memory to show min and max even after filtering by duration
* checkbox ordering does not change when the user selects or un-selects a checkbox
This commit is contained in:
palash-signoz 2022-04-05 13:23:08 +05:30 committed by GitHub
parent 147ed9f24b
commit 3f2a4d6eac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 308 additions and 167 deletions

View File

@ -93,11 +93,15 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
if (response.statusCode === 200) { if (response.statusCode === 200) {
const updatedFilter = getFilter(response.payload); const updatedFilter = getFilter(response.payload);
updatedFilter.forEach((value, key) => { // updatedFilter.forEach((value, key) => {
if (key !== 'duration' && name !== key) { // if (key !== 'duration' && name !== key) {
preUserSelectedMap.set(key, Object.keys(value)); // preUserSelectedMap.set(key, Object.keys(value));
} // }
});
// if (key === 'duration') {
// newSelectedMap.set('duration', [value.maxDuration, value.minDuration]);
// }
// });
updatedFilter.set(name, { updatedFilter.set(name, {
[`${keyValue}`]: '-1', [`${keyValue}`]: '-1',
@ -115,6 +119,7 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
selectedFilter: newSelectedMap, selectedFilter: newSelectedMap,
userSelected: preUserSelectedMap, userSelected: preUserSelectedMap,
isFilterExclude: preIsFilterExclude, isFilterExclude: preIsFilterExclude,
order: spansAggregate.order,
}, },
}); });
@ -128,6 +133,7 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
updatedFilter, updatedFilter,
preIsFilterExclude, preIsFilterExclude,
preUserSelectedMap, preUserSelectedMap,
spansAggregate.order,
); );
} else { } else {
setIsLoading(false); setIsLoading(false);

View File

@ -18,16 +18,26 @@ function CommonCheckBox(props: CommonCheckBoxProps): JSX.Element {
return ( return (
<> <>
{statusObj.map((e) => ( {statusObj
<CheckBoxComponent .sort((a, b) => {
key={e} const countA = +status[a];
{...{ const countB = +status[b];
name,
keyValue: e, if (countA === countB) {
value: status[e], return a.length - b.length;
}} }
/> return countA - countB;
))} })
.map((e) => (
<CheckBoxComponent
key={e}
{...{
name,
keyValue: e,
value: status[e],
}}
/>
))}
</> </>
); );
} }

View File

@ -5,7 +5,7 @@ import getFilters from 'api/trace/getFilters';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import durationPlugin from 'dayjs/plugin/duration'; import durationPlugin from 'dayjs/plugin/duration';
import useDebouncedFn from 'hooks/useDebouncedFunction'; import useDebouncedFn from 'hooks/useDebouncedFunction';
import React, { useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import { getFilter, updateURL } from 'store/actions/trace/util'; import { getFilter, updateURL } from 'store/actions/trace/util';
@ -20,11 +20,13 @@ import { Container, InputContainer, Text } from './styles';
dayjs.extend(durationPlugin); dayjs.extend(durationPlugin);
const getMs = (value: string): string => { const getMs = (value: string): string => {
return dayjs return parseFloat(
.duration({ dayjs
milliseconds: parseInt(value, 10) / 1000000, .duration({
}) milliseconds: parseInt(value, 10) / 1000000,
.format('SSS'); })
.format('SSS'),
).toFixed(2);
}; };
function Duration(): JSX.Element { function Duration(): JSX.Element {
@ -43,9 +45,10 @@ function Duration(): JSX.Element {
(state) => state.globalTime, (state) => state.globalTime,
); );
const getDuration = (): const preLocalMaxDuration = useRef<number>();
| { maxDuration: string; minDuration: string } const preLocalMinDuration = useRef<number>();
| Record<string, string> => {
const getDuration = useMemo(() => {
const selectedDuration = selectedFilter.get('duration'); const selectedDuration = selectedFilter.get('duration');
if (selectedDuration) { if (selectedDuration) {
@ -56,17 +59,29 @@ function Duration(): JSX.Element {
} }
return filter.get('duration') || {}; return filter.get('duration') || {};
}; }, [selectedFilter, filter]);
const duration = getDuration(); const [preMax, setPreMax] = useState<string>('');
const [preMin, setPreMin] = useState<string>('');
const maxDuration = duration.maxDuration || '0'; useEffect(() => {
const minDuration = duration.minDuration || '0'; const duration = getDuration || {};
const [localMax, setLocalMax] = useState<string>(maxDuration); const maxDuration = duration.maxDuration || '0';
const [localMin, setLocalMin] = useState<string>(minDuration); const minDuration = duration.minDuration || '0';
const defaultValue = [parseFloat(minDuration), parseFloat(maxDuration)]; if (preLocalMaxDuration.current === undefined) {
preLocalMaxDuration.current = parseFloat(maxDuration);
}
if (preLocalMinDuration.current === undefined) {
preLocalMinDuration.current = parseFloat(minDuration);
}
setPreMax(maxDuration);
setPreMin(minDuration);
}, [getDuration]);
const defaultValue = [parseFloat(preMin), parseFloat(preMax)];
const updatedUrl = async (min: number, max: number): Promise<void> => { const updatedUrl = async (min: number, max: number): Promise<void> => {
const preSelectedFilter = new Map(selectedFilter); const preSelectedFilter = new Map(selectedFilter);
@ -74,7 +89,6 @@ function Duration(): JSX.Element {
preSelectedFilter.set('duration', [String(max), String(min)]); preSelectedFilter.set('duration', [String(max), String(min)]);
console.log('on the update Url');
const response = await getFilters({ const response = await getFilters({
end: String(globalTime.maxTime), end: String(globalTime.maxTime),
getFilters: filterToFetchData, getFilters: filterToFetchData,
@ -87,8 +101,9 @@ function Duration(): JSX.Element {
const preFilter = getFilter(response.payload); const preFilter = getFilter(response.payload);
preFilter.forEach((value, key) => { preFilter.forEach((value, key) => {
if (key !== 'duration') { const values = Object.keys(value);
preUserSelected.set(key, Object.keys(value)); if (key !== 'duration' && values.length) {
preUserSelected.set(key, values);
} }
}); });
@ -102,6 +117,7 @@ function Duration(): JSX.Element {
selectedTags, selectedTags,
userSelected: preUserSelected, userSelected: preUserSelected,
isFilterExclude, isFilterExclude,
order: spansAggregate.order,
}, },
}); });
@ -113,6 +129,7 @@ function Duration(): JSX.Element {
preFilter, preFilter,
isFilterExclude, isFilterExclude,
userSelectedFilter, userSelectedFilter,
spansAggregate.order,
); );
} }
}; };
@ -120,13 +137,12 @@ function Duration(): JSX.Element {
const onRangeSliderHandler = (number: [number, number]): void => { const onRangeSliderHandler = (number: [number, number]): void => {
const [min, max] = number; const [min, max] = number;
setLocalMin(min.toString()); setPreMin(min.toString());
setLocalMax(max.toString()); setPreMax(max.toString());
}; };
const debouncedFunction = useDebouncedFn( const debouncedFunction = useDebouncedFn(
(min, max) => { (min, max) => {
console.log('debounce function');
updatedUrl(min as number, max as number); updatedUrl(min as number, max as number);
}, },
500, 500,
@ -137,11 +153,9 @@ function Duration(): JSX.Element {
event, event,
) => { ) => {
const { value } = event.target; const { value } = event.target;
const min = parseFloat(localMin); const min = parseFloat(preMin);
const max = parseFloat(value) * 1000000; const max = parseFloat(value) * 1000000;
console.log('on change in max');
onRangeSliderHandler([min, max]); onRangeSliderHandler([min, max]);
debouncedFunction(min, max); debouncedFunction(min, max);
}; };
@ -151,9 +165,8 @@ function Duration(): JSX.Element {
) => { ) => {
const { value } = event.target; const { value } = event.target;
const min = parseFloat(value) * 1000000; const min = parseFloat(value) * 1000000;
const max = parseFloat(localMax); const max = parseFloat(preMax);
onRangeSliderHandler([min, max]); onRangeSliderHandler([min, max]);
console.log('on change in min');
debouncedFunction(min, max); debouncedFunction(min, max);
}; };
@ -170,7 +183,7 @@ function Duration(): JSX.Element {
<Input <Input
addonAfter="ms" addonAfter="ms"
onChange={onChangeMinHandler} onChange={onChangeMinHandler}
value={getMs(localMin)} value={getMs(preMin)}
/> />
<InputContainer> <InputContainer>
@ -179,27 +192,27 @@ function Duration(): JSX.Element {
<Input <Input
addonAfter="ms" addonAfter="ms"
onChange={onChangeMaxHandler} onChange={onChangeMaxHandler}
value={getMs(localMax)} value={getMs(preMax)}
/> />
</Container> </Container>
<Container> <Container>
<Slider <Slider
defaultValue={[defaultValue[0], defaultValue[1]]} defaultValue={[defaultValue[0], defaultValue[1]]}
min={parseFloat((filter.get('duration') || {}).minDuration)} min={parseFloat((preLocalMinDuration.current || 0).toString())}
max={parseFloat((filter.get('duration') || {}).maxDuration)} max={parseFloat((preLocalMaxDuration.current || 0).toString())}
range range
tipFormatter={(value): JSX.Element => { tipFormatter={(value): JSX.Element => {
if (value === undefined) { if (value === undefined) {
return <div />; return <div />;
} }
return <div>{`${getMs(value.toString())}ms`}</div>; return <div>{`${getMs(value?.toString())}ms`}</div>;
}} }}
onChange={([min, max]): void => { onChange={([min, max]): void => {
onRangeSliderHandler([min, max]); onRangeSliderHandler([min, max]);
}} }}
onAfterChange={onRangeHandler} onAfterChange={onRangeHandler}
value={[parseFloat(localMin), parseFloat(localMax)]} value={[parseFloat(preMin), parseFloat(preMax)]}
/> />
</Container> </Container>
</div> </div>

View File

@ -21,7 +21,7 @@ import {
ButtonContainer, ButtonContainer,
Container, Container,
IconContainer, IconContainer,
TextCotainer, TextContainer,
} from './styles'; } from './styles';
const { Text } = Typography; const { Text } = Typography;
@ -64,16 +64,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
const getprepdatedSelectedFilter = new Map(selectedFilter); const getprepdatedSelectedFilter = new Map(selectedFilter);
const getPreUserSelected = new Map(userSelectedFilter); const getPreUserSelected = new Map(userSelectedFilter);
if (!isDefaultOpen) { updatedFilterData = [PanelName];
updatedFilterData = [PanelName];
} else {
// removing the selected filter
updatedFilterData = [
...filterToFetchData.filter((name) => name !== PanelName),
];
getprepdatedSelectedFilter.delete(PanelName);
getPreUserSelected.delete(PanelName);
}
const response = await getFilters({ const response = await getFilters({
end: String(global.maxTime), end: String(global.maxTime),
@ -86,33 +77,14 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
if (response.statusCode === 200) { if (response.statusCode === 200) {
const updatedFilter = getFilter(response.payload); const updatedFilter = getFilter(response.payload);
// is closed if (!getPreUserSelected.has(PanelName)) {
if (!isDefaultOpen) {
// getprepdatedSelectedFilter.set(
// props.name,
// Object.keys(updatedFilter.get(props.name) || {}),
// );
getPreUserSelected.set( getPreUserSelected.set(
PanelName, PanelName,
Object.keys(updatedFilter.get(PanelName) || {}), Object.keys(updatedFilter.get(PanelName) || []),
); );
updatedFilterData = [...filterToFetchData, PanelName];
} }
// now append the non prop.name trace filter enum over the list updatedFilterData = [...filterToFetchData, PanelName];
// selectedFilter.forEach((value, key) => {
// if (key !== props.name) {
// getprepdatedSelectedFilter.set(key, value);
// }
// });
getPreUserSelected.forEach((value, key) => {
if (key !== PanelName) {
getPreUserSelected.set(key, value);
}
});
filter.forEach((value, key) => { filter.forEach((value, key) => {
if (key !== PanelName) { if (key !== PanelName) {
updatedFilter.set(key, value); updatedFilter.set(key, value);
@ -129,6 +101,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
selectedTags, selectedTags,
userSelected: getPreUserSelected, userSelected: getPreUserSelected,
isFilterExclude, isFilterExclude,
order: spansAggregate.order,
}, },
}); });
@ -140,6 +113,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
updatedFilter, updatedFilter,
isFilterExclude, isFilterExclude,
getPreUserSelected, getPreUserSelected,
spansAggregate.order,
); );
} else { } else {
notification.error({ notification.error({
@ -155,6 +129,44 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
} }
}; };
/**
* @description this function removes the selected filter
*/
const onCloseHandler = (): void => {
const preSelectedFilter = new Map(selectedFilter);
// removing the filter from filter to fetch the data
const preFilterToFetchTheData = [
...filterToFetchData.filter((name) => name !== PanelName),
];
preSelectedFilter.delete(PanelName);
dispatch({
type: UPDATE_ALL_FILTERS,
payload: {
current: spansAggregate.currentPage,
filter,
filterToFetchData: preFilterToFetchTheData,
selectedFilter: preSelectedFilter,
selectedTags,
userSelected: userSelectedFilter,
isFilterExclude,
order: spansAggregate.order,
},
});
updateURL(
preSelectedFilter,
preFilterToFetchTheData,
spansAggregate.currentPage,
selectedTags,
filter,
isFilterExclude,
userSelectedFilter,
spansAggregate.order,
);
};
const onClearAllHandler = async (): Promise<void> => { const onClearAllHandler = async (): Promise<void> => {
try { try {
setIsLoading(true); setIsLoading(true);
@ -177,18 +189,19 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
}); });
if (response.statusCode === 200 && response.payload) { if (response.statusCode === 200 && response.payload) {
const getUpatedFilter = getFilter(response.payload); const getUpdatedFilter = getFilter(response.payload);
dispatch({ dispatch({
type: UPDATE_ALL_FILTERS, type: UPDATE_ALL_FILTERS,
payload: { payload: {
current: spansAggregate.currentPage, current: spansAggregate.currentPage,
filter: getUpatedFilter, filter: getUpdatedFilter,
filterToFetchData, filterToFetchData,
selectedFilter: updatedFilter, selectedFilter: updatedFilter,
selectedTags, selectedTags,
userSelected: preUserSelected, userSelected: preUserSelected,
isFilterExclude: postIsFilterExclude, isFilterExclude: postIsFilterExclude,
order: spansAggregate.order,
}, },
}); });
@ -197,9 +210,10 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
filterToFetchData, filterToFetchData,
spansAggregate.currentPage, spansAggregate.currentPage,
selectedTags, selectedTags,
getUpatedFilter, getUpdatedFilter,
postIsFilterExclude, postIsFilterExclude,
preUserSelected, preUserSelected,
spansAggregate.order,
); );
} else { } else {
notification.error({ notification.error({
@ -280,7 +294,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
aria-disabled={filterLoading || isLoading} aria-disabled={filterLoading || isLoading}
aria-expanded={IsPanelOpen} aria-expanded={IsPanelOpen}
> >
<TextCotainer onClick={onExpandHandler}> <TextContainer onClick={isDefaultOpen ? onCloseHandler : onExpandHandler}>
<IconContainer> <IconContainer>
{!IsPanelOpen ? <RightOutlined /> : <DownOutlined />} {!IsPanelOpen ? <RightOutlined /> : <DownOutlined />}
</IconContainer> </IconContainer>
@ -288,7 +302,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
<Text style={{ textTransform: 'capitalize' }} ellipsis> <Text style={{ textTransform: 'capitalize' }} ellipsis>
{AllPanelHeading.find((e) => e.key === PanelName)?.displayValue || ''} {AllPanelHeading.find((e) => e.key === PanelName)?.displayValue || ''}
</Text> </Text>
</TextCotainer> </TextContainer>
{PanelName !== 'duration' && ( {PanelName !== 'duration' && (
<ButtonContainer> <ButtonContainer>

View File

@ -30,7 +30,7 @@ export const IconContainer = styled.div`
} }
`; `;
export const TextCotainer = styled.div` export const TextContainer = styled.div`
&&& { &&& {
display: flex; display: flex;
cursor: pointer; cursor: pointer;

View File

@ -5,7 +5,7 @@ import { connect, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
import { UpdateTagIsError } from 'store/actions/trace/updateIsTagsError'; import { UpdateTagIsError } from 'store/actions/trace/updateIsTagsError';
import { UpdateTagVisiblity } from 'store/actions/trace/updateTagPanelVisiblity'; import { UpdateTagVisibility } from 'store/actions/trace/updateTagPanelVisiblity';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { TraceReducer } from 'types/reducer/trace'; import { TraceReducer } from 'types/reducer/trace';
@ -27,7 +27,7 @@ const { Paragraph } = Typography;
function AllTags({ function AllTags({
updateTagIsError, updateTagIsError,
onChangeHandler, onChangeHandler,
updateTagVisiblity, updateTagVisibility,
updateFilters, updateFilters,
}: AllTagsProps): JSX.Element { }: AllTagsProps): JSX.Element {
const traces = useSelector<AppState, TraceReducer>((state) => state.traces); const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
@ -63,7 +63,7 @@ function AllTags({
onChangeHandler(parsedQuery.payload); onChangeHandler(parsedQuery.payload);
updateFilters(localSelectedTags); updateFilters(localSelectedTags);
updateTagIsError(false); updateTagIsError(false);
updateTagVisiblity(false); updateTagVisibility(false);
} }
}; };
@ -75,7 +75,7 @@ function AllTags({
return ( return (
<ErrorContainer> <ErrorContainer>
<Paragraph style={{ color: '#E89A3C' }}> <Paragraph style={{ color: '#E89A3C' }}>
Unrecognised query format. Please reset your query by clicking `X` in the Unrecognized query format. Please reset your query by clicking `X` in the
search bar above. search bar above.
</Paragraph> </Paragraph>
@ -131,14 +131,14 @@ function AllTags({
interface DispatchProps { interface DispatchProps {
updateTagIsError: (value: boolean) => void; updateTagIsError: (value: boolean) => void;
updateTagVisiblity: (value: boolean) => void; updateTagVisibility: (value: boolean) => void;
} }
const mapDispatchToProps = ( const mapDispatchToProps = (
dispatch: ThunkDispatch<unknown, unknown, AppActions>, dispatch: ThunkDispatch<unknown, unknown, AppActions>,
): DispatchProps => ({ ): DispatchProps => ({
updateTagIsError: bindActionCreators(UpdateTagIsError, dispatch), updateTagIsError: bindActionCreators(UpdateTagIsError, dispatch),
updateTagVisiblity: bindActionCreators(UpdateTagVisiblity, dispatch), updateTagVisibility: bindActionCreators(UpdateTagVisibility, dispatch),
}); });
interface AllTagsProps extends DispatchProps { interface AllTagsProps extends DispatchProps {

View File

@ -6,7 +6,7 @@ import { connect, useDispatch, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux'; import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
import { UpdateTagIsError } from 'store/actions/trace/updateIsTagsError'; import { UpdateTagIsError } from 'store/actions/trace/updateIsTagsError';
import { UpdateTagVisiblity } from 'store/actions/trace/updateTagPanelVisiblity'; import { UpdateTagVisibility } from 'store/actions/trace/updateTagPanelVisiblity';
import { updateURL } from 'store/actions/trace/util'; import { updateURL } from 'store/actions/trace/util';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
@ -18,7 +18,7 @@ import { Container, SearchComponent } from './styles';
import { parseQueryToTags, parseTagsToQuery } from './util'; import { parseQueryToTags, parseTagsToQuery } from './util';
function Search({ function Search({
updateTagVisiblity, updateTagVisibility,
updateTagIsError, updateTagIsError,
}: SearchProps): JSX.Element { }: SearchProps): JSX.Element {
const traces = useSelector<AppState, TraceReducer>((state) => state.traces); const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
@ -66,7 +66,7 @@ function Search({
!(e.ariaSelected === 'true') && !(e.ariaSelected === 'true') &&
traces.isTagModalOpen traces.isTagModalOpen
) { ) {
updateTagVisiblity(false); updateTagVisibility(false);
} }
}); });
@ -75,7 +75,7 @@ function Search({
}; };
const setIsTagsModalHandler = (value: boolean): void => { const setIsTagsModalHandler = (value: boolean): void => {
updateTagVisiblity(value); updateTagVisibility(value);
}; };
const onFocusHandler: React.FocusEventHandler<HTMLInputElement> = (e) => { const onFocusHandler: React.FocusEventHandler<HTMLInputElement> = (e) => {
@ -96,6 +96,7 @@ function Search({
selectedFilter: traces.selectedFilter, selectedFilter: traces.selectedFilter,
userSelected: traces.userSelectedFilter, userSelected: traces.userSelectedFilter,
isFilterExclude: traces.isFilterExclude, isFilterExclude: traces.isFilterExclude,
order: traces.spansAggregate.order,
}, },
}); });
@ -107,6 +108,7 @@ function Search({
traces.filter, traces.filter,
traces.isFilterExclude, traces.isFilterExclude,
traces.userSelectedFilter, traces.userSelectedFilter,
traces.spansAggregate.order,
); );
}; };
@ -124,7 +126,7 @@ function Search({
enterButton={<CaretRightFilled />} enterButton={<CaretRightFilled />}
onSearch={(string): void => { onSearch={(string): void => {
if (string.length === 0) { if (string.length === 0) {
updateTagVisiblity(false); updateTagVisibility(false);
updateFilters([]); updateFilters([]);
return; return;
} }
@ -135,7 +137,7 @@ function Search({
updateTagIsError(true); updateTagIsError(true);
} else { } else {
updateTagIsError(false); updateTagIsError(false);
updateTagVisiblity(false); updateTagVisibility(false);
updateFilters(payload); updateFilters(payload);
} }
}} }}
@ -150,14 +152,14 @@ function Search({
} }
interface DispatchProps { interface DispatchProps {
updateTagVisiblity: (value: boolean) => void; updateTagVisibility: (value: boolean) => void;
updateTagIsError: (value: boolean) => void; updateTagIsError: (value: boolean) => void;
} }
const mapDispatchToProps = ( const mapDispatchToProps = (
dispatch: ThunkDispatch<unknown, unknown, AppActions>, dispatch: ThunkDispatch<unknown, unknown, AppActions>,
): DispatchProps => ({ ): DispatchProps => ({
updateTagVisiblity: bindActionCreators(UpdateTagVisiblity, dispatch), updateTagVisibility: bindActionCreators(UpdateTagVisibility, dispatch),
updateTagIsError: bindActionCreators(UpdateTagIsError, dispatch), updateTagIsError: bindActionCreators(UpdateTagIsError, dispatch),
}); });

View File

@ -4,34 +4,32 @@ import ROUTES from 'constants/routes';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration'; import duration from 'dayjs/plugin/duration';
import React from 'react'; import React from 'react';
import { connect, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { bindActionCreators } from 'redux'; import { Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk'; import { updateURL } from 'store/actions/trace/util';
import {
GetSpansAggregate,
GetSpansAggregateProps,
} from 'store/actions/trace/getInitialSpansAggregate';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { GlobalReducer } from 'types/reducer/globalTime'; import { UPDATE_SPAN_ORDER } from 'types/actions/trace';
import { TraceReducer } from 'types/reducer/trace'; import { TraceReducer } from 'types/reducer/trace';
dayjs.extend(duration); dayjs.extend(duration);
function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element { function TraceTable(): JSX.Element {
const { const {
spansAggregate, spansAggregate,
selectedFilter, selectedFilter,
selectedTags, selectedTags,
filterLoading, filterLoading,
userSelectedFilter,
filter,
isFilterExclude,
filterToFetchData,
} = useSelector<AppState, TraceReducer>((state) => state.traces); } = useSelector<AppState, TraceReducer>((state) => state.traces);
const globalTime = useSelector<AppState, GlobalReducer>( const dispatch = useDispatch<Dispatch<AppActions>>();
(state) => state.globalTime,
);
const { loading, total } = spansAggregate; const { loading, total, order: spansAggregateOrder } = spansAggregate;
type TableType = FlatArray<TraceReducer['spansAggregate']['data'], 1>; type TableType = FlatArray<TraceReducer['spansAggregate']['data'], 1>;
@ -101,7 +99,8 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element {
<Typography> <Typography>
{`${dayjs {`${dayjs
.duration({ milliseconds: value / 1000000 }) .duration({ milliseconds: value / 1000000 })
.asMilliseconds()} ms`} .asMilliseconds()
.toFixed(2)} ms`}
</Typography> </Typography>
</Link> </Link>
), ),
@ -126,17 +125,27 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element {
sort, sort,
) => { ) => {
if (!Array.isArray(sort)) { if (!Array.isArray(sort)) {
const { order = 'ascend' } = sort; const { order = spansAggregateOrder } = sort;
if (props.current && props.pageSize) { if (props.current && props.pageSize) {
getSpansAggregate({ const spanOrder = order || spansAggregateOrder;
maxTime: globalTime.maxTime,
minTime: globalTime.minTime, dispatch({
selectedFilter, type: UPDATE_SPAN_ORDER,
current: props.current, payload: {
pageSize: props.pageSize, order: spanOrder,
selectedTags, },
order: order === 'ascend' ? 'ascending' : 'descending',
}); });
updateURL(
selectedFilter,
filterToFetchData,
props.current,
selectedTags,
filter,
isFilterExclude,
userSelectedFilter,
spanOrder,
);
} }
} }
}; };
@ -147,7 +156,7 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element {
dataSource={spansAggregate.data} dataSource={spansAggregate.data}
loading={loading || filterLoading} loading={loading || filterLoading}
columns={columns} columns={columns}
rowKey="timestamp" rowKey={(record): string => `${record.traceID}-${record.spanID}`}
style={{ style={{
cursor: 'pointer', cursor: 'pointer',
}} }}
@ -158,20 +167,9 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element {
position: ['bottomLeft'], position: ['bottomLeft'],
total, total,
}} }}
sortDirections={['ascend', 'descend']}
/> />
); );
} }
interface DispatchProps { export default TraceTable;
getSpansAggregate: (props: GetSpansAggregateProps) => void;
}
const mapDispatchToProps = (
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
): DispatchProps => ({
getSpansAggregate: bindActionCreators(GetSpansAggregate, dispatch),
});
type TraceProps = DispatchProps;
export default connect(null, mapDispatchToProps)(TraceTable);

View File

@ -63,7 +63,7 @@ function Trace({
current: spansAggregate.currentPage, current: spansAggregate.currentPage,
pageSize: spansAggregate.pageSize, pageSize: spansAggregate.pageSize,
selectedTags, selectedTags,
order: 'ascending', order: spansAggregate.order === 'ascend' ? 'ascending' : 'descending',
}); });
}, [ }, [
selectedTags, selectedTags,
@ -73,6 +73,7 @@ function Trace({
getSpansAggregate, getSpansAggregate,
spansAggregate.currentPage, spansAggregate.currentPage,
spansAggregate.pageSize, spansAggregate.pageSize,
spansAggregate.order,
]); ]);
useEffect(() => { useEffect(() => {
@ -93,8 +94,8 @@ function Trace({
selectedTags, selectedTags,
maxTime, maxTime,
minTime, minTime,
isFilterExclude,
getSpans, getSpans,
isFilterExclude,
]); ]);
useEffect(() => { useEffect(() => {

View File

@ -18,6 +18,7 @@ import {
parseIsSkippedSelection, parseIsSkippedSelection,
parseQueryIntoCurrent, parseQueryIntoCurrent,
parseQueryIntoFilter, parseQueryIntoFilter,
parseQueryIntoOrder,
parseQueryIntoSelectedTags, parseQueryIntoSelectedTags,
parseSelectedFilter, parseSelectedFilter,
} from './util'; } from './util';
@ -66,6 +67,11 @@ export const GetInitialTraceFilter = (
traces.spansAggregate.currentPage, traces.spansAggregate.currentPage,
); );
const parsedQueryOrder = parseQueryIntoOrder(
query,
traces.spansAggregate.order,
);
const isSelectionSkipped = parseIsSkippedSelection(query); const isSelectionSkipped = parseIsSkippedSelection(query);
const parsedSelectedTags = parseQueryIntoSelectedTags( const parsedSelectedTags = parseQueryIntoSelectedTags(
@ -148,6 +154,7 @@ export const GetInitialTraceFilter = (
selectedTags: parsedSelectedTags.currentValue, selectedTags: parsedSelectedTags.currentValue,
userSelected: getUserSelected.currentValue, userSelected: getUserSelected.currentValue,
isFilterExclude: getIsFilterExcluded.currentValue, isFilterExclude: getIsFilterExcluded.currentValue,
order: parsedQueryOrder.currentValue,
}, },
}); });
} else { } else {

View File

@ -3,11 +3,13 @@ import getSpansAggregate from 'api/trace/getSpansAggregate';
import { Dispatch, Store } from 'redux'; import { Dispatch, Store } from 'redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { UPDATE_SPANS_AGGREEGATE } from 'types/actions/trace'; import { UPDATE_SPANS_AGGREGATE } from 'types/actions/trace';
import { Props as GetSpanAggregateProps } from 'types/api/trace/getSpanAggregate'; import { Props as GetSpanAggregateProps } from 'types/api/trace/getSpanAggregate';
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 { updateURL } from './util';
export const GetSpansAggregate = ( export const GetSpansAggregate = (
props: GetSpansAggregateProps, props: GetSpansAggregateProps,
): (( ): ((
@ -29,10 +31,12 @@ export const GetSpansAggregate = (
return; return;
} }
const order = props.order === 'ascending' ? 'ascend' : 'descend';
try { try {
// triggering loading // triggering loading
dispatch({ dispatch({
type: UPDATE_SPANS_AGGREEGATE, type: UPDATE_SPANS_AGGREGATE,
payload: { payload: {
spansAggregate: { spansAggregate: {
currentPage: props.current, currentPage: props.current,
@ -41,6 +45,7 @@ export const GetSpansAggregate = (
error: false, error: false,
total: spansAggregate.total, total: spansAggregate.total,
pageSize: props.pageSize, pageSize: props.pageSize,
order,
}, },
}, },
}); });
@ -53,12 +58,12 @@ export const GetSpansAggregate = (
offset: props.current * props.pageSize - props.pageSize, offset: props.current * props.pageSize - props.pageSize,
selectedTags: props.selectedTags, selectedTags: props.selectedTags,
isFilterExclude: traces.isFilterExclude, isFilterExclude: traces.isFilterExclude,
order: props.order, order,
}); });
if (response.statusCode === 200) { if (response.statusCode === 200) {
dispatch({ dispatch({
type: UPDATE_SPANS_AGGREEGATE, type: UPDATE_SPANS_AGGREGATE,
payload: { payload: {
spansAggregate: { spansAggregate: {
currentPage: props.current, currentPage: props.current,
@ -67,16 +72,28 @@ export const GetSpansAggregate = (
error: false, error: false,
total: response.payload.totalSpans, total: response.payload.totalSpans,
pageSize: props.pageSize, pageSize: props.pageSize,
order,
}, },
}, },
}); });
updateURL(
traces.selectedFilter,
traces.filterToFetchData,
props.current,
traces.selectedTags,
traces.filter,
traces.isFilterExclude,
traces.userSelectedFilter,
order,
);
} else { } else {
notification.error({ notification.error({
message: response.error || 'Something went wrong', message: response.error || 'Something went wrong',
}); });
dispatch({ dispatch({
type: UPDATE_SPANS_AGGREEGATE, type: UPDATE_SPANS_AGGREGATE,
payload: { payload: {
spansAggregate: { spansAggregate: {
currentPage: props.current, currentPage: props.current,
@ -85,13 +102,14 @@ export const GetSpansAggregate = (
error: true, error: true,
total: spansAggregate.total, total: spansAggregate.total,
pageSize: props.pageSize, pageSize: props.pageSize,
order,
}, },
}, },
}); });
} }
} catch (error) { } catch (error) {
dispatch({ dispatch({
type: UPDATE_SPANS_AGGREEGATE, type: UPDATE_SPANS_AGGREGATE,
payload: { payload: {
spansAggregate: { spansAggregate: {
currentPage: props.current, currentPage: props.current,
@ -100,6 +118,7 @@ export const GetSpansAggregate = (
error: true, error: true,
total: spansAggregate.total, total: spansAggregate.total,
pageSize: props.pageSize, pageSize: props.pageSize,
order,
}, },
}, },
}); });

View File

@ -1,4 +1,3 @@
export * from './current';
export * from './filter'; export * from './filter';
export * from './filterToFetchData'; export * from './filterToFetchData';
export * from './isFilterExclude'; export * from './isFilterExclude';
@ -6,3 +5,5 @@ export * from './minMaxTime';
export * from './selectedFilter'; export * from './selectedFilter';
export * from './selectedTags'; export * from './selectedTags';
export * from './skippedSelected'; export * from './skippedSelected';
export * from './spanAggregateCurrentPage';
export * from './spanAggregateOrder';

View File

@ -10,7 +10,7 @@ export const parseQueryIntoCurrent = (
let current = 1; let current = 1;
const selected = url.get('current'); const selected = url.get('spanAggregateCurrentPage');
if (selected) { if (selected) {
try { try {

View File

@ -0,0 +1,39 @@
import { TraceReducer } from 'types/reducer/trace';
import { ParsedUrl } from '../util';
export const parseQueryIntoOrder = (
query: string,
stateCurrent: TraceReducer['spansAggregate']['order'],
): ParsedUrl<TraceReducer['spansAggregate']['order']> => {
const url = new URLSearchParams(query);
let current = 'ascend';
const selected = url.get('spanAggregateOrder');
if (selected) {
try {
const parsedValue = selected;
if (parsedValue && typeof parsedValue === 'string') {
current = parsedValue;
}
} catch (error) {
console.log(error);
console.log('error while parsing json');
}
}
if (selected) {
return {
currentValue: current,
urlValue: current,
};
}
return {
currentValue: stateCurrent,
urlValue: current,
};
};

View File

@ -47,6 +47,7 @@ export const SelectedTraceFilter = (props: {
traces.filter, traces.filter,
traces.isFilterExclude, traces.isFilterExclude,
traces.userSelectedFilter, traces.userSelectedFilter,
traces.spansAggregate.order,
); );
}; };
}; };

View File

@ -1,14 +1,14 @@
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { UPDATE_TAG_MODAL_VISIBLITY } from 'types/actions/trace'; import { UPDATE_TAG_MODAL_VISIBILITY } from 'types/actions/trace';
import { TraceReducer } from 'types/reducer/trace'; import { TraceReducer } from 'types/reducer/trace';
export const UpdateTagVisiblity = ( export const UpdateTagVisibility = (
isTagModalOpen: TraceReducer['isTagModalOpen'], isTagModalOpen: TraceReducer['isTagModalOpen'],
): ((dispatch: Dispatch<AppActions>) => void) => { ): ((dispatch: Dispatch<AppActions>) => void) => {
return (dispatch): void => { return (dispatch): void => {
dispatch({ dispatch({
type: UPDATE_TAG_MODAL_VISIBLITY, type: UPDATE_TAG_MODAL_VISIBILITY,
payload: { payload: {
isTagModalOpen, isTagModalOpen,
}, },

View File

@ -18,11 +18,12 @@ export function isTraceFilterEnum(
export const updateURL = ( export const updateURL = (
selectedFilter: TraceReducer['selectedFilter'], selectedFilter: TraceReducer['selectedFilter'],
filterToFetchData: TraceReducer['filterToFetchData'], filterToFetchData: TraceReducer['filterToFetchData'],
current: TraceReducer['spansAggregate']['total'], spanAggregateCurrentPage: TraceReducer['spansAggregate']['currentPage'],
selectedTags: TraceReducer['selectedTags'], selectedTags: TraceReducer['selectedTags'],
filter: TraceReducer['filter'], filter: TraceReducer['filter'],
isFilterExclude: TraceReducer['isFilterExclude'], isFilterExclude: TraceReducer['isFilterExclude'],
userSelectedFilter: TraceReducer['userSelectedFilter'], userSelectedFilter: TraceReducer['userSelectedFilter'],
spanAggregateOrder: TraceReducer['spansAggregate']['order'],
): void => { ): void => {
const search = new URLSearchParams(window.location.search); const search = new URLSearchParams(window.location.search);
const preResult: { key: string; value: string }[] = []; const preResult: { key: string; value: string }[] = [];
@ -30,11 +31,12 @@ export const updateURL = (
const keyToSkip = [ const keyToSkip = [
'selected', 'selected',
'filterToFetchData', 'filterToFetchData',
'current',
'selectedTags', 'selectedTags',
'filter', 'filter',
'isFilterExclude', 'isFilterExclude',
'userSelectedFilter', 'userSelectedFilter',
'spanAggregateCurrentPage',
'spanAggregateOrder',
]; ];
search.forEach((value, key) => { search.forEach((value, key) => {
@ -51,7 +53,7 @@ export const updateURL = (
Object.fromEntries(selectedFilter), Object.fromEntries(selectedFilter),
)}&filterToFetchData=${JSON.stringify( )}&filterToFetchData=${JSON.stringify(
filterToFetchData, filterToFetchData,
)}&current=${current}&selectedTags=${JSON.stringify( )}&spanAggregateCurrentPage=${spanAggregateCurrentPage}&selectedTags=${JSON.stringify(
selectedTags, selectedTags,
)}&filter=${JSON.stringify(Object.fromEntries(filter))}&${preResult )}&filter=${JSON.stringify(Object.fromEntries(filter))}&${preResult
.map((e) => `${e.key}=${e.value}`) .map((e) => `${e.key}=${e.value}`)
@ -59,7 +61,7 @@ export const updateURL = (
Object.fromEntries(isFilterExclude), Object.fromEntries(isFilterExclude),
)}&userSelectedFilter=${JSON.stringify( )}&userSelectedFilter=${JSON.stringify(
Object.fromEntries(userSelectedFilter), Object.fromEntries(userSelectedFilter),
)}`, )}&spanAggregateCurrentPage=${spanAggregateCurrentPage}&spanAggregateOrder=${spanAggregateOrder}`,
); );
}; };

View File

@ -9,8 +9,9 @@ import {
UPDATE_SELECTED_FUNCTION, UPDATE_SELECTED_FUNCTION,
UPDATE_SELECTED_GROUP_BY, UPDATE_SELECTED_GROUP_BY,
UPDATE_SELECTED_TAGS, UPDATE_SELECTED_TAGS,
UPDATE_SPANS_AGGREEGATE, UPDATE_SPAN_ORDER,
UPDATE_TAG_MODAL_VISIBLITY, UPDATE_SPANS_AGGREGATE,
UPDATE_TAG_MODAL_VISIBILITY,
UPDATE_TRACE_FILTER, UPDATE_TRACE_FILTER,
UPDATE_TRACE_FILTER_LOADING, UPDATE_TRACE_FILTER_LOADING,
UPDATE_TRACE_GRAPH_ERROR, UPDATE_TRACE_GRAPH_ERROR,
@ -37,6 +38,7 @@ const initialValue: TraceReducer = {
error: false, error: false,
total: 0, total: 0,
pageSize: 10, pageSize: 10,
order: 'ascend',
}, },
selectedGroupBy: '', selectedGroupBy: '',
selectedFunction: 'count', selectedFunction: 'count',
@ -71,6 +73,7 @@ const traceReducer = (
selectedTags, selectedTags,
userSelected, userSelected,
isFilterExclude, isFilterExclude,
order,
} = payload; } = payload;
return { return {
@ -84,6 +87,7 @@ const traceReducer = (
spansAggregate: { spansAggregate: {
...state.spansAggregate, ...state.spansAggregate,
currentPage: current, currentPage: current,
order,
}, },
}; };
} }
@ -115,14 +119,14 @@ const traceReducer = (
}; };
} }
case UPDATE_SPANS_AGGREEGATE: { case UPDATE_SPANS_AGGREGATE: {
return { return {
...state, ...state,
spansAggregate: action.payload.spansAggregate, spansAggregate: action.payload.spansAggregate,
}; };
} }
case UPDATE_TAG_MODAL_VISIBLITY: { case UPDATE_TAG_MODAL_VISIBILITY: {
return { return {
...state, ...state,
isTagModalOpen: action.payload.isTagModalOpen, isTagModalOpen: action.payload.isTagModalOpen,
@ -199,6 +203,16 @@ const traceReducer = (
}; };
} }
case UPDATE_SPAN_ORDER: {
return {
...state,
spansAggregate: {
...state.spansAggregate,
order: action.payload.order,
},
};
}
default: default:
return state; return state;
} }

View File

@ -7,9 +7,9 @@ export const UPDATE_TRACE_FILTER_LOADING = 'UPDATE_TRACE_FILTER_LOADING';
export const SELECT_TRACE_FILTER = 'SELECT_TRACE_FILTER'; export const SELECT_TRACE_FILTER = 'SELECT_TRACE_FILTER';
export const UPDATE_ALL_FILTERS = 'UPDATE_ALL_FILTERS'; export const UPDATE_ALL_FILTERS = 'UPDATE_ALL_FILTERS';
export const UPDATE_SELECTED_TAGS = 'UPDATE_SELECTED_TAGS'; export const UPDATE_SELECTED_TAGS = 'UPDATE_SELECTED_TAGS';
export const UPDATE_TAG_MODAL_VISIBLITY = 'UPDATE_TAG_MODAL_VISIBLITY'; export const UPDATE_TAG_MODAL_VISIBILITY = 'UPDATE_TAG_MODAL_VISIBILITY';
export const UPDATE_SPANS_AGGREEGATE = 'UPDATE_SPANS_AGGREEGATE'; export const UPDATE_SPANS_AGGREGATE = 'UPDATE_SPANS_AGGREGATE';
export const UPDATE_IS_TAG_ERROR = 'UPDATE_IS_TAG_ERROR'; export const UPDATE_IS_TAG_ERROR = 'UPDATE_IS_TAG_ERROR';
@ -25,6 +25,8 @@ export const UPDATE_FILTER_RESPONSE_SELECTED =
'UPDATE_FILTER_RESPONSE_SELECTED'; 'UPDATE_FILTER_RESPONSE_SELECTED';
export const UPDATE_FILTER_EXCLUDE = 'UPDATE_FILTER_EXCLUDE'; export const UPDATE_FILTER_EXCLUDE = 'UPDATE_FILTER_EXCLUDE';
export const UPDATE_SPAN_ORDER = 'UPDATE_SPAN_ORDER';
export interface UpdateFilter { export interface UpdateFilter {
type: typeof UPDATE_TRACE_FILTER; type: typeof UPDATE_TRACE_FILTER;
payload: { payload: {
@ -33,14 +35,14 @@ export interface UpdateFilter {
} }
export interface UpdateSpansAggregate { export interface UpdateSpansAggregate {
type: typeof UPDATE_SPANS_AGGREEGATE; type: typeof UPDATE_SPANS_AGGREGATE;
payload: { payload: {
spansAggregate: TraceReducer['spansAggregate']; spansAggregate: TraceReducer['spansAggregate'];
}; };
} }
export interface UpdateTagVisiblity { export interface UpdateTagVisibility {
type: typeof UPDATE_TAG_MODAL_VISIBLITY; type: typeof UPDATE_TAG_MODAL_VISIBILITY;
payload: { payload: {
isTagModalOpen: TraceReducer['isTagModalOpen']; isTagModalOpen: TraceReducer['isTagModalOpen'];
}; };
@ -70,6 +72,7 @@ export interface UpdateAllFilters {
selectedTags: TraceReducer['selectedTags']; selectedTags: TraceReducer['selectedTags'];
userSelected: TraceReducer['userSelectedFilter']; userSelected: TraceReducer['userSelectedFilter'];
isFilterExclude: TraceReducer['isFilterExclude']; isFilterExclude: TraceReducer['isFilterExclude'];
order: TraceReducer['spansAggregate']['order'];
}; };
} }
@ -149,6 +152,13 @@ export interface UpdateSpans {
}; };
} }
export interface UpdateSpanOrder {
type: typeof UPDATE_SPAN_ORDER;
payload: {
order: TraceReducer['spansAggregate']['order'];
};
}
export type TraceActions = export type TraceActions =
| UpdateFilter | UpdateFilter
| GetTraceFilter | GetTraceFilter
@ -156,7 +166,7 @@ export type TraceActions =
| SelectTraceFilter | SelectTraceFilter
| UpdateAllFilters | UpdateAllFilters
| UpdateSelectedTags | UpdateSelectedTags
| UpdateTagVisiblity | UpdateTagVisibility
| UpdateSpansAggregate | UpdateSpansAggregate
| UpdateIsTagError | UpdateIsTagError
| UpdateSelectedGroupBy | UpdateSelectedGroupBy
@ -166,4 +176,5 @@ export type TraceActions =
| UpdateSpans | UpdateSpans
| ResetTraceFilter | ResetTraceFilter
| UpdateSelected | UpdateSelected
| UpdateFilterExclude; | UpdateFilterExclude
| UpdateSpanOrder;

View File

@ -7,7 +7,7 @@ export interface Props {
limit: number; limit: number;
offset: number; offset: number;
selectedTags: TraceReducer['selectedTags']; selectedTags: TraceReducer['selectedTags'];
order?: 'descending' | 'ascending'; order?: TraceReducer['spansAggregate']['order'];
isFilterExclude: TraceReducer['isFilterExclude']; isFilterExclude: TraceReducer['isFilterExclude'];
} }

View File

@ -4,8 +4,10 @@ export interface TraceReducer {
filter: Map<TraceFilterEnum, Record<string, string>>; filter: Map<TraceFilterEnum, Record<string, string>>;
filterToFetchData: TraceFilterEnum[]; filterToFetchData: TraceFilterEnum[];
filterLoading: boolean; filterLoading: boolean;
selectedFilter: Map<TraceFilterEnum, string[]>; selectedFilter: Map<TraceFilterEnum, string[]>;
userSelectedFilter: Map<TraceFilterEnum, string[]>; userSelectedFilter: Map<TraceFilterEnum, string[]>;
isFilterExclude: Map<TraceFilterEnum, boolean>; isFilterExclude: Map<TraceFilterEnum, boolean>;
selectedTags: Tags[]; selectedTags: Tags[];
isTagModalOpen: boolean; isTagModalOpen: boolean;
@ -18,6 +20,7 @@ export interface TraceReducer {
error: boolean; error: boolean;
total: number; total: number;
pageSize: number; pageSize: number;
order: string;
}; };
selectedGroupBy: string; selectedGroupBy: string;
selectedFunction: string; selectedFunction: string;