diff --git a/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx b/frontend/app/components/Assist/AssistSearchActions/AssistSearchActions.tsx similarity index 88% rename from frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx rename to frontend/app/components/Assist/AssistSearchActions/AssistSearchActions.tsx index 974b72fb7..33f6c23e9 100644 --- a/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx +++ b/frontend/app/components/Assist/AssistSearchActions/AssistSearchActions.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Button } from 'antd'; import { useModal } from 'App/components/Modal'; -import SessionSearchField from 'Shared/SessionSearchField'; import { MODULES } from 'Components/Client/Modules'; import AssistStats from '../../AssistStats'; @@ -9,7 +8,7 @@ import Recordings from '../RecordingsList/Recordings'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; -function AssistSearchField() { +function AssistSearchActions() { const { searchStoreLive, userStore } = useStore(); const modules = userStore.account.settings?.modules ?? []; const isEnterprise = userStore.isEnterprise @@ -27,9 +26,6 @@ function AssistSearchField() { }; return (
-
- -
{isEnterprise && modules.includes(MODULES.OFFLINE_RECORDINGS) ? : null } @@ -50,4 +46,4 @@ function AssistSearchField() { ); } -export default observer(AssistSearchField); +export default observer(AssistSearchActions); diff --git a/frontend/app/components/Assist/AssistSearchActions/index.ts b/frontend/app/components/Assist/AssistSearchActions/index.ts new file mode 100644 index 000000000..64715d02b --- /dev/null +++ b/frontend/app/components/Assist/AssistSearchActions/index.ts @@ -0,0 +1 @@ +export { default } from './AssistSearchActions' \ No newline at end of file diff --git a/frontend/app/components/Assist/AssistSearchField/index.ts b/frontend/app/components/Assist/AssistSearchField/index.ts deleted file mode 100644 index 8ecf4e244..000000000 --- a/frontend/app/components/Assist/AssistSearchField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './AssistSearchField' \ No newline at end of file diff --git a/frontend/app/components/Assist/AssistView.tsx b/frontend/app/components/Assist/AssistView.tsx index 56fc3108a..37919238d 100644 --- a/frontend/app/components/Assist/AssistView.tsx +++ b/frontend/app/components/Assist/AssistView.tsx @@ -1,11 +1,12 @@ import React from 'react'; import LiveSessionList from 'Shared/LiveSessionList'; import LiveSessionSearch from 'Shared/LiveSessionSearch'; -import AssistSearchField from './AssistSearchField'; +import AssistSearchActions from './AssistSearchActions'; function AssistView() { return (
+
diff --git a/frontend/app/components/Dashboard/components/DashboardView/AiQuery.tsx b/frontend/app/components/Dashboard/components/DashboardView/AiQuery.tsx index 58f35eac5..d207fdce8 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/AiQuery.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/AiQuery.tsx @@ -5,7 +5,7 @@ import { observer } from 'mobx-react-lite'; import React from 'react'; import colors from 'tailwindcss/colors'; -import { gradientBox } from 'App/components/shared/SessionSearchField/AiSessionSearchField'; +import { gradientBox } from 'App/components/shared/SessionFilters/AiSessionSearchField'; import aiSpinner from 'App/lottie/aiSpinner.json'; import { useStore } from 'App/mstore'; import { Icon, Input } from 'UI'; diff --git a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx index 3ebaf61c7..af5ec52a3 100644 --- a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx @@ -2,181 +2,219 @@ import React, { useEffect, useState } from 'react'; import FilterList from 'Shared/Filters/FilterList'; import SeriesName from './SeriesName'; import cn from 'classnames'; -import {observer} from 'mobx-react-lite'; +import { observer } from 'mobx-react-lite'; import ExcludeFilters from './ExcludeFilters'; -import AddStepButton from "Components/Dashboard/components/FilterSeries/AddStepButton"; -import {Button, Space} from "antd"; -import {ChevronDown, ChevronUp, Trash} from "lucide-react"; +import { Button, Space } from 'antd'; +import { ChevronDown, ChevronUp, Trash } from 'lucide-react'; - -const FilterCountLabels = observer((props: { filters: any, toggleExpand: any }) => { +const FilterCountLabels = observer( + (props: { filters: any; toggleExpand: any }) => { const events = props.filters.filter((i: any) => i && i.isEvent).length; const filters = props.filters.filter((i: any) => i && !i.isEvent).length; - return
+ return ( +
- {events > 0 && ( - - )} + {events > 0 && ( + + )} - {filters > 0 && ( - - )} + {filters > 0 && ( + + )} -
; -}); +
+ ); + } +); -const FilterSeriesHeader = observer((props: { - expanded: boolean, - hidden: boolean, - seriesIndex: number, - series: any, - onRemove: (seriesIndex: any) => void, - canDelete: boolean | undefined, - toggleExpand: () => void -}) => { - - const onUpdate = (name: any) => { - props.series.update('name', name) - } - return
- - - {!props.expanded && - } - - - -
; -}) - -interface Props { +const FilterSeriesHeader = observer( + (props: { + expanded: boolean; + hidden: boolean; seriesIndex: number; series: any; - onRemoveSeries: (seriesIndex: any) => void; - canDelete?: boolean; - supportsEmpty?: boolean; - hideHeader?: boolean; - emptyMessage?: any; - observeChanges?: () => void; - excludeFilterKeys?: Array; - canExclude?: boolean; - expandable?: boolean; + onRemove: (seriesIndex: any) => void; + canDelete: boolean | undefined; + toggleExpand: () => void; + }) => { + const onUpdate = (name: any) => { + props.series.update('name', name); + }; + return ( +
+ + + {!props.expanded && ( + + )} + + + +
+ ); + } +); + +interface Props { + seriesIndex: number; + series: any; + onRemoveSeries: (seriesIndex: any) => void; + canDelete?: boolean; + supportsEmpty?: boolean; + hideHeader?: boolean; + emptyMessage?: any; + observeChanges?: () => void; + excludeFilterKeys?: Array; + canExclude?: boolean; + expandable?: boolean; } function FilterSeries(props: Props) { - const { - observeChanges = () => { - }, - canDelete, - hideHeader = false, - emptyMessage = 'Add an event or filter step to define the series.', - supportsEmpty = true, - excludeFilterKeys = [], - canExclude = false, - expandable = false - } = props; - const [expanded, setExpanded] = useState(!expandable); - const {series, seriesIndex} = props; - const [prevLength, setPrevLength] = useState(0); + const { + observeChanges = () => {}, + canDelete, + hideHeader = false, + emptyMessage = 'Add an event or filter step to define the series.', + supportsEmpty = true, + excludeFilterKeys = [], + canExclude = false, + expandable = false, + } = props; + const [expanded, setExpanded] = useState(!expandable); + const { series, seriesIndex } = props; + const [prevLength, setPrevLength] = useState(0); - useEffect(() => { - if (series.filter.filters.length === 1 && prevLength === 0 && seriesIndex === 0) { - setExpanded(true); - } - setPrevLength(series.filter.filters.length); - }, [series.filter.filters.length]); - - const onUpdateFilter = (filterIndex: any, filter: any) => { - series.filter.updateFilter(filterIndex, filter); - observeChanges(); - }; - - const onFilterMove = (newFilters: any) => { - series.filter.replaceFilters(newFilters.toArray()) - observeChanges(); + useEffect(() => { + if ( + series.filter.filters.length === 1 && + prevLength === 0 && + seriesIndex === 0 + ) { + setExpanded(true); } + setPrevLength(series.filter.filters.length); + }, [series.filter.filters.length]); - const onChangeEventsOrder = (_: any, {name, value}: any) => { - series.filter.updateKey(name, value); - observeChanges(); - }; + const onUpdateFilter = (filterIndex: any, filter: any) => { + series.filter.updateFilter(filterIndex, filter); + observeChanges(); + }; - const onRemoveFilter = (filterIndex: any) => { - series.filter.removeFilter(filterIndex); - observeChanges(); - }; + const onFilterMove = (newFilters: any) => { + series.filter.replaceFilters(newFilters.toArray()); + observeChanges(); + }; - return ( -
- {canExclude && } + const onChangeEventsOrder = (_: any, { name, value }: any) => { + series.filter.updateKey(name, value); + observeChanges(); + }; - {!hideHeader && ( -
+ ); } export default observer(FilterSeries); diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx index a95b7482e..873cb570f 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx @@ -44,17 +44,6 @@ function WidgetFormNew() { export default observer(WidgetFormNew); - -function DefineSteps({ metric, excludeFilterKeys }: any) { - return ( -
- Filter - -
- ); -} - - const FilterSection = observer(({ metric, excludeFilterKeys }: any) => { // const timeseriesOptions = metricOf.filter((i) => i.type === 'timeseries'); // const tableOptions = metricOf.filter((i) => i.type === 'table'); diff --git a/frontend/app/components/Overview/Overview.tsx b/frontend/app/components/Overview/Overview.tsx index ac5ebce64..ec7ffe562 100644 --- a/frontend/app/components/Overview/Overview.tsx +++ b/frontend/app/components/Overview/Overview.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from 'react'; import withPageTitle from 'HOCs/withPageTitle'; import NoSessionsMessage from 'Shared/NoSessionsMessage'; import MainSearchBar from 'Shared/MainSearchBar'; -import SessionSearch from 'Shared/SessionSearch'; +import SearchActions from 'Shared/SearchActions'; import SessionsTabOverview from 'Shared/SessionsTabOverview/SessionsTabOverview'; import FFlagsList from 'Components/FFlags'; import NewFFlag from 'Components/FFlags/NewFFlag'; @@ -32,15 +32,14 @@ function Overview({ match: { params } }: IProps) { React.useEffect(() => { searchStore.setActiveTab(tab); }, [tab]); - return (
+ -
diff --git a/frontend/app/components/Session_/Player/Controls/AssistSessionsModal/AssistSessionsModal.tsx b/frontend/app/components/Session_/Player/Controls/AssistSessionsModal/AssistSessionsModal.tsx index 6106326b0..54e15e521 100644 --- a/frontend/app/components/Session_/Player/Controls/AssistSessionsModal/AssistSessionsModal.tsx +++ b/frontend/app/components/Session_/Player/Controls/AssistSessionsModal/AssistSessionsModal.tsx @@ -7,7 +7,7 @@ import { KEYS } from 'Types/filter/customFilter'; import { capitalize } from 'App/utils'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; -import AssistSearchField from 'App/components/Assist/AssistSearchField'; +import AssistSearchField from 'App/components/Assist/AssistSearchActions'; import LiveSessionSearch from 'Shared/LiveSessionSearch'; import cn from 'classnames'; import Session from 'App/mstore/types/session'; @@ -70,7 +70,7 @@ function AssistSessionsModal(props: ConnectProps) { icon="arrow-repeat" /> - +
Sort By diff --git a/frontend/app/components/shared/Filters/FilterAutoComplete/AutocompleteModal.tsx b/frontend/app/components/shared/Filters/FilterAutoComplete/AutocompleteModal.tsx new file mode 100644 index 000000000..6548e296d --- /dev/null +++ b/frontend/app/components/shared/Filters/FilterAutoComplete/AutocompleteModal.tsx @@ -0,0 +1,175 @@ +import React, { useRef, useState } from 'react'; +import { Button, Checkbox, Input } from 'antd'; +import cn from 'classnames'; + +export function AutocompleteModal({ + onClose, + onApply, + values, + handleFocus, + loadOptions, + options, + isLoading, +}: { + values: string[]; + onClose: () => void; + onApply: (values: string[]) => void; + handleFocus: () => void; + loadOptions: (query: string) => void; + options: { value: string; label: string }[]; + placeholder?: string; + isLoading?: boolean; +}) { + const [query, setQuery] = React.useState(''); + const [selectedValues, setSelectedValues] = React.useState( + values.filter((i) => i.length > 0) + ); + + const handleInputChange = (value: string) => { + setQuery(value); + loadOptions(value); + }; + const onSelectOption = (item: { value: string; label: string }) => { + const selected = isSelected(item); + if (!selected) { + setSelectedValues([...selectedValues, item.value]); + } else { + setSelectedValues(selectedValues.filter((i) => i !== item.value)); + } + }; + const isSelected = (item: { value: string; label: string }) => { + return selectedValues.includes(item.value); + }; + + const applyValues = () => { + onApply(selectedValues); + }; + + const sortedOptions = React.useMemo(() => { + if (values[0] && values[0].length) { + const sorted = options.sort((a, b) => { + return values.includes(a.value) ? -1 : 1; + }); + return sorted; + } + return options; + }, [options.length]); + return ( +
+ handleInputChange(e.target.value)} + /> + +
+ {sortedOptions.map((item) => ( +
onSelectOption(item)} + className={ + 'cursor-pointer w-full py-1 hover:bg-active-blue rounded px-2' + } + > + {item.label} +
+ ))} +
+ {query.length ? ( +
+
onApply([query])} + > + Apply "{query}" +
+
+ ) : null} +
+ + +
+
+ ); +} + +interface Props { + value: string[]; + params?: any; + onApplyValues: (values: string[]) => void; + modalRenderer: (props: any) => React.ReactElement; + placeholder?: string; + modalProps?: any; + mapValues?: (value: string) => string; +} + +export function AutoCompleteContainer(props: Props) { + const filterValueContainer = useRef(null); + const [showValueModal, setShowValueModal] = useState(false); + const isEmpty = props.value.length === 0 || !props.value[0].length; + const onClose = () => setShowValueModal(false); + const onApply = (values: string[]) => { + props.onApplyValues(values); + setShowValueModal(false); + }; + return ( +
+
setShowValueModal(true)} + className={'flex items-center gap-2 cursor-pointer'} + > + {!isEmpty ? ( + <> +
+ {props.mapValues + ? props.mapValues(props.value[0]) + : props.value[0]} +
+ {props.value.length > 1 ? ( +
+ + {props.value.length - 1} More +
+ ) : null} + + ) : ( +
+ {props.placeholder ? props.placeholder : 'Select values'} +
+ )} +
+ {showValueModal ? ( + + ) : null} +
+ ); +} diff --git a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx index cb0e0a7d1..01c3d5434 100644 --- a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx +++ b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx @@ -1,117 +1,25 @@ -import React, { useState, useEffect, useCallback, useRef, ChangeEvent, KeyboardEvent } from 'react'; -import { Icon } from 'UI'; -import APIClient from 'App/api_client'; +import React, { + useState, + useEffect, + useCallback, + useRef, +} from 'react'; import { debounce } from 'App/utils'; -import stl from './FilterAutoComplete.module.css'; -import colors from 'App/theme/colors'; -import Select from 'react-select'; -import cn from 'classnames'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; -import { searchService} from 'App/services'; - -const dropdownStyles = { - option: (provided: any, state: any) => ({ - ...provided, - whiteSpace: 'nowrap', - width: '100%', - minWidth: 150, - transition: 'all 0.3s', - overflow: 'hidden', - textOverflow: 'ellipsis', - backgroundColor: state.isFocused ? colors['active-blue'] : 'transparent', - color: state.isFocused ? colors.teal : 'black', - fontSize: '14px', - '&:hover': { - transition: 'all 0.2s', - backgroundColor: colors['active-blue'] - }, - '&:focus': { - transition: 'all 0.2s', - backgroundColor: colors['active-blue'] - } - }), - control: (provided: any) => { - const obj = { - ...provided, - border: 'solid thin transparent !important', - backgroundColor: 'transparent', - cursor: 'pointer', - height: '26px', - minHeight: '26px', - borderRadius: '.5rem', - boxShadow: 'none !important' - }; - return obj; - }, - valueContainer: (provided: any) => ({ - ...provided, - // paddingRight: '0px', - width: 'fit-content', - alignItems: 'center', - height: '26px', - padding: '0 3px' - }), - indicatorsContainer: (provided: any) => ({ - ...provided, - padding: '0px', - height: '26px' - }), - menu: (provided: any, state: any) => ({ - ...provided, - top: 0, - borderRadius: '3px', - border: `1px solid ${colors['gray-light']}`, - backgroundColor: '#fff', - boxShadow: '1px 1px 1px rgba(0, 0, 0, 0.1)', - position: 'absolute', - width: 'unset', - maxWidth: '300px', - overflow: 'hidden', - zIndex: 100 - }), - menuList: (provided: any, state: any) => ({ - ...provided, - padding: 0 - }), - noOptionsMessage: (provided: any) => ({ - ...provided, - whiteSpace: 'nowrap !important' - // minWidth: 'fit-content', - }), - container: (provided: any) => ({ - ...provided, - top: '18px', - position: 'absolute' - }), - input: (provided: any) => ({ - ...provided, - height: '22px', - '& input:focus': { - border: 'none !important' - } - }), - singleValue: (provided: any, state: { isDisabled: any }) => { - const opacity = state.isDisabled ? 0.5 : 1; - const transition = 'opacity 300ms'; - - return { - ...provided, - opacity, - transition, - display: 'flex', - alignItems: 'center', - height: '20px' - }; - } -}; +import { searchService } from 'App/services'; +import { AutocompleteModal, AutoCompleteContainer } from './AutocompleteModal'; type FilterParam = { [key: string]: any }; function processKey(input: FilterParam): FilterParam { const result: FilterParam = {}; for (const key in input) { - if (input.type === 'metadata' && typeof input[key] === 'string' && input[key].startsWith('_')) { + if ( + input.type === 'metadata' && + typeof input[key] === 'string' && + input[key].startsWith('_') + ) { result[key] = input[key].substring(1); } else { result[key] = input[key]; @@ -123,190 +31,110 @@ function processKey(input: FilterParam): FilterParam { interface Props { showOrButton?: boolean; showCloseButton?: boolean; - onRemoveValue?: () => void; - onAddValue?: () => void; + onRemoveValue?: (ind: number) => void; + onAddValue?: (ind: number) => void; endpoint?: string; method?: string; params?: any; headerText?: string; placeholder?: string; - onSelect: (e: any, item: any) => void; + onSelect: (e: any, item: any, index: number) => void; value: any; icon?: string; hideOrText?: boolean; + onApplyValues: (values: string[]) => void; } -const FilterAutoComplete: React.FC = ({ - showCloseButton = false, - placeholder = 'Type to search', - method = 'GET', - showOrButton = false, - endpoint = '', - params = {}, - value = '', - hideOrText = false, - onSelect, - onRemoveValue, - onAddValue - }: Props) => { - const [loading, setLoading] = useState(false); - const [options, setOptions] = useState<{ value: string; label: string }[]>([]); - const [query, setQuery] = useState(value); - const [menuIsOpen, setMenuIsOpen] = useState(false); - const [initialFocus, setInitialFocus] = useState(false); - const [previousQuery, setPreviousQuery] = useState(value); - const selectRef = useRef(null); - const inputRef = useRef(null); - const { filterStore } = useStore(); - const _params = processKey(params); - const filterKey = `${_params.type}${_params.key || ''}`; - const topValues = filterStore.topValues[filterKey] || []; - const [topValuesLoading, setTopValuesLoading] = useState(false); +const FilterAutoComplete = observer( + ({ + params = {}, + onClose, + onApply, + values, + }: { params: any, values: string[], onClose: () => void, onApply: (values: string[]) => void }) => { + const [options, setOptions] = useState<{ value: string; label: string }[]>( + [] + ); + const [initialFocus, setInitialFocus] = useState(false); + const [loading, setLoading] = useState(false); + const { filterStore } = useStore(); + const _params = processKey(params); + const filterKey = `${_params.type}${_params.key || ''}`; + const topValues = filterStore.topValues[filterKey] || []; - const loadTopValues = () => { - setTopValuesLoading(true); - filterStore.fetchTopValues(_params.type, _params.key).finally(() => { - setTopValuesLoading(false); - setLoading(false); - }); - }; + const loadTopValues = () => { + void filterStore.fetchTopValues(_params.type, _params.key); + }; - useEffect(() => { - if (topValues.length > 0) { - const mappedValues = topValues.map((i) => ({ value: i.value, label: i.value })); - setOptions(mappedValues); - if (!query.length && initialFocus) { - setMenuIsOpen(true); + useEffect(() => { + if (topValues.length > 0) { + const mappedValues = topValues.map((i) => ({ + value: i.value, + label: i.value, + })); + setOptions(mappedValues); } - } - }, [topValues, initialFocus, query.length]); + }, [topValues, initialFocus]); - useEffect(loadTopValues, [_params.type]); + useEffect(loadTopValues, [_params.type]); - useEffect(() => { - setQuery(value); - }, [value]); + const loadOptions = async ( + inputValue: string, + ) => { + if (!inputValue.length) { + const mappedValues = topValues.map((i) => ({ + value: i.value, + label: i.value, + })); + setOptions(mappedValues); + return; + } + setLoading(true); + try { + const data = await searchService.fetchAutoCompleteValues({ + ..._params, + q: inputValue, + }); + const _options = + data.map((i: any) => ({ value: i.value, label: i.value })) || []; + setOptions(_options); + } catch (e) { + throw new Error(e); + } finally { + setLoading(false); + } + }; - const loadOptions = async (inputValue: string, callback: (options: { value: string; label: string }[]) => void) => { - if (!inputValue.length) { - const mappedValues = topValues.map((i) => ({ value: i.value, label: i.value })); - setOptions(mappedValues); - callback(mappedValues); - setLoading(false); - return; - } + const debouncedLoadOptions = useCallback(debounce(loadOptions, 500), [ + params, + topValues, + ]); - try { - // const response = await new APIClient()[method.toLowerCase()](endpoint, { ..._params, q: inputValue }); - const data = await searchService.fetchAutoCompleteValues({ ..._params, q: inputValue }) - // const data = await response.json(); - const _options = data.map((i: any) => ({ value: i.value, label: i.value })) || []; - setOptions(_options); - callback(_options); - } catch (e) { - throw new Error(e); - } finally { - setLoading(false); - } - }; + const handleInputChange = (newValue: string) => { + setInitialFocus(true); + debouncedLoadOptions(newValue); + }; - const debouncedLoadOptions = useCallback(debounce(loadOptions, 1000), [params, topValues]); - - const handleInputChange = (newValue: string) => { - setLoading(true); - setInitialFocus(true); - setQuery(newValue); - debouncedLoadOptions(newValue, () => { - selectRef.current?.focus(); - }); - }; - - const handleChange = (item: { value: string }) => { - setMenuIsOpen(false); - setQuery(item.value); - onSelect(null, item.value); - }; - - const handleFocus = () => { - setInitialFocus(true); - if (!query.length) { - setLoading(topValuesLoading); - setMenuIsOpen(!topValuesLoading && topValues.length > 0); + const handleFocus = () => { + setInitialFocus(true); setOptions(topValues.map((i) => ({ value: i.value, label: i.value }))); - } else { - setMenuIsOpen(true); - } - }; + }; - const handleBlur = () => { - setMenuIsOpen(false); - setInitialFocus(false); - if (query !== previousQuery) { - onSelect(null, query); - } - setPreviousQuery(query); - }; + console.log(options) + return + } +); - const selected = value ? options.find((i) => i.value === query) : null; - const uniqueOptions = options.filter((i) => i.value !== query); - const selectOptionsArr = query.length ? [{ value: query, label: query }, ...uniqueOptions] : options; +function AutoCompleteController(props: Props) { + return +} - return ( -
-
- ) => handleInputChange(e.target.value)} - onClick={handleFocus} - onFocus={handleFocus} - onBlur={handleBlur} - placeholder={placeholder} - onKeyDown={(e: KeyboardEvent) => { - if (e.key === 'Enter') { - inputRef.current.blur(); - } - }} - /> - {loading && ( -
- -
- )} - value ? `${ value / 1000 / 60 }` : '' const toMs = value => value !== '' ? value * 1000 * 60 : null diff --git a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx index 29a3f2bac..3781912c4 100644 --- a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx +++ b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx @@ -1,9 +1,7 @@ -import { Space } from 'antd'; -import { List } from 'immutable'; import { GripHorizontal } from 'lucide-react'; import { observer } from 'mobx-react-lite'; import React, { useEffect } from 'react'; -import { Button, Card } from 'antd'; +import { Button } from 'antd'; import { Icon } from 'UI'; import FilterItem from '../FilterItem'; @@ -44,7 +42,6 @@ function FilterList(props: Props) { const filters = filter.filters; const hasEvents = filters.filter((i: any) => i.isEvent).length > 0; - const hasFilters = filters.filter((i: any) => !i.isEvent).length > 0; let rowIndex = 0; const cannotDeleteFilter = hasEvents && !supportsEmpty; @@ -97,7 +94,7 @@ function FilterList(props: Props) { (event: Record) => { event.preventDefault(); if (draggedInd === null) return; - const newItems = filters.toArray(); + const newItems = filters; const newPosition = calculateNewPosition( draggedInd, hoveredItem.i, @@ -107,23 +104,32 @@ function FilterList(props: Props) { const reorderedItem = newItems.splice(draggedInd, 1)[0]; newItems.splice(newPosition, 0, reorderedItem); - props.onFilterMove?.(List(newItems)); + props.onFilterMove?.(newItems); setHoveredItem({ i: null, position: null }); setDraggedItem(null); }, [draggedInd, hoveredItem, filters, props.onFilterMove] ); - const eventsNum = filters.filter((i: any) => i.isEvent).size; + const eventsNum = filters.filter((i: any) => i.isEvent).length; return ( -
- {onlyFilters ? null : ( -
+
+ {onlyFilters ? null : (
+
{filter.eventsHeader || 'Events'}
+ + + - +
{!hideEventsOrder && (
{action}
)} - +
{filters.map((filter: any, filterIndex: number) => @@ -193,10 +199,9 @@ function FilterList(props: Props) { ) : null )}
-
- )} +
)} - +
Filters
@@ -232,7 +237,7 @@ function FilterList(props: Props) {
) : null )} - +
); } diff --git a/frontend/app/components/shared/Filters/FilterOperator/FilterOperator.tsx b/frontend/app/components/shared/Filters/FilterOperator/FilterOperator.tsx index 4aec1ce40..c90a57954 100644 --- a/frontend/app/components/shared/Filters/FilterOperator/FilterOperator.tsx +++ b/frontend/app/components/shared/Filters/FilterOperator/FilterOperator.tsx @@ -17,6 +17,7 @@ const dropdownStyles = { valueContainer: (provided: any) => ({ ...provided, width: 'fit-content', + height: 26, '& input': { marginTop: '-3px', }, diff --git a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx index 2e0c95db1..74bf1719c 100644 --- a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx +++ b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx @@ -29,6 +29,10 @@ function FilterValue(props: Props) { props.onUpdate({ ...filter, value: newValue }); }; + const onApplyValues = (values: string[]) => { + props.onUpdate({ ...filter, value: values }); + } + const onRemoveValue = (valueIndex: any) => { const newValue = filter.value.filter( (_: any, index: any) => index !== valueIndex @@ -37,6 +41,7 @@ function FilterValue(props: Props) { }; const onChange = (e: any, item: any, valueIndex: any) => { + console.log('item', item, valueIndex); const newValues = filter.value.map((_: any, _index: any) => { if (_index === valueIndex) { return item; @@ -86,7 +91,6 @@ function FilterValue(props: Props) { const renderValueFiled = (value: any[]) => { const showOrButton = filter.value.length > 1; - const valueIndex = 0; const BaseFilterLocalAutoComplete = (props) => ( ); + const BaseDropDown = (props) => ( + onChange(null, { value: item.value }, index)} + {...props} + /> + ) switch (filter.type) { case FilterType.NUMBER_MULTIPLE: return ; @@ -113,22 +127,13 @@ function FilterValue(props: Props) { return ; case FilterType.DROPDOWN: return ( - onChange(null, { value: item.value }, index)} - /> + ); case FilterType.ISSUE: case FilterType.MULTIPLE_DROPDOWN: return ( - onChange(null, { value: item.value }, index)} onAddValue={onAddValue} onRemoveValue={(ind) => onRemoveValue(ind)} showCloseButton={showCloseButton} @@ -151,14 +156,14 @@ function FilterValue(props: Props) { value={value} showCloseButton={showCloseButton} showOrButton={showOrButton} - onAddValue={onAddValue} - onRemoveValue={() => onRemoveValue(valueIndex)} + onApplyValues={onApplyValues} + onRemoveValue={(index) => onRemoveValue(index)} method={'GET'} endpoint="/PROJECT_ID/events/search" params={getParms(filter.key)} headerText={''} placeholder={filter.placeholder} - onSelect={(e, item) => onChange(e, item, valueIndex)} + onSelect={(e, item, index) => onChange(e, item, index)} icon={filter.icon} /> ); @@ -177,63 +182,4 @@ function FilterValue(props: Props) { ); } -// const isEmpty = filter.value.length === 0 || !filter.value[0].length; -// return ( -//
-//
setShowValueModal(true)} className={'flex items-center gap-2 '}> -// {!isEmpty ? ( -// <> -//
-// {filter.value[0]} -//
-//
-// + {filter.value.length - 1} More -//
-// -// ) : ( -//
Select values
-// )} -//
-// {showValueModal ? ( -//
-// {filter.type === FilterType.DURATION -// ? renderValueFiled(filter.value, 0) -// : filter.value && -// filter.value.map((value: any, valueIndex: any) => ( -//
-// {renderValueFiled(value, valueIndex)} -//
-// ))} -//
-// -// -//
-//
-// ) : null} -//
-// ); - export default observer(FilterValue); diff --git a/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx b/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx index 2cacad21b..1ad7a3c14 100644 --- a/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx +++ b/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx @@ -1,136 +1,41 @@ import React from 'react'; import { Icon } from 'UI'; +import { AutoCompleteContainer, AutocompleteModal } from "../FilterAutoComplete/AutocompleteModal"; import stl from './FilterValueDropdown.module.css'; -import Select from 'Shared/Select'; -const dropdownStyles = { - control: (provided: any) => { - const obj = { - ...provided, - border: 'solid thin transparent !important', - backgroundColor: 'transparent', - cursor: 'pointer', - height: '26px', - minHeight: '26px', - borderRadius: '.5rem', - boxShadow: 'none !important', - }; - return obj; - }, - valueContainer: (provided: any) => ({ - ...provided, - // paddingRight: '0px', - width: 'fit-content', - alignItems: 'center', - height: '26px', - padding: '0 3px', - }), - // placeholder: (provided: any) => ({ - // ...provided, - // }), - indicatorsContainer: (provided: any) => ({ - ...provided, - padding: '0px', - height: '26px', - }), - option: (provided: any, state: any) => ({ - ...provided, - whiteSpace: 'nowrap', - }), - menu: (provided: any, state: any) => ({ - ...provided, - top: 20, - left: 0, - minWidth: 'fit-content', - overflow: 'hidden', - }), - container: (provided: any) => ({ - ...provided, - width: '100%', - }), - input: (provided: any) => ({ - ...provided, - height: '22px', - '& input:focus': { - border: 'none !important', - }, - }), - singleValue: (provided: any, state: { isDisabled: any }) => { - const opacity = state.isDisabled ? 0.5 : 1; - const transition = 'opacity 300ms'; - - return { - ...provided, - opacity, - transition, - display: 'flex', - alignItems: 'center', - height: '20px', - }; - }, -}; interface Props { - placeholder?: string; - value: string; - onChange: (value: any, ind: number) => void; - className?: string; options: any[]; - search?: boolean; - showCloseButton?: boolean; - showOrButton?: boolean; - onRemoveValue?: (ind: number) => void; - onAddValue?: (ind: number) => void; - isMultiple?: boolean; - index: number; + onApply: (values: string[]) => void; + onClose: () => void; + values: string[]; } function FilterValueDropdown(props: Props) { const { - placeholder = 'Select', - isMultiple = true, - search = false, options, - onChange, - value, - showCloseButton = true, - showOrButton = true, - index, + onApply, + onClose, + values, } = props; + const [query, setQuery] = React.useState(''); + const filteredOptions = query.length ? options.filter((option) => { + return option.label.toLowerCase().includes(query.toLowerCase()); + }) : options return ( -
-
- setShowModal(true)} - onBlur={() => setTimeout(setShowModal, 200, false)} - onChange={onSearchChange} - placeholder={'Search sessions using any captured event (click, input, page, error...)'} - style={{ minWidth: 360 }} - id="search" - type="search" - autoComplete="off" - className="hover:border-gray-medium text-lg placeholder-lg h-9 shadow-sm" - /> - - {showModal && ( -
- -
- )} -
- ); -} - -export default observer(SessionSearchField); diff --git a/frontend/app/components/shared/SessionSearchField/index.ts b/frontend/app/components/shared/SessionSearchField/index.ts deleted file mode 100644 index 1f99e0c0b..000000000 --- a/frontend/app/components/shared/SessionSearchField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SessionSearchField'; \ No newline at end of file diff --git a/frontend/app/styles/general.css b/frontend/app/styles/general.css index 05d022b1b..e95217db4 100644 --- a/frontend/app/styles/general.css +++ b/frontend/app/styles/general.css @@ -436,3 +436,9 @@ p { display: flex; align-items: center; } +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; /* Remove default styling */ + appearance: none; + margin: 0; /* Fix margin if necessary */ +}