diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/CommonCheckBox/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/CommonCheckBox/index.tsx index 0681e7e5d5..440c9652a7 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelBody/CommonCheckBox/index.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/CommonCheckBox/index.tsx @@ -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( + const { filter, filterDisplayValue } = useSelector( (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>(); + const [searchFilter, setSearchFilter] = useState(''); + + 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 && ( + 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) => ( ))} + + {isMoreButtonAvilable && ( + + )} ); } diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx index a2a5d163a1..81bd6e7faa 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx @@ -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 => { 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
; + } + return
{`${getMs(value?.toString())}ms`}
; + }, []); + return (
Min - Max - { - if (value === undefined) { - return
; - } - return
{`${getMs(value?.toString())}ms`}
; - }} + 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)]} />
diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/styles.ts b/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/styles.ts index d80c0e503d..1cab3f8954 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/styles.ts +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/styles.ts @@ -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; diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/util.ts b/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/util.ts new file mode 100644 index 0000000000..2a6bcf9586 --- /dev/null +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/util.ts @@ -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); diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx index b57b4fc361..bb9794d8e7 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx +++ b/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx @@ -73,11 +73,24 @@ function TagsKey(props: TagsKeysProps): JSX.Element { { - 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(''); } }} > diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx index 756bb54225..60b2d4118b 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx +++ b/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx @@ -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(selectedValues[0]); const globalReducer = useSelector( (state) => state.globalTime, @@ -34,22 +35,38 @@ function TagValue(props: TagValueProps): JSX.Element { ); return ( - ({ + 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} ))} - + ); } diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/styles.ts b/frontend/src/container/Trace/Search/AllTags/Tag/styles.ts index 347bc287f2..e604a444d7 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/styles.ts +++ b/frontend/src/container/Trace/Search/AllTags/Tag/styles.ts @@ -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%; + } +`; diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts index 39f43fdf85..3ff983b82e 100644 --- a/frontend/src/store/index.ts +++ b/frontend/src/store/index.ts @@ -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'; diff --git a/frontend/src/store/reducers/trace.ts b/frontend/src/store/reducers/trace.ts index b99ee7dd3a..3f7672cdd5 100644 --- a/frontend/src/store/reducers/trace.ts +++ b/frontend/src/store/reducers/trace.ts @@ -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([ + ['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; } diff --git a/frontend/src/types/actions/trace.ts b/frontend/src/types/actions/trace.ts index da97d05129..f043926142 100644 --- a/frontend/src/types/actions/trace.ts +++ b/frontend/src/types/actions/trace.ts @@ -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; diff --git a/frontend/src/types/reducer/trace.ts b/frontend/src/types/reducer/trace.ts index babeb344c6..fc1c08f4fc 100644 --- a/frontend/src/types/reducer/trace.ts +++ b/frontend/src/types/reducer/trace.ts @@ -32,6 +32,7 @@ export interface TraceReducer { payload: PayloadProps; }; yAxisUnit: string | undefined; + filterDisplayValue: Map; } interface SpansAggregateData {