feat(ui) - assist filters
This commit is contained in:
parent
0584883a1b
commit
74944ed778
18 changed files with 317 additions and 49 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { fetchList } from 'Duck/sessions';
|
||||
import { fetchLiveList } from 'Duck/sessions';
|
||||
import { connect } from 'react-redux';
|
||||
import { NoContent, Loader } from 'UI';
|
||||
import { List, Map } from 'immutable';
|
||||
|
|
@ -7,26 +7,56 @@ import SessionItem from 'Shared/SessionItem';
|
|||
import withPermissions from 'HOCs/withPermissions'
|
||||
import { KEYS } from 'Types/filter/customFilter';
|
||||
import { applyFilter, addAttribute } from 'Duck/filters';
|
||||
import Filter from 'Types/filter';
|
||||
import { FilterCategory, FilterKey } from 'App/types/filter/filterType';
|
||||
import { addFilterByKeyAndValue } from 'Duck/liveSearch';
|
||||
|
||||
const AUTOREFRESH_INTERVAL = .5 * 60 * 1000
|
||||
|
||||
interface Props {
|
||||
loading: Boolean,
|
||||
list?: List<any>,
|
||||
fetchList: (params) => void,
|
||||
list: List<any>,
|
||||
fetchLiveList: () => Promise<void>,
|
||||
applyFilter: () => void,
|
||||
filters: Filter
|
||||
filters: any,
|
||||
addAttribute: (obj) => void,
|
||||
addFilterByKeyAndValue: (key: FilterKey, value: string) => void,
|
||||
}
|
||||
|
||||
function LiveSessionList(props: Props) {
|
||||
const { loading, list, filters } = props;
|
||||
const { loading, filters, list } = props;
|
||||
var timeoutId;
|
||||
const hasUserFilter = filters && filters.filters.map(i => i.key).includes(KEYS.USERID);
|
||||
const hasUserFilter = filters.map(i => i.key).includes(KEYS.USERID);
|
||||
const [sessions, setSessions] = React.useState(list);
|
||||
|
||||
useEffect(() => {
|
||||
if (filters.size === 0) {
|
||||
props.addFilterByKeyAndValue(FilterKey.USERID, '');
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const filteredSessions = filters.size > 0 ? props.list.filter(session => {
|
||||
let hasValidFilter = true;
|
||||
filters.forEach(filter => {
|
||||
if (!hasValidFilter) return;
|
||||
|
||||
const _values = filter.value.filter(i => i !== '' && i !== null && i !== undefined).map(i => i.toLowerCase());
|
||||
if (filter.key === FilterKey.USERID) {
|
||||
const _userId = session.userId ? session.userId.toLowerCase() : '';
|
||||
hasValidFilter = _values.length > 0 ? (_values.includes(_userId) && hasValidFilter) || _values.some(i => _userId.includes(i)) : hasValidFilter;
|
||||
}
|
||||
if (filter.category === FilterCategory.METADATA) {
|
||||
const _source = session.metadata[filter.key] ? session.metadata[filter.key].toLowerCase() : '';
|
||||
hasValidFilter = _values.length > 0 ? (_values.includes(_source) && hasValidFilter) || _values.some(i => _source.includes(i)) : hasValidFilter;
|
||||
}
|
||||
})
|
||||
return hasValidFilter;
|
||||
}) : props.list;
|
||||
setSessions(filteredSessions);
|
||||
}, [filters, list]);
|
||||
|
||||
useEffect(() => {
|
||||
props.fetchList(filters.toJS());
|
||||
props.fetchLiveList();
|
||||
timeout();
|
||||
return () => {
|
||||
clearTimeout(timeoutId)
|
||||
|
|
@ -35,17 +65,15 @@ function LiveSessionList(props: Props) {
|
|||
|
||||
const onUserClick = (userId, userAnonymousId) => {
|
||||
if (userId) {
|
||||
props.addAttribute({ label: 'User Id', key: KEYS.USERID, type: KEYS.USERID, operator: 'is', value: userId })
|
||||
props.addFilterByKeyAndValue(FilterKey.USERID, userId);
|
||||
} else {
|
||||
props.addAttribute({ label: 'Anonymous ID', key: 'USERANONYMOUSID', type: "USERANONYMOUSID", operator: 'is', value: userAnonymousId })
|
||||
props.addFilterByKeyAndValue(FilterKey.USERANONYMOUSID, userAnonymousId);
|
||||
}
|
||||
|
||||
props.applyFilter()
|
||||
}
|
||||
|
||||
const timeout = () => {
|
||||
timeoutId = setTimeout(() => {
|
||||
props.fetchList(filters.toJS());
|
||||
props.fetchLiveList();
|
||||
timeout();
|
||||
}, AUTOREFRESH_INTERVAL);
|
||||
}
|
||||
|
|
@ -59,11 +87,12 @@ function LiveSessionList(props: Props) {
|
|||
See how to <a target="_blank" className="link" href="https://docs.openreplay.com/plugins/assist">{'enable Assist'}</a> if you haven't yet done so.
|
||||
</span>
|
||||
}
|
||||
image={<img src="/img/live-sessions.png" style={{ width: '70%', marginBottom: '30px' }}/>}
|
||||
show={ !loading && list && list.size === 0}
|
||||
image={<img src="/img/live-sessions.png"
|
||||
style={{ width: '70%', marginBottom: '30px' }}/>}
|
||||
show={ !loading && sessions && sessions.size === 0}
|
||||
>
|
||||
<Loader loading={ loading }>
|
||||
{list && list.map(session => (
|
||||
{sessions && sessions.map(session => (
|
||||
<SessionItem
|
||||
key={ session.sessionId }
|
||||
session={ session }
|
||||
|
|
@ -82,8 +111,7 @@ export default withPermissions(['ASSIST_LIVE', 'SESSION_REPLAY'])(connect(
|
|||
(state) => ({
|
||||
list: state.getIn(['sessions', 'liveSessions']),
|
||||
loading: state.getIn([ 'sessions', 'loading' ]),
|
||||
filters: state.getIn([ 'filters', 'appliedFilter' ]),
|
||||
filters: state.getIn([ 'liveSearch', 'instance', 'filters' ]),
|
||||
}),
|
||||
{
|
||||
fetchList, applyFilter, addAttribute }
|
||||
{ fetchLiveList, applyFilter, addAttribute, addFilterByKeyAndValue }
|
||||
)(LiveSessionList));
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ function FilterSeries(props: Props) {
|
|||
}
|
||||
|
||||
const onChangeEventsOrder = (e, { name, value }) => {
|
||||
|
||||
props.editSeriesFilter(seriesIndex, { eventsOrder: value });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
.wrapper {
|
||||
border: solid thin $gray-light !important;
|
||||
border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
& input {
|
||||
height: 24px;
|
||||
font-size: 13px !important;
|
||||
padding: 0 5px !important;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
border: solid thin transparent !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
& .right {
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
padding: 0;
|
||||
background-color: $gray-lightest;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
|
||||
& div {
|
||||
/* background-color: red; */
|
||||
border-left: solid thin $gray-light !important;
|
||||
width: 28px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&:last-child {
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: $gray-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
border-radius: 0 0 3px 3px;
|
||||
border: solid thin $gray-light !important;
|
||||
box-shadow: 0 2px 2px 0 $gray-light;
|
||||
/* padding: 20px; */
|
||||
background-color: white;
|
||||
max-height: 350px;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
left: 0;
|
||||
width: 500px;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.filterItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
/* transition: all 0.4s; */
|
||||
margin-bottom: 5px;
|
||||
max-width: 100%;
|
||||
& .label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $gray-lightest;
|
||||
/* transition: all 0.2s; */
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Icon, Loader } from 'UI';
|
||||
import { debounce } from 'App/utils';
|
||||
import stl from './FilterAutoCompleteLocal.css';
|
||||
import cn from 'classnames';
|
||||
|
||||
interface Props {
|
||||
showOrButton?: boolean;
|
||||
showCloseButton?: boolean;
|
||||
onRemoveValue?: () => void;
|
||||
onAddValue?: () => void;
|
||||
placeholder?: string;
|
||||
onSelect: (e, item) => void;
|
||||
value: any;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
function FilterAutoCompleteLocal(props: Props) {
|
||||
const {
|
||||
showCloseButton = false,
|
||||
placeholder = 'Type to search',
|
||||
showOrButton = false,
|
||||
onRemoveValue = () => null,
|
||||
onAddValue = () => null,
|
||||
value = '',
|
||||
icon = null,
|
||||
} = props;
|
||||
const [showModal, setShowModal] = useState(true)
|
||||
const [query, setQuery] = useState(value);
|
||||
|
||||
const onInputChange = ({ target: { value } }) => {
|
||||
setQuery(value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setQuery(value);
|
||||
}, [value])
|
||||
|
||||
const onBlur = (e) => {
|
||||
setTimeout(() => { setShowModal(false) }, 200)
|
||||
props.onSelect(e, { value: query })
|
||||
}
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
props.onSelect(e, { value: query })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center">
|
||||
<div className={stl.wrapper}>
|
||||
<input
|
||||
name="query"
|
||||
onChange={ onInputChange }
|
||||
onBlur={ onBlur }
|
||||
onFocus={ () => setShowModal(true)}
|
||||
value={ query }
|
||||
autoFocus={ true }
|
||||
type="text"
|
||||
placeholder={ placeholder }
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
<div
|
||||
className={stl.right}
|
||||
>
|
||||
{ showCloseButton && <div onClick={onRemoveValue}><Icon name="close" size="12" /></div> }
|
||||
{ showOrButton && <div onClick={onAddValue} className="color-teal"><span className="px-1">or</span></div> }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ !showOrButton && <div className="ml-3">or</div> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterAutoCompleteLocal;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './FilterAutoCompleteLocal';
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState } from 'react';
|
||||
import FilterAutoComplete from '../FilterAutoComplete';
|
||||
import FilterAutoCompleteLocal from '../FilterAutoCompleteLocal';
|
||||
import { FilterKey, FilterCategory, FilterType } from 'Types/filter/filterType';
|
||||
import FilterValueDropdown from '../FilterValueDropdown';
|
||||
import FilterDuration from '../FilterDuration';
|
||||
|
|
@ -63,6 +64,18 @@ function FilterValue(props: Props) {
|
|||
const renderValueFiled = (value, valueIndex) => {
|
||||
const showOrButton = valueIndex === lastIndex;
|
||||
switch(filter.type) {
|
||||
case FilterType.STRING:
|
||||
return (
|
||||
<FilterAutoCompleteLocal
|
||||
value={value}
|
||||
showCloseButton={showCloseButton}
|
||||
showOrButton={showOrButton}
|
||||
onAddValue={onAddValue}
|
||||
onRemoveValue={() => onRemoveValue(valueIndex)}
|
||||
onSelect={(e, item) => onChange(e, item, valueIndex)}
|
||||
icon={filter.icon}
|
||||
/>
|
||||
)
|
||||
case FilterType.DROPDOWN:
|
||||
return (
|
||||
<FilterValueDropdown
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ import { Icon, Loader } from 'UI';
|
|||
import { connect } from 'react-redux';
|
||||
import cn from 'classnames';
|
||||
import stl from './LiveFilterModal.css';
|
||||
import { filtersMap, getMetaDataFilter } from 'Types/filter/newFilter';
|
||||
import { FilterCategory, FilterKey } from 'App/types/filter/filterType';
|
||||
import { filtersMap } from 'Types/filter/newFilter';
|
||||
|
||||
interface Props {
|
||||
filters: any,
|
||||
|
|
@ -69,11 +68,11 @@ function LiveFilterModal(props: Props) {
|
|||
|
||||
{ !hasSearchQuery && (
|
||||
<div className="">
|
||||
{filters && Object.keys(filters).filter(i => i === 'User' || i === 'Metadata').map((key) => (
|
||||
{filters && Object.keys(filters).map((key) => (
|
||||
<div className="mb-6" key={key}>
|
||||
<div className="uppercase font-medium mb-1 color-gray-medium tracking-widest text-sm">{key}</div>
|
||||
<div>
|
||||
{filters[key].filter((i: any) => i.key === FilterKey.USERID || i.key === FilterKey.USERANONYMOUSID || i.category === FilterCategory.METADATA).map((filter: any) => (
|
||||
{filters[key].map((filter: any) => (
|
||||
<div key={filter.label} className={cn(stl.optionItem, "flex items-center py-2 cursor-pointer -mx-2 px-2")} onClick={() => onFilterClick(filter)}>
|
||||
<Icon name={filter.icon} size="16"/>
|
||||
<span className="ml-2">{filter.label}</span>
|
||||
|
|
@ -89,7 +88,7 @@ function LiveFilterModal(props: Props) {
|
|||
}
|
||||
|
||||
export default connect(state => ({
|
||||
filters: state.getIn([ 'search', 'filterList' ]),
|
||||
filters: state.getIn([ 'search', 'filterListLive' ]),
|
||||
filterSearchList: state.getIn([ 'search', 'filterSearchList' ]),
|
||||
metaOptions: state.getIn([ 'customFields', 'list' ]),
|
||||
fetchingFilterSearchList: state.getIn([ 'search', 'fetchFilterSearch', 'loading' ]),
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ const LiveSearchBar = (props: Props) => {
|
|||
const hasFilters = appliedFilter && appliedFilter.filters && appliedFilter.filters.size > 0;
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<div style={{ width: "80%", marginRight: "10px"}}>
|
||||
<div style={{ width: "60%", marginRight: "10px"}}>
|
||||
<LiveSessionSearchField />
|
||||
</div>
|
||||
<div className="flex items-center" style={{ width: "20%"}}>
|
||||
<div className="flex items-center" style={{ width: "40%"}}>
|
||||
<Popup
|
||||
trigger={
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import React from 'react';
|
|||
import FilterList from 'Shared/Filters/FilterList';
|
||||
import { connect } from 'react-redux';
|
||||
import { edit, addFilter } from 'Duck/liveSearch';
|
||||
import LiveFilterModal from 'Shared/Filters/LiveFilterModal';
|
||||
import FilterSelection from 'Shared/Filters/FilterSelection';
|
||||
import { IconButton } from 'UI';
|
||||
|
||||
interface Props {
|
||||
appliedFilter: any;
|
||||
|
|
@ -14,6 +15,10 @@ function LiveSessionSearch(props: Props) {
|
|||
const hasEvents = appliedFilter.filters.filter(i => i.isEvent).size > 0;
|
||||
const hasFilters = appliedFilter.filters.filter(i => !i.isEvent).size > 0;
|
||||
|
||||
const onAddFilter = (filter) => {
|
||||
props.addFilter(filter);
|
||||
}
|
||||
|
||||
const onUpdateFilter = (filterIndex, filter) => {
|
||||
const newFilters = appliedFilter.filters.map((_filter, i) => {
|
||||
if (i === filterIndex) {
|
||||
|
|
@ -55,6 +60,17 @@ function LiveSessionSearch(props: Props) {
|
|||
onChangeEventsOrder={onChangeEventsOrder}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="border-t px-5 py-1 flex items-center -mx-2">
|
||||
<div>
|
||||
<FilterSelection
|
||||
filter={undefined}
|
||||
onFilterClick={onAddFilter}
|
||||
>
|
||||
<IconButton primaryText label="ADD FILTER" icon="plus" />
|
||||
</FilterSelection>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : <></>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ function LiveSessionSearchField(props: Props) {
|
|||
}
|
||||
|
||||
const onAddFilter = (filter) => {
|
||||
console.log('onAddFilter', filter)
|
||||
props.addFilterByKeyAndValue(filter.key, filter.value)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ function SavedSearch(props) {
|
|||
className="flex items-center"
|
||||
onClick={() => setShowMenu(true)}
|
||||
>
|
||||
<span className="mr-2">{`Search Saved (${list.size})`}</span>
|
||||
<span className="mr-2">{`Saved Search (${list.size})`}</span>
|
||||
<Icon name="ellipsis-v" color="teal" size="14" />
|
||||
</Button>
|
||||
{ savedSearch.exists() && (
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import Filter from 'Types/filter';
|
|||
import SavedFilter from 'Types/filter/savedFilter';
|
||||
import { fetchList as fetchSessionList } from './sessions';
|
||||
import { filtersMap } from 'Types/filter/newFilter';
|
||||
import { filterMap, checkFilterValue } from './search';
|
||||
import { filterMap, checkFilterValue, hasFilterApplied } from './search';
|
||||
|
||||
const name = "liveSearch";
|
||||
const idKey = "searchId";
|
||||
|
|
@ -73,8 +73,16 @@ export const clearSearch = () => (dispatch, getState) => {
|
|||
export const addFilter = (filter) => (dispatch, getState) => {
|
||||
filter.value = checkFilterValue(filter.value);
|
||||
const instance = getState().getIn([ 'liveSearch', 'instance']);
|
||||
const filters = instance.filters.push(filter);
|
||||
return dispatch(edit(instance.set('filters', filters)));
|
||||
|
||||
if (hasFilterApplied(instance.filters, filter)) {
|
||||
// const index = instance.filters.findIndex(f => f.key === filter.key);
|
||||
// const oldFilter = instance.filters.get(index);
|
||||
// oldFilter.value = oldFilter.value.concat(filter.value);
|
||||
// return dispatch(edit(instance.setIn(['filters', index], oldFilter)));
|
||||
} else {
|
||||
const filters = instance.filters.push(filter);
|
||||
return dispatch(edit(instance.set('filters', filters)));
|
||||
}
|
||||
}
|
||||
|
||||
export const addFilterByKeyAndValue = (key, value) => (dispatch, getState) => {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { errors as errorsRoute, isRoute } from "App/routes";
|
|||
import { fetchList as fetchSessionList } from './sessions';
|
||||
import { fetchList as fetchErrorsList } from './errors';
|
||||
import { FilterCategory, FilterKey } from '../types/filter/filterType';
|
||||
import { filtersMap, generateFilterOptions } from 'Types/filter/newFilter';
|
||||
import { filtersMap, generateFilterOptions, generateLiveFilterOptions } from 'Types/filter/newFilter';
|
||||
|
||||
const ERRORS_ROUTE = errorsRoute();
|
||||
|
||||
|
|
@ -43,6 +43,7 @@ const updateInstance = (state, instance) => state.getIn([ "savedSearch", savedSe
|
|||
|
||||
const initialState = Map({
|
||||
filterList: generateFilterOptions(filtersMap),
|
||||
filterListLive: generateLiveFilterOptions(filtersMap),
|
||||
list: List(),
|
||||
alertMetricId: null,
|
||||
instance: new Filter({ filters: [] }),
|
||||
|
|
@ -54,7 +55,8 @@ const initialState = Map({
|
|||
function reducer(state = initialState, action = {}) {
|
||||
switch (action.type) {
|
||||
case REFRESH_FILTER_OPTIONS:
|
||||
return state.set('filterList', generateFilterOptions(filtersMap));
|
||||
return state.set('filterList', generateFilterOptions(filtersMap))
|
||||
.set('filterListLive', generateLiveFilterOptions(filtersMap));
|
||||
case EDIT:
|
||||
return state.mergeIn(['instance'], action.instance);
|
||||
case APPLY:
|
||||
|
|
@ -212,11 +214,20 @@ export const clearSearch = () => (dispatch, getState) => {
|
|||
});
|
||||
}
|
||||
|
||||
export const hasFilterApplied = (filters, filter) => {
|
||||
return !filter.isEvent && filters.some(f => f.key === filter.key);
|
||||
}
|
||||
|
||||
export const addFilter = (filter) => (dispatch, getState) => {
|
||||
filter.value = checkFilterValue(filter.value);
|
||||
const instance = getState().getIn([ 'search', 'instance']);
|
||||
const filters = instance.filters.push(filter);
|
||||
return dispatch(edit(instance.set('filters', filters)));
|
||||
|
||||
if (hasFilterApplied(instance.filters, filter)) {
|
||||
|
||||
} else {
|
||||
const filters = instance.filters.push(filter);
|
||||
return dispatch(edit(instance.set('filters', filters)));
|
||||
}
|
||||
}
|
||||
|
||||
export const addFilterByKeyAndValue = (key, value) => (dispatch, getState) => {
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@ const reducer = (state = initialState, action = {}) => {
|
|||
case FETCH_ERROR_STACK.SUCCESS:
|
||||
return state.set('errorStack', List(action.data.trace).map(ErrorStack)).set('sourcemapUploaded', action.data.sourcemapUploaded)
|
||||
case FETCH_LIVE_LIST.SUCCESS:
|
||||
// const { sessions, total } = action.data;
|
||||
const liveList = List(action.data).map(s => new Session({...s, live: true}));
|
||||
return state
|
||||
.set('liveSessions', liveList)
|
||||
|
|
@ -284,6 +283,13 @@ export const fetchList = (params = {}, clear = false, live = false) => (dispatch
|
|||
})
|
||||
}
|
||||
|
||||
// export const fetchLiveList = (id) => (dispatch, getState) => {
|
||||
// return dispatch({
|
||||
// types: FETCH_LIVE_LIST.toArray(),
|
||||
// call: client => client.get('/assist/sessions'),
|
||||
// })
|
||||
// }
|
||||
|
||||
export function fetchErrorStackList(sessionId, errorId) {
|
||||
return {
|
||||
types: FETCH_ERROR_STACK.toArray(),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export enum FilterCategory {
|
|||
};
|
||||
|
||||
export enum FilterType {
|
||||
STRING = "STRING",
|
||||
ISSUE = "ISSUE",
|
||||
BOOLEAN = "BOOLEAN",
|
||||
NUMBER = "NUMBER",
|
||||
|
|
@ -17,6 +18,7 @@ export enum FilterType {
|
|||
COUNTRY = "COUNTRY",
|
||||
DROPDOWN = "DROPDOWN",
|
||||
MULTIPLE_DROPDOWN = "MULTIPLE_DROPDOWN",
|
||||
AUTOCOMPLETE_LOCAL = "AUTOCOMPLETE_LOCAL",
|
||||
};
|
||||
|
||||
export enum FilterKey {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export const filtersMap = {
|
|||
[FilterKey.DURATION]: { key: FilterKey.DURATION, type: FilterType.DURATION, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'Duration', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is']), icon: 'filters/duration' },
|
||||
[FilterKey.USER_COUNTRY]: { key: FilterKey.USER_COUNTRY, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'User Country', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/country', options: countryOptions },
|
||||
// [FilterKey.CONSOLE]: { key: FilterKey.CONSOLE, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Console', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/console' },
|
||||
[FilterKey.USERID]: { key: FilterKey.USERID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User Id', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' },
|
||||
[FilterKey.USERID]: { key: FilterKey.USERID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User Id', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid', isLive: true },
|
||||
[FilterKey.USERANONYMOUSID]: { key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User AnonymousId', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' },
|
||||
|
||||
// PERFORMANCE
|
||||
|
|
@ -56,6 +56,15 @@ export const filtersMap = {
|
|||
[FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/click', options: ISSUE_OPTIONS },
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new filter to the filter list
|
||||
* @param {*} category
|
||||
* @param {*} key
|
||||
* @param {*} type
|
||||
* @param {*} operator
|
||||
* @param {*} operatorOptions
|
||||
* @param {*} icon
|
||||
*/
|
||||
export const addElementToFiltersMap = (
|
||||
category = FilterCategory.METADATA,
|
||||
key,
|
||||
|
|
@ -64,14 +73,7 @@ export const addElementToFiltersMap = (
|
|||
operatorOptions = filterOptions.stringOperators,
|
||||
icon = 'filters/metadata'
|
||||
) => {
|
||||
console.log('addElementToFiltersMap', category, key, type, operator, operatorOptions, icon)
|
||||
filtersMap[key] = { key, type, category, label: capitalize(key), operator: operator, operatorOptions, icon }
|
||||
}
|
||||
|
||||
|
||||
export const getMetaDataFilter = (key) => {
|
||||
const METADATA_FILTER = { key: key, type: FilterType.MULTIPLE, category: FilterCategory.METADATA, label: capitalize(key), operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/metadata' }
|
||||
return METADATA_FILTER;
|
||||
filtersMap[key] = { key, type, category, label: capitalize(key), operator: operator, operatorOptions, icon, isLive: true }
|
||||
}
|
||||
|
||||
export default Record({
|
||||
|
|
@ -113,11 +115,16 @@ export default Record({
|
|||
..._filter,
|
||||
key: _filter.key,
|
||||
type: _filter.type, // camelCased(filter.type.toLowerCase()),
|
||||
value: value.length === 0 ? [""] : value,
|
||||
value: value.length === 0 ? [""] : value, // make sure there an empty value
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Group filters by category
|
||||
* @param {*} filtersMap
|
||||
* @returns
|
||||
*/
|
||||
export const generateFilterOptions = (filtersMap) => {
|
||||
const _options = {};
|
||||
Object.keys(filtersMap).forEach(key => {
|
||||
|
|
@ -129,4 +136,24 @@ export const generateFilterOptions = (filtersMap) => {
|
|||
}
|
||||
});
|
||||
return _options;
|
||||
}
|
||||
|
||||
export const generateLiveFilterOptions = (filtersMap) => {
|
||||
const _options = {};
|
||||
Object.keys(filtersMap).filter(i => filtersMap[i].isLive).forEach(key => {
|
||||
const filter = filtersMap[key];
|
||||
filter.operator = 'contains';
|
||||
filter.type = FilterType.STRING;
|
||||
// filter.type = FilterType.AUTOCOMPLETE_LOCAL;
|
||||
// filter.options = countryOptions;
|
||||
filter.operatorOptions = [
|
||||
{ key: 'contains', text: 'contains', value: 'contains' },
|
||||
]
|
||||
if (_options.hasOwnProperty(filter.category)) {
|
||||
_options[filter.category].push(filter);
|
||||
} else {
|
||||
_options[filter.category] = [filter];
|
||||
}
|
||||
});
|
||||
return _options;
|
||||
}
|
||||
|
|
@ -142,6 +142,7 @@ export default Record({
|
|||
firstResourceTime,
|
||||
issues: issuesList,
|
||||
sessionId: sessionId || sessionID,
|
||||
userId: session.userId || session.userID,
|
||||
};
|
||||
},
|
||||
idKey: "sessionId",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { promises as fs } from 'fs';
|
||||
import replaceInFiles from 'replace-in-files';
|
||||
import packageConfig from '../package.json';
|
||||
import packageConfig from '../package.json' assert { type: 'json' };
|
||||
|
||||
async function main() {
|
||||
const webworker = await fs.readFile('build/webworker.js', 'utf8');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue