feat(ui): show filters top values (#2466)
* change(ui): click filter with selector options * feat(ui): show top filter values
This commit is contained in:
parent
a119e442db
commit
1dcb8e112c
8 changed files with 352 additions and 234 deletions
|
|
@ -130,6 +130,7 @@ export default class APIClient {
|
|||
async fetch(path: string, params?: any, method: string = 'GET', options: {
|
||||
clean?: boolean
|
||||
} = { clean: true }, headers?: Record<string, any>): Promise<Response> {
|
||||
let _path = path;
|
||||
let jwt = store.getState().getIn(['user', 'jwt']);
|
||||
if (!path.includes('/refresh') && jwt && this.isTokenExpired(jwt)) {
|
||||
jwt = await this.handleTokenRefresh();
|
||||
|
|
@ -175,7 +176,11 @@ export default class APIClient {
|
|||
delete init.credentials;
|
||||
}
|
||||
|
||||
return fetch(edp + path, init).then((response) => {
|
||||
if (path.includes('PROJECT_ID')) {
|
||||
_path = _path.replace('PROJECT_ID', this.siteId + '');
|
||||
}
|
||||
|
||||
return fetch(edp + _path, init).then((response) => {
|
||||
if (response.ok) {
|
||||
return response;
|
||||
} else {
|
||||
|
|
@ -229,4 +234,4 @@ export default class APIClient {
|
|||
this.init.method = 'PATCH';
|
||||
return this.fetch(path, params, 'PATCH');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,267 +3,300 @@ import { Icon } from 'UI';
|
|||
import APIClient from 'App/api_client';
|
||||
import { debounce } from 'App/utils';
|
||||
import stl from './FilterAutoComplete.module.css';
|
||||
import { components, DropdownIndicatorProps } from 'react-select';
|
||||
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';
|
||||
|
||||
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;
|
||||
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']
|
||||
},
|
||||
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';
|
||||
'&: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',
|
||||
};
|
||||
},
|
||||
return {
|
||||
...provided,
|
||||
opacity,
|
||||
transition,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
height: '20px'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
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('_')) {
|
||||
result[key] = input[key].substring(1);
|
||||
} else {
|
||||
result[key] = input[key];
|
||||
}
|
||||
const result: FilterParam = {};
|
||||
for (const key in input) {
|
||||
if (input.type === 'metadata' && typeof input[key] === 'string' && input[key].startsWith('_')) {
|
||||
result[key] = input[key].substring(1);
|
||||
} else {
|
||||
result[key] = input[key];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
showOrButton?: boolean;
|
||||
showCloseButton?: boolean;
|
||||
onRemoveValue?: () => void;
|
||||
onAddValue?: () => void;
|
||||
endpoint?: string;
|
||||
method?: string;
|
||||
params?: any;
|
||||
headerText?: string;
|
||||
placeholder?: string;
|
||||
onSelect: (e: any, item: any) => void;
|
||||
value: any;
|
||||
icon?: string;
|
||||
hideOrText?: boolean
|
||||
showOrButton?: boolean;
|
||||
showCloseButton?: boolean;
|
||||
onRemoveValue?: () => void;
|
||||
onAddValue?: () => void;
|
||||
endpoint?: string;
|
||||
method?: string;
|
||||
params?: any;
|
||||
headerText?: string;
|
||||
placeholder?: string;
|
||||
onSelect: (e: any, item: any) => void;
|
||||
value: any;
|
||||
icon?: string;
|
||||
hideOrText?: boolean;
|
||||
}
|
||||
|
||||
function FilterAutoComplete(props: Props) {
|
||||
const {
|
||||
showCloseButton = false,
|
||||
placeholder = 'Type to search',
|
||||
method = 'GET',
|
||||
showOrButton = false,
|
||||
onRemoveValue = () => null,
|
||||
onAddValue = () => null,
|
||||
endpoint = '',
|
||||
params = {},
|
||||
value = '',
|
||||
hideOrText = false,
|
||||
} = props;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [options, setOptions] = useState<any>([]);
|
||||
const [query, setQuery] = useState(value);
|
||||
const [menuIsOpen, setMenuIsOpen] = useState(false);
|
||||
const [initialFocus, setInitialFocus] = useState(false);
|
||||
let selectRef: any = null;
|
||||
let inputRef: any = null;
|
||||
const {
|
||||
showCloseButton = false,
|
||||
placeholder = 'Type to search',
|
||||
method = 'GET',
|
||||
showOrButton = false,
|
||||
onRemoveValue = () => null,
|
||||
onAddValue = () => null,
|
||||
endpoint = '',
|
||||
params = {},
|
||||
value = '',
|
||||
hideOrText = false
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
setQuery(value);
|
||||
}, [value])
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [options, setOptions] = useState<any>([]);
|
||||
const [query, setQuery] = useState(value);
|
||||
const [menuIsOpen, setMenuIsOpen] = useState(false);
|
||||
const [initialFocus, setInitialFocus] = useState(false);
|
||||
let selectRef: any = null;
|
||||
let inputRef: any = null;
|
||||
const { filterStore } = useStore();
|
||||
const _params = processKey(params);
|
||||
const [topValues, setTopValues] = useState<any>([]);
|
||||
const [topValuesLoading, setTopValuesLoading] = useState(false);
|
||||
|
||||
const loadOptions = (inputValue: string, callback: (options: []) => void) => {
|
||||
const _params = processKey(params);
|
||||
useEffect(() => {
|
||||
const fetchValues = async () => {
|
||||
setTopValuesLoading(true);
|
||||
const values = await filterStore.getTopValues(_params.type);
|
||||
setTopValues(values);
|
||||
|
||||
new APIClient()
|
||||
[method?.toLocaleLowerCase()](endpoint, { ..._params, q: inputValue })
|
||||
.then((response: any) => {
|
||||
return response.json();
|
||||
})
|
||||
.then(({ data }: any) => {
|
||||
const _options = data.map((i: any) => ({ value: i.value, label: i.value })) || [];
|
||||
setOptions(_options);
|
||||
callback(_options);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
throw new Error(e);
|
||||
})
|
||||
setTopValuesLoading(false);
|
||||
setLoading(false);
|
||||
};
|
||||
fetchValues().then(r => {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const debouncedLoadOptions = React.useCallback(debounce(loadOptions, 1000), [params]);
|
||||
useEffect(() => {
|
||||
setQuery(value);
|
||||
}, [value]);
|
||||
|
||||
const handleInputChange = (newValue: string) => {
|
||||
// const inputValue = newValue.replace(/\W/g, '');
|
||||
setLoading(true);
|
||||
setInitialFocus(true);
|
||||
setQuery(newValue);
|
||||
debouncedLoadOptions(newValue, (opt: any) => {
|
||||
selectRef?.focus();
|
||||
});
|
||||
};
|
||||
const loadOptions = (inputValue: string, callback: (options: []) => void) => {
|
||||
if (!inputValue.length) {
|
||||
setOptions(topValues.map((i: any) => ({ value: i.value, label: i.value })));
|
||||
callback(topValues.map((i: any) => ({ value: i.value, label: i.value })));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const onChange = (item: any) => {
|
||||
setMenuIsOpen(false);
|
||||
setQuery(item);
|
||||
props.onSelect(null, item);
|
||||
// inputRef?.blur();
|
||||
};
|
||||
// @ts-ignore
|
||||
new APIClient()
|
||||
[method?.toLocaleLowerCase()](endpoint, { ..._params, q: inputValue })
|
||||
.then((response: any) => {
|
||||
return response.json();
|
||||
})
|
||||
.then(({ data }: any) => {
|
||||
const _options = data.map((i: any) => ({ value: i.value, label: i.value })) || [];
|
||||
setOptions(_options);
|
||||
callback(_options);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
throw new Error(e);
|
||||
});
|
||||
};
|
||||
|
||||
const onFocus = () => {
|
||||
setMenuIsOpen(true);
|
||||
};
|
||||
const debouncedLoadOptions = React.useCallback(debounce(loadOptions, 1000), [params, topValues]);
|
||||
|
||||
const onBlur = () => {
|
||||
setMenuIsOpen(false);
|
||||
props.onSelect(null, query);
|
||||
};
|
||||
const handleInputChange = (newValue: string) => {
|
||||
setLoading(true);
|
||||
setInitialFocus(true);
|
||||
setQuery(newValue);
|
||||
debouncedLoadOptions(newValue, (opt: any) => {
|
||||
selectRef?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
const selected = value ? options.find((i: any) => i.value === query) : null;
|
||||
const onChange = (item: any) => {
|
||||
setMenuIsOpen(false);
|
||||
setQuery(item);
|
||||
props.onSelect(null, item);
|
||||
};
|
||||
|
||||
const uniqueOptions = options.filter((i: Record<string, string>) => i.value !== query)
|
||||
const selectOptionsArr = query.length ? [{ value: query, label: query }, ...uniqueOptions] : options;
|
||||
return (
|
||||
<div className="relative flex items-center">
|
||||
<div className={cn(stl.wrapper, 'relative')}>
|
||||
<input
|
||||
ref={(ref: any) => (inputRef = ref)}
|
||||
className="w-full rounded px-2 no-focus"
|
||||
value={query}
|
||||
onChange={({ target: { value } }: any) => handleInputChange(value)}
|
||||
onClick={onFocus}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
placeholder={placeholder}
|
||||
onKeyDown={(e: any) => {
|
||||
if (e.key === 'Enter') {
|
||||
inputRef?.blur();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{loading && (
|
||||
<div className="absolute top-0 right-0" style={{ marginTop: '5px', marginRight: !showCloseButton || (showCloseButton && !showOrButton) ? '34px' : '62px'}}>
|
||||
<Icon name="spinner" className="animate-spin" size="14" />
|
||||
</div>
|
||||
)}
|
||||
<Select
|
||||
ref={(ref: any) => {
|
||||
selectRef = ref;
|
||||
}}
|
||||
options={selectOptionsArr}
|
||||
value={selected}
|
||||
onChange={(e: any) => onChange(e.value)}
|
||||
menuIsOpen={initialFocus && menuIsOpen && query !== ''}
|
||||
menuPlacement="auto"
|
||||
styles={dropdownStyles}
|
||||
components={{
|
||||
Control: ({ children, ...props }: any) => <></>,
|
||||
}}
|
||||
/>
|
||||
<div className={stl.right}>
|
||||
{showCloseButton && (
|
||||
<div onClick={props.onRemoveValue}>
|
||||
<Icon name="close" size="12" />
|
||||
</div>
|
||||
)}
|
||||
{showOrButton && (
|
||||
<div onClick={props.onAddValue} className="color-teal">
|
||||
<span className="px-1">or</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
const onFocus = () => {
|
||||
setInitialFocus(true);
|
||||
if (!query.length) {
|
||||
setLoading(topValuesLoading);
|
||||
setMenuIsOpen(!topValuesLoading && topValues.length > 0);
|
||||
setOptions(topValues.map((i: any) => ({ value: i.value, label: i.value })));
|
||||
} else {
|
||||
setMenuIsOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const onBlur = () => {
|
||||
setMenuIsOpen(false);
|
||||
props.onSelect(null, query);
|
||||
};
|
||||
|
||||
const selected = value ? options.find((i: any) => i.value === query) : null;
|
||||
const uniqueOptions = options.filter((i: Record<string, string>) => i.value !== query);
|
||||
const selectOptionsArr = query.length ? [{ value: query, label: query }, ...uniqueOptions] : options;
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center">
|
||||
<div className={cn(stl.wrapper, 'relative')}>
|
||||
<input
|
||||
ref={(ref: any) => (inputRef = ref)}
|
||||
className="w-full rounded px-2 no-focus"
|
||||
value={query}
|
||||
onChange={({ target: { value } }: any) => handleInputChange(value)}
|
||||
onClick={onFocus}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
placeholder={placeholder}
|
||||
onKeyDown={(e: any) => {
|
||||
if (e.key === 'Enter') {
|
||||
inputRef?.blur();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{loading && (
|
||||
<div className="absolute top-0 right-0" style={{
|
||||
marginTop: '5px',
|
||||
marginRight: !showCloseButton || (showCloseButton && !showOrButton) ? '34px' : '62px'
|
||||
}}>
|
||||
<Icon name="spinner" className="animate-spin" size="14" />
|
||||
</div>
|
||||
)}
|
||||
<Select
|
||||
ref={(ref: any) => {
|
||||
selectRef = ref;
|
||||
}}
|
||||
options={selectOptionsArr}
|
||||
value={selected}
|
||||
onChange={(e: any) => onChange(e.value)}
|
||||
menuIsOpen={initialFocus && menuIsOpen}
|
||||
menuPlacement="auto"
|
||||
styles={dropdownStyles}
|
||||
components={{
|
||||
Control: ({ children, ...props }: any) => <></>
|
||||
}}
|
||||
/>
|
||||
<div className={stl.right}>
|
||||
{showCloseButton && (
|
||||
<div onClick={props.onRemoveValue}>
|
||||
<Icon name="close" size="12" />
|
||||
</div>
|
||||
|
||||
{!showOrButton && !hideOrText && <div className="ml-3">or</div>}
|
||||
)}
|
||||
{showOrButton && (
|
||||
<div onClick={props.onAddValue} className="color-teal">
|
||||
<span className="px-1">or</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
|
||||
{!showOrButton && !hideOrText && <div className="ml-3">or</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterAutoComplete;
|
||||
export default observer(FilterAutoComplete);
|
||||
|
|
|
|||
|
|
@ -61,6 +61,15 @@ export const stringOperatorsPerformance = options.filter(({ key }) => stringFilt
|
|||
export const targetOperators = options.filter(({ key }) => targetFilterKeys.includes(key));
|
||||
export const targetConditional = options.filter(({ key }) => ['on', 'notOn', 'startsWith', 'endsWith', 'contains'].includes(key));
|
||||
export const stringConditional = options.filter(({ key }) => ['isAny', 'is', 'isNot', 'startsWith', 'endsWith', 'contains'].includes(key));
|
||||
export const clickSelectorOperators = [
|
||||
{ key: 'selectorIs', label: 'selector is', value: 'selectorIs' },
|
||||
{ key: 'selectorIsAny', label: 'selector is any', value: 'selectorIsAny' },
|
||||
{ key: 'selectorIsNot', label: 'selector is not', value: 'selectorIsNot' },
|
||||
{ key: 'selectorContains', label: 'selector contains', value: 'selectorContains' },
|
||||
{ key: 'selectorNotContains', label: 'selector not contains', value: 'selectorNotContains' },
|
||||
{ key: 'selectorStartsWith', label: 'selector starts with', value: 'selectorStartsWith' },
|
||||
{ key: 'selectorEndsWith', label: 'selector ends with', value: 'selectorEndsWith' }
|
||||
]
|
||||
|
||||
export const booleanOperators = [
|
||||
{ key: 'true', label: 'true', value: 'true' },
|
||||
|
|
|
|||
42
frontend/app/mstore/filterStore.ts
Normal file
42
frontend/app/mstore/filterStore.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { makeAutoObservable } from 'mobx';
|
||||
import { filters } from 'Types/filter/newFilter';
|
||||
import { filterService } from 'App/services';
|
||||
|
||||
interface TopValue {
|
||||
rowCount: number;
|
||||
rowPercentage: number;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface TopValues {
|
||||
[key: string]: TopValue[];
|
||||
}
|
||||
|
||||
export default class FilterStore {
|
||||
topValues: TopValues = {};
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
|
||||
filters.forEach((filter) => {
|
||||
this.topValues[filter.key] = [];
|
||||
});
|
||||
}
|
||||
|
||||
setTopValues = (key: string, values: TopValue[]) => {
|
||||
this.topValues[key] = values.filter((value) => value !== null && value.value !== '');
|
||||
};
|
||||
|
||||
getTopValues = async (key: string) => {
|
||||
if (!this.topValues[key] || this.topValues[key].length === 0) {
|
||||
await this.fetchTopValues(key);
|
||||
}
|
||||
return Promise.resolve(this.topValues[key]);
|
||||
};
|
||||
|
||||
fetchTopValues = async (key: string) => {
|
||||
return filterService.fetchTopValues(key).then((response: TopValue[]) => {
|
||||
this.setTopValues(key, response);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ import AiSummaryStore from "./aiSummaryStore";
|
|||
import AiFiltersStore from "./aiFiltersStore";
|
||||
import SpotStore from "./spotStore";
|
||||
import LoginStore from "./loginStore";
|
||||
import FilterStore from "./filterStore";
|
||||
|
||||
export class RootStore {
|
||||
dashboardStore: DashboardStore;
|
||||
|
|
@ -47,6 +48,7 @@ export class RootStore {
|
|||
aiFiltersStore: AiFiltersStore;
|
||||
spotStore: SpotStore;
|
||||
loginStore: LoginStore;
|
||||
filterStore: FilterStore;
|
||||
|
||||
constructor() {
|
||||
this.dashboardStore = new DashboardStore();
|
||||
|
|
@ -71,6 +73,7 @@ export class RootStore {
|
|||
this.aiFiltersStore = new AiFiltersStore();
|
||||
this.spotStore = new SpotStore();
|
||||
this.loginStore = new LoginStore();
|
||||
this.filterStore = new FilterStore();
|
||||
}
|
||||
|
||||
initClient() {
|
||||
|
|
|
|||
18
frontend/app/services/FilterService.ts
Normal file
18
frontend/app/services/FilterService.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import APIClient from 'App/api_client';
|
||||
|
||||
export default class FilterService {
|
||||
private client: APIClient;
|
||||
|
||||
constructor(client?: APIClient) {
|
||||
this.client = client ? client : new APIClient();
|
||||
}
|
||||
|
||||
initClient(client?: APIClient) {
|
||||
this.client = client || new APIClient();
|
||||
}
|
||||
|
||||
fetchTopValues = async (key: string) => {
|
||||
const response = await this.client.get(`/PROJECT_ID/events/search?type=${key}`);
|
||||
return await response.json();
|
||||
};
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import UxtestingService from './UxtestingService';
|
|||
import WebhookService from './WebhookService';
|
||||
import SpotService from './spotService';
|
||||
import LoginService from "./loginService";
|
||||
import FilterService from "./FilterService";
|
||||
|
||||
export const dashboardService = new DashboardService();
|
||||
export const metricService = new MetricService();
|
||||
|
|
@ -40,6 +41,7 @@ export const tagWatchService = new TagWatchService();
|
|||
export const aiService = new AiService();
|
||||
export const spotService = new SpotService();
|
||||
export const loginService = new LoginService();
|
||||
export const filterService = new FilterService();
|
||||
|
||||
export const services = [
|
||||
dashboardService,
|
||||
|
|
@ -62,4 +64,5 @@ export const services = [
|
|||
aiService,
|
||||
spotService,
|
||||
loginService,
|
||||
filterService,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import { stringConditional, tagElementOperators, targetConditional } from 'App/constants/filterOptions';
|
||||
import {
|
||||
clickSelectorOperators,
|
||||
stringConditional,
|
||||
tagElementOperators,
|
||||
targetConditional
|
||||
} from 'App/constants/filterOptions';
|
||||
import { KEYS } from 'Types/filter/customFilter';
|
||||
import Record from 'Types/Record';
|
||||
import { FilterType, FilterKey, FilterCategory } from './filterType';
|
||||
|
|
@ -91,7 +96,7 @@ export const filters = [
|
|||
category: FilterCategory.INTERACTIONS,
|
||||
label: 'Click',
|
||||
operator: 'on',
|
||||
operatorOptions: filterOptions.targetOperators,
|
||||
operatorOptions: filterOptions.targetOperators.concat(clickSelectorOperators),
|
||||
icon: 'filters/click',
|
||||
isEvent: true
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue