Merge branch 'dev-assist-filters' into dev
This commit is contained in:
commit
6ba773fe6d
35 changed files with 440 additions and 647 deletions
|
|
@ -4,12 +4,16 @@ import LiveSessionSearch from 'Shared/LiveSessionSearch';
|
|||
import cn from 'classnames'
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
import withPermissions from 'HOCs/withPermissions'
|
||||
// import SessionSearch from '../shared/SessionSearch';
|
||||
// import MainSearchBar from '../shared/MainSearchBar';
|
||||
import AssistSearchField from './AssistSearchField';
|
||||
|
||||
function Assist() {
|
||||
return (
|
||||
<div className="page-margin container-90 flex relative">
|
||||
<div className="flex-1 flex">
|
||||
<div className={cn("w-full mx-auto")} style={{ maxWidth: '1300px'}}>
|
||||
<AssistSearchField />
|
||||
<LiveSessionSearch />
|
||||
<div className="my-4" />
|
||||
<LiveSessionList />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react';
|
||||
import { Button } from 'UI';
|
||||
import SessionSearchField from 'Shared/SessionSearchField';
|
||||
// import { fetchFilterSearch } from 'Duck/search';
|
||||
import { connect } from 'react-redux';
|
||||
import { edit as editFilter, addFilterByKeyAndValue, clearSearch, fetchFilterSearch } from 'Duck/liveSearch';
|
||||
// import { clearSearch } from 'Duck/search';
|
||||
|
||||
function AssistSearchField(props: any) {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<div style={{ width: "60%", marginRight: "10px"}}>
|
||||
<SessionSearchField
|
||||
fetchFilterSearch={props.fetchFilterSearch}
|
||||
addFilterByKeyAndValue={props.addFilterByKeyAndValue}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="text-primary"
|
||||
className="ml-auto font-medium"
|
||||
// disabled={!hasFilters}
|
||||
onClick={() => props.clearSearch()}
|
||||
>
|
||||
Clear Search
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(null, {
|
||||
fetchFilterSearch, editFilter, addFilterByKeyAndValue, clearSearch
|
||||
})(AssistSearchField);
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './AssistSearchField'
|
||||
|
|
@ -10,6 +10,7 @@ import stl from './sortDropdown.module.css';
|
|||
export default class SortDropdown extends React.PureComponent {
|
||||
state = { value: null }
|
||||
sort = ({ value }) => {
|
||||
value = value.value
|
||||
this.setState({ value: value })
|
||||
const [ sort, order ] = value.split('-');
|
||||
const sign = order === 'desc' ? -1 : 1;
|
||||
|
|
@ -25,7 +26,6 @@ export default class SortDropdown extends React.PureComponent {
|
|||
<Select
|
||||
name="sortSessions"
|
||||
plain
|
||||
// className={ stl.dropdown }
|
||||
right
|
||||
options={ options }
|
||||
onChange={ this.sort }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react'
|
||||
import { connect } from 'react-redux';
|
||||
import cn from 'classnames';
|
||||
import { SideMenuitem, SavedSearchList, Popup } from 'UI'
|
||||
import { SideMenuitem, Popup } from 'UI'
|
||||
import stl from './sessionMenu.module.css';
|
||||
import { clearEvents } from 'Duck/filters';
|
||||
import { issues_types } from 'Types/session/issue'
|
||||
|
|
@ -10,7 +10,7 @@ import { useModal } from 'App/components/Modal';
|
|||
import SessionSettings from 'Shared/SessionSettings/SessionSettings'
|
||||
|
||||
function SessionsMenu(props) {
|
||||
const { activeTab, keyMap, wdTypeCount, toggleRehydratePanel, isEnterprise } = props;
|
||||
const { activeTab, isEnterprise } = props;
|
||||
const { showModal } = useModal();
|
||||
|
||||
const onMenuItemClick = (filter) => {
|
||||
|
|
@ -45,34 +45,25 @@ function SessionsMenu(props) {
|
|||
{ issues_types.filter(item => item.visible).map(item => (
|
||||
<SideMenuitem
|
||||
key={item.key}
|
||||
// disabled={!keyMap[item.type] && !wdTypeCount[item.type]}
|
||||
active={activeTab.type === item.type}
|
||||
title={item.name} iconName={item.icon}
|
||||
onClick={() => onMenuItemClick(item)}
|
||||
/>
|
||||
))}
|
||||
|
||||
<div className={stl.divider} />
|
||||
<div className="my-3">
|
||||
<SideMenuitem
|
||||
title={ isEnterprise ? "Vault" : "Bookmarks" }
|
||||
iconName={ isEnterprise ? "safe" : "star" }
|
||||
active={activeTab.type === 'bookmark'}
|
||||
onClick={() => onMenuItemClick({ name: isEnterprise ? 'Vault' : 'Bookmarks', type: 'bookmark', description: isEnterprise ? 'Sessions saved to vault never get\'s deleted from records.' : '' })}
|
||||
// TODO show the description in header
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={cn(stl.divider, 'mb-4')} />
|
||||
<SavedSearchList />
|
||||
<div className={cn(stl.divider, 'my-4')} />
|
||||
<SideMenuitem
|
||||
title={ isEnterprise ? "Vault" : "Bookmarks" }
|
||||
iconName={ isEnterprise ? "safe" : "star" }
|
||||
active={activeTab.type === 'bookmark'}
|
||||
onClick={() => onMenuItemClick({ name: isEnterprise ? 'Vault' : 'Bookmarks', type: 'bookmark', description: isEnterprise ? 'Sessions saved to vault never get\'s deleted from records.' : '' })}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
activeTab: state.getIn([ 'search', 'activeTab' ]),
|
||||
keyMap: state.getIn([ 'sessions', 'keyMap' ]),
|
||||
wdTypeCount: state.getIn([ 'sessions', 'wdTypeCount' ]),
|
||||
captureRate: state.getIn(['watchdogs', 'captureRate']),
|
||||
filters: state.getIn([ 'filters', 'appliedFilter' ]),
|
||||
sessionsLoading: state.getIn([ 'sessions', 'fetchLiveListRequest', 'loading' ]),
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class CustomFields extends React.Component {
|
|||
}
|
||||
|
||||
onChangeSelect = ({ value }) => {
|
||||
const site = this.props.sites.find(s => s.id === value);
|
||||
const site = this.props.sites.find(s => s.id === value.value);
|
||||
this.setState({ currentSite: site })
|
||||
this.props.fetchList(site.id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
assist,
|
||||
client,
|
||||
errors,
|
||||
funnels,
|
||||
// funnels,
|
||||
dashboard,
|
||||
withSiteId,
|
||||
CLIENT_DEFAULT_TAB,
|
||||
|
|
@ -21,18 +21,17 @@ import OnboardingExplore from './OnboardingExplore/OnboardingExplore'
|
|||
import Announcements from '../Announcements';
|
||||
import Notifications from '../Alerts/Notifications';
|
||||
import { init as initSite, fetchList as fetchSiteList } from 'Duck/site';
|
||||
import Logo from '../../svg/logo-small.svg';
|
||||
|
||||
import ErrorGenPanel from 'App/dev/components';
|
||||
import ErrorsBadge from 'Shared/ErrorsBadge';
|
||||
import Alerts from '../Alerts/Alerts';
|
||||
import AnimatedSVG, { ICONS } from '../shared/AnimatedSVG/AnimatedSVG';
|
||||
import { fetchList as fetchMetadata } from 'Duck/customField';
|
||||
|
||||
const DASHBOARD_PATH = dashboard();
|
||||
const SESSIONS_PATH = sessions();
|
||||
const ASSIST_PATH = assist();
|
||||
const ERRORS_PATH = errors();
|
||||
const FUNNELS_PATH = funnels();
|
||||
// const FUNNELS_PATH = funnels();
|
||||
const CLIENT_PATH = client(CLIENT_DEFAULT_TAB);
|
||||
const AUTOREFRESH_INTERVAL = 30 * 1000;
|
||||
|
||||
|
|
@ -52,33 +51,15 @@ const Header = (props) => {
|
|||
useEffect(() => {
|
||||
activeSite = sites.find(s => s.id == siteId);
|
||||
props.initSite(activeSite);
|
||||
props.fetchMetadata();
|
||||
}, [sites])
|
||||
|
||||
const showTrackingModal = (
|
||||
isRoute(SESSIONS_PATH, location.pathname) ||
|
||||
isRoute(ERRORS_PATH, location.pathname)
|
||||
) && activeSite && !activeSite.recorded;
|
||||
|
||||
useEffect(() => {
|
||||
if(showTrackingModal) {
|
||||
interval = setInterval(() => {
|
||||
fetchSiteList()
|
||||
}, AUTOREFRESH_INTERVAL);
|
||||
} else if (interval){
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, [showTrackingModal])
|
||||
|
||||
return (
|
||||
<div className={ cn(styles.header, showTrackingModal ? styles.placeOnTop : '') }>
|
||||
<div className={ cn(styles.header) }>
|
||||
<NavLink to={ withSiteId(SESSIONS_PATH, siteId) }>
|
||||
<div className="relative">
|
||||
{/* <img src={ Logo } alt="React Logo" /> */}
|
||||
{/* <object style={{ width: '30px' }} type="image/svg+xml" data={ Logo } /> */}
|
||||
<div className="p-2">
|
||||
<AnimatedSVG name={ICONS.LOGO_SMALL} size="30" />
|
||||
{/* <object style={{ width: '30px' }} type="image/svg+xml" data={ Logo } /> */}
|
||||
{/* <Logo width={30} /> */}
|
||||
</div>
|
||||
<div className="absolute bottom-0" style={{ fontSize: '7px', right: '5px' }}>v{window.env.VERSION}</div>
|
||||
</div>
|
||||
|
|
@ -100,7 +81,7 @@ const Header = (props) => {
|
|||
>
|
||||
{ 'Assist' }
|
||||
</NavLink>
|
||||
<NavLink
|
||||
{/* <NavLink
|
||||
to={ withSiteId(ERRORS_PATH, siteId) }
|
||||
className={ styles.nav }
|
||||
activeClassName={ styles.active }
|
||||
|
|
@ -113,7 +94,7 @@ const Header = (props) => {
|
|||
activeClassName={ styles.active }
|
||||
>
|
||||
{ 'Funnels' }
|
||||
</NavLink>
|
||||
</NavLink> */}
|
||||
<NavLink
|
||||
to={ withSiteId(DASHBOARD_PATH, siteId) }
|
||||
className={ styles.nav }
|
||||
|
|
@ -164,5 +145,5 @@ export default withRouter(connect(
|
|||
showAlerts: state.getIn([ 'dashboard', 'showAlerts' ]),
|
||||
boardingCompletion: state.getIn([ 'dashboard', 'boardingCompletion' ])
|
||||
}),
|
||||
{ onLogoutClick: logout, initSite, fetchSiteList },
|
||||
{ onLogoutClick: logout, initSite, fetchSiteList, fetchMetadata },
|
||||
)(Header));
|
||||
|
|
|
|||
|
|
@ -14,15 +14,15 @@ const COUNTRY = 'country';
|
|||
const LOCATION = 'location';
|
||||
|
||||
const platformOptions = [
|
||||
{ 'key': PLATFORM, text: 'Desktop', value: 1},
|
||||
{ 'key': PLATFORM, text: 'Tablet', value: 2 },
|
||||
{ 'key': PLATFORM, text: 'Tablet Landscape', value: 3 },
|
||||
{ 'key': PLATFORM, text: 'Mobile', value: 4 },
|
||||
{ 'key': PLATFORM, text: 'Mobile Landscape', value: 5 }
|
||||
{ 'key': PLATFORM, label: 'Desktop', value: 1},
|
||||
{ 'key': PLATFORM, label: 'Tablet', value: 2 },
|
||||
{ 'key': PLATFORM, label: 'Tablet Landscape', value: 3 },
|
||||
{ 'key': PLATFORM, label: 'Mobile', value: 4 },
|
||||
{ 'key': PLATFORM, label: 'Mobile Landscape', value: 5 }
|
||||
];
|
||||
|
||||
const countryOptions = Object.keys(countries).map(c => ({key: COUNTRY, text: countries[c], value: c}));
|
||||
const locationOptions = Object.keys(regionLabels).map(k => ({ key: LOCATION, text: regionLabels[k], value: k}));
|
||||
const countryOptions = Object.keys(countries).map(c => ({key: COUNTRY, label: countries[c], value: c}));
|
||||
const locationOptions = Object.keys(regionLabels).map(k => ({ key: LOCATION, label: regionLabels[k], value: k}));
|
||||
|
||||
const _filterKeys = [
|
||||
{ key: 'userId', name: 'User ID', icon: 'user-alt', placeholder: 'Search for User ID' },
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ interface Props {
|
|||
params?: any;
|
||||
headerText?: string;
|
||||
placeholder?: string;
|
||||
onSelect: (e, item) => void;
|
||||
onSelect: (e: any, item: any) => void;
|
||||
value: any;
|
||||
icon?: string;
|
||||
}
|
||||
|
|
@ -44,17 +44,17 @@ function FilterAutoComplete(props: Props) {
|
|||
const [options, setOptions] = useState<any>([]);
|
||||
const [query, setQuery] = useState(value);
|
||||
|
||||
const requestValues = (q) => {
|
||||
const requestValues = (q: any) => {
|
||||
setLoading(true);
|
||||
|
||||
return new APIClient()[method?.toLocaleLowerCase()](endpoint, { ...params, q })
|
||||
.then(response => {
|
||||
.then((response: any) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
throw new Error(response.statusText);
|
||||
})
|
||||
.then(({ data }) => {
|
||||
.then(({ data }: any) => {
|
||||
setOptions(data);
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
|
|
@ -62,7 +62,7 @@ function FilterAutoComplete(props: Props) {
|
|||
|
||||
const debouncedRequestValues = React.useCallback(debounce(requestValues, 1000), [params]);
|
||||
|
||||
const onInputChange = ({ target: { value } }) => {
|
||||
const onInputChange = ({ target: { value } }: any) => {
|
||||
setQuery(value);
|
||||
if (!showModal) {
|
||||
setShowModal(true);
|
||||
|
|
@ -85,7 +85,7 @@ function FilterAutoComplete(props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
const onItemClick = (e, item) => {
|
||||
const onItemClick = (e: any, item: any) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
|
|
@ -132,7 +132,7 @@ function FilterAutoComplete(props: Props) {
|
|||
) : (
|
||||
<div>
|
||||
{
|
||||
options.map((item, i) => (
|
||||
options.map((item: any, i: any) => (
|
||||
<div
|
||||
key={item.value + '_' + i}
|
||||
className={ cn(stl.filterItem) }
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ function FilterModal(props: Props) {
|
|||
} = props;
|
||||
const hasSearchQuery = searchQuery && searchQuery.length > 0;
|
||||
const showSearchList = isMainSearch && searchQuery.length > 0;
|
||||
console.log('filters', props.filters)
|
||||
|
||||
const onFilterSearchClick = (filter) => {
|
||||
const _filter = filtersMap[filter.type];
|
||||
|
|
@ -123,7 +124,7 @@ function FilterModal(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' ]),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import FilterModal from '../FilterModal';
|
||||
import LiveFilterModal from '../LiveFilterModal';
|
||||
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
|
||||
import { Icon } from 'UI';
|
||||
import { connect } from 'react-redux';
|
||||
|
|
@ -10,6 +9,8 @@ const ASSIST_ROUTE = assistRoute();
|
|||
|
||||
interface Props {
|
||||
filter?: any; // event/filter
|
||||
filterList: any;
|
||||
filterListLive: any;
|
||||
onFilterClick: (filter) => void;
|
||||
children?: any;
|
||||
isLive?: boolean;
|
||||
|
|
@ -43,13 +44,15 @@ function FilterSelection(props: Props) {
|
|||
</OutsideClickDetectingDiv>
|
||||
{showModal && (
|
||||
<div className="absolute left-0 border shadow rounded bg-white z-50">
|
||||
{ isRoute(ASSIST_ROUTE, window.location.pathname) ? <LiveFilterModal onFilterClick={onFilterClick} /> : <FilterModal onFilterClick={onFilterClick} /> }
|
||||
<FilterModal onFilterClick={onFilterClick} filters={isRoute(ASSIST_ROUTE, window.location.pathname) ? props.filterListLive : props.filterList } />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
export default connect((state: any) => ({
|
||||
filters: state.getIn([ 'search', 'filterList' ]),
|
||||
liveFilters: state.getIn([ 'search', 'filterListLive' ]),
|
||||
isLive: state.getIn([ 'sessions', 'activeTab' ]).type === 'live',
|
||||
}), { })(FilterSelection);
|
||||
|
|
@ -5,10 +5,13 @@ import { FilterKey, FilterCategory, FilterType } from 'Types/filter/filterType';
|
|||
import FilterValueDropdown from '../FilterValueDropdown';
|
||||
import FilterDuration from '../FilterDuration';
|
||||
import { debounce } from 'App/utils';
|
||||
import { assist as assistRoute, isRoute } from "App/routes";
|
||||
|
||||
const ASSIST_ROUTE = assistRoute();
|
||||
|
||||
interface Props {
|
||||
filter: any;
|
||||
onUpdate: (filter) => void;
|
||||
onUpdate: (filter: any) => void;
|
||||
}
|
||||
function FilterValue(props: Props) {
|
||||
const { filter } = props;
|
||||
|
|
@ -21,13 +24,13 @@ function FilterValue(props: Props) {
|
|||
props.onUpdate({ ...filter, value: newValue });
|
||||
}
|
||||
|
||||
const onRemoveValue = (valueIndex) => {
|
||||
const newValue = filter.value.filter((_, index) => index !== valueIndex);
|
||||
const onRemoveValue = (valueIndex: any) => {
|
||||
const newValue = filter.value.filter((_: any, index: any) => index !== valueIndex);
|
||||
props.onUpdate({ ...filter, value: newValue });
|
||||
}
|
||||
|
||||
const onChange = (e, item, valueIndex) => {
|
||||
const newValues = filter.value.map((_, _index) => {
|
||||
const onChange = (e: any, item: any, valueIndex: any) => {
|
||||
const newValues = filter.value.map((_: any, _index: any) => {
|
||||
if (_index === valueIndex) {
|
||||
return item.value;
|
||||
}
|
||||
|
|
@ -38,11 +41,11 @@ function FilterValue(props: Props) {
|
|||
|
||||
const debounceOnSelect = React.useCallback(debounce(onChange, 500), [onChange]);
|
||||
|
||||
const onDurationChange = (newValues) => {
|
||||
const onDurationChange = (newValues: any) => {
|
||||
setDurationValues({ ...durationValues, ...newValues });
|
||||
}
|
||||
|
||||
const handleBlur = (e) => {
|
||||
const handleBlur = (e: any) => {
|
||||
if (filter.type === FilterType.DURATION) {
|
||||
const { maxDuration, minDuration, key } = filter;
|
||||
if (maxDuration || minDuration) return;
|
||||
|
|
@ -53,16 +56,22 @@ function FilterValue(props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
const getParms = (key) => {
|
||||
const getParms = (key: any) => {
|
||||
let params = {};
|
||||
switch (filter.category) {
|
||||
case FilterCategory.METADATA:
|
||||
return { type: FilterKey.METADATA, key: key };
|
||||
params = { type: FilterKey.METADATA, key: key };
|
||||
default:
|
||||
return { type: filter.key };
|
||||
params = { type: filter.key };
|
||||
}
|
||||
|
||||
if (isRoute(ASSIST_ROUTE, window.location.pathname)) {
|
||||
params = { ...params, live: true };
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
const renderValueFiled = (value, valueIndex) => {
|
||||
const renderValueFiled = (value: any, valueIndex: any) => {
|
||||
const showOrButton = valueIndex === lastIndex && filter.type !== FilterType.NUMBER;
|
||||
switch(filter.type) {
|
||||
case FilterType.STRING:
|
||||
|
|
@ -85,7 +94,7 @@ function FilterValue(props: Props) {
|
|||
filter={filter}
|
||||
options={filter.options}
|
||||
onChange={({ value }) => onChange(null, { value }, valueIndex)}
|
||||
/>
|
||||
/>
|
||||
)
|
||||
case FilterType.ISSUE:
|
||||
case FilterType.MULTIPLE_DROPDOWN:
|
||||
|
|
@ -174,7 +183,7 @@ function FilterValue(props: Props) {
|
|||
{ filter.type === FilterType.DURATION ? (
|
||||
renderValueFiled(filter.value, 0)
|
||||
) : (
|
||||
filter.value && filter.value.map((value, valueIndex) => (
|
||||
filter.value && filter.value.map((value: any, valueIndex: any) => (
|
||||
<div key={valueIndex}>
|
||||
{renderValueFiled(value, valueIndex)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { fetchLiveList } from 'Duck/sessions';
|
||||
import { connect } from 'react-redux';
|
||||
import { NoContent, Loader, LoadMoreButton, Pagination } from 'UI';
|
||||
import { List, Map } from 'immutable';
|
||||
import { NoContent, Loader, Pagination } from 'UI';
|
||||
import { List } from 'immutable';
|
||||
import SessionItem from 'Shared/SessionItem';
|
||||
import withPermissions from 'HOCs/withPermissions'
|
||||
import { KEYS } from 'Types/filter/customFilter';
|
||||
import { applyFilter, addAttribute } from 'Duck/filters';
|
||||
import { FilterCategory, FilterKey } from 'App/types/filter/filterType';
|
||||
import { FilterKey } from 'App/types/filter/filterType';
|
||||
import { addFilterByKeyAndValue, updateCurrentPage, updateSort } from 'Duck/liveSearch';
|
||||
import Select from 'Shared/Select';
|
||||
import SortOrderButton from 'Shared/SortOrderButton';
|
||||
import { capitalize, sliceListPerPage } from 'App/utils';
|
||||
import { capitalize } from 'App/utils';
|
||||
import LiveSessionReloadButton from 'Shared/LiveSessionReloadButton';
|
||||
|
||||
const AUTOREFRESH_INTERVAL = .5 * 60 * 1000
|
||||
|
|
@ -23,7 +23,7 @@ interface Props {
|
|||
fetchLiveList: () => Promise<void>,
|
||||
applyFilter: () => void,
|
||||
filters: any,
|
||||
addAttribute: (obj) => void,
|
||||
addAttribute: (obj: any) => void,
|
||||
addFilterByKeyAndValue: (key: FilterKey, value: string) => void,
|
||||
updateCurrentPage: (page: number) => void,
|
||||
currentPage: number,
|
||||
|
|
@ -34,16 +34,13 @@ interface Props {
|
|||
|
||||
function LiveSessionList(props: Props) {
|
||||
const { loading, filters, list, currentPage, metaList = [], sort } = props;
|
||||
var timeoutId;
|
||||
const hasUserFilter = filters.map(i => i.key).includes(KEYS.USERID);
|
||||
var timeoutId: any;
|
||||
const hasUserFilter = filters.map((i: any) => i.key).includes(KEYS.USERID);
|
||||
const [sessions, setSessions] = React.useState(list);
|
||||
const sortOptions = metaList.map(i => ({
|
||||
const sortOptions = metaList.map((i: any) => ({
|
||||
text: capitalize(i), label: i
|
||||
})).toJS();
|
||||
|
||||
// const displayedCount = Math.min(currentPage * PER_PAGE, sessions.size);
|
||||
// const addPage = () => props.updateCurrentPage(props.currentPage + 1)
|
||||
|
||||
// useEffect(() => {
|
||||
// if (filters.size === 0) {
|
||||
// props.addFilterByKeyAndValue(FilterKey.USERID, '');
|
||||
|
|
@ -53,31 +50,31 @@ function LiveSessionList(props: Props) {
|
|||
useEffect(() => {
|
||||
if (metaList.size === 0 || !!sort.field) return;
|
||||
|
||||
if ( sortOptions[0]) {
|
||||
if (sortOptions[0]) {
|
||||
props.updateSort({ field: sortOptions[0].value });
|
||||
}
|
||||
}, [metaList]);
|
||||
|
||||
useEffect(() => {
|
||||
const filteredSessions = filters.size > 0 ? props.list.filter(session => {
|
||||
let hasValidFilter = true;
|
||||
filters.forEach(filter => {
|
||||
if (!hasValidFilter) return;
|
||||
// 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]);
|
||||
// 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.fetchLiveList();
|
||||
|
|
@ -95,7 +92,8 @@ function LiveSessionList(props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
const onSortChange = (e, { value }) => {
|
||||
const onSortChange = ({ value }: any) => {
|
||||
value = value.value
|
||||
props.updateSort({ field: value });
|
||||
}
|
||||
|
||||
|
|
@ -145,9 +143,7 @@ function LiveSessionList(props: Props) {
|
|||
show={ !loading && sessions && sessions.size === 0}
|
||||
>
|
||||
<Loader loading={ loading }>
|
||||
{sessions && sliceListPerPage(sessions.sortBy(i => i.metadata[sort.field]).update(list => {
|
||||
return sort.order === 'desc' ? list.reverse() : list;
|
||||
}), currentPage - 1).map(session => (
|
||||
{sessions.map(session => (
|
||||
<SessionItem
|
||||
key={ session.sessionId }
|
||||
session={ session }
|
||||
|
|
@ -162,7 +158,7 @@ function LiveSessionList(props: Props) {
|
|||
<Pagination
|
||||
page={currentPage}
|
||||
totalPages={Math.ceil(sessions.size / PER_PAGE)}
|
||||
onPageChange={(page) => props.updateCurrentPage(page)}
|
||||
onPageChange={(page: any) => props.updateCurrentPage(page)}
|
||||
limit={PER_PAGE}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -173,12 +169,12 @@ function LiveSessionList(props: Props) {
|
|||
}
|
||||
|
||||
export default withPermissions(['ASSIST_LIVE'])(connect(
|
||||
(state) => ({
|
||||
list: state.getIn(['sessions', 'liveSessions']),
|
||||
loading: state.getIn([ 'sessions', 'loading' ]),
|
||||
(state: any) => ({
|
||||
list: state.getIn(['liveSearch', 'list']),
|
||||
loading: state.getIn([ 'liveSearch', 'fetchList', 'loading' ]),
|
||||
filters: state.getIn([ 'liveSearch', 'instance', 'filters' ]),
|
||||
currentPage: state.getIn(["liveSearch", "currentPage"]),
|
||||
metaList: state.getIn(['customFields', 'list']).map(i => i.key),
|
||||
metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key),
|
||||
sort: state.getIn(['liveSearch', 'sort']),
|
||||
}),
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import React from 'react'
|
||||
import ReloadButton from '../ReloadButton'
|
||||
import { connect } from 'react-redux'
|
||||
import { fetchLiveList } from 'Duck/sessions'
|
||||
import { fetchSessions } from 'Duck/liveSearch'
|
||||
|
||||
interface Props {
|
||||
loading: boolean
|
||||
fetchLiveList: typeof fetchLiveList
|
||||
fetchSessions: typeof fetchSessions
|
||||
}
|
||||
function LiveSessionReloadButton(props: Props) {
|
||||
const { loading } = props
|
||||
return (
|
||||
<ReloadButton loading={loading} onClick={() => props.fetchLiveList()} className="cursor-pointer" />
|
||||
<ReloadButton loading={loading} onClick={() => props.fetchSessions()} className="cursor-pointer" />
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
loading: state.getIn([ 'sessions', 'fetchLiveListRequest', 'loading' ]),
|
||||
}), { fetchLiveList })(LiveSessionReloadButton)
|
||||
}), { fetchSessions })(LiveSessionReloadButton)
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
import React from 'react';
|
||||
import FilterList from 'Shared/Filters/FilterList';
|
||||
import { connect } from 'react-redux';
|
||||
import { edit, addFilter, addFilterByKeyAndValue } from 'Duck/liveSearch';
|
||||
import FilterSelection from 'Shared/Filters/FilterSelection';
|
||||
import { IconButton } from 'UI';
|
||||
import SaveFilterButton from 'Shared/SaveFilterButton';
|
||||
import { connect } from 'react-redux';
|
||||
import { Button } from 'UI';
|
||||
import { edit, addFilter } from 'Duck/liveSearch';
|
||||
import SaveFunnelButton from '../SaveFunnelButton';
|
||||
|
||||
interface Props {
|
||||
list: any,
|
||||
appliedFilter: any;
|
||||
edit: typeof edit;
|
||||
addFilter: typeof addFilter;
|
||||
addFilterByKeyAndValue: typeof addFilterByKeyAndValue;
|
||||
saveRequestPayloads: boolean;
|
||||
}
|
||||
function LiveSessionSearch(props: Props) {
|
||||
const { appliedFilter } = props;
|
||||
const { appliedFilter, saveRequestPayloads = false } = props;
|
||||
const hasEvents = appliedFilter.filters.filter(i => i.isEvent).size > 0;
|
||||
const hasFilters = appliedFilter.filters.filter(i => !i.isEvent).size > 0;
|
||||
|
||||
|
|
@ -41,10 +42,9 @@ function LiveSessionSearch(props: Props) {
|
|||
return i !== filterIndex;
|
||||
});
|
||||
|
||||
props.edit({ filters: newFilters, });
|
||||
// if (newFilters.size === 0) {
|
||||
// props.addFilterByKeyAndValue(FilterKey.USERID, '');
|
||||
// }
|
||||
props.edit({
|
||||
filters: newFilters,
|
||||
});
|
||||
}
|
||||
|
||||
const onChangeEventsOrder = (e, { name, value }) => {
|
||||
|
|
@ -53,18 +53,17 @@ function LiveSessionSearch(props: Props) {
|
|||
});
|
||||
}
|
||||
|
||||
return props.list.size > 0 ? (
|
||||
return (hasEvents || hasFilters) ? (
|
||||
<div className="border bg-white rounded mt-4">
|
||||
{ hasEvents || hasFilters && (
|
||||
<div className="p-5">
|
||||
<FilterList
|
||||
filter={appliedFilter}
|
||||
onUpdateFilter={onUpdateFilter}
|
||||
onRemoveFilter={onRemoveFilter}
|
||||
onChangeEventsOrder={onChangeEventsOrder}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-5">
|
||||
<FilterList
|
||||
filter={appliedFilter}
|
||||
onUpdateFilter={onUpdateFilter}
|
||||
onRemoveFilter={onRemoveFilter}
|
||||
onChangeEventsOrder={onChangeEventsOrder}
|
||||
saveRequestPayloads={saveRequestPayloads}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="border-t px-5 py-1 flex items-center -mx-2">
|
||||
<div>
|
||||
|
|
@ -72,15 +71,26 @@ function LiveSessionSearch(props: Props) {
|
|||
filter={undefined}
|
||||
onFilterClick={onAddFilter}
|
||||
>
|
||||
<IconButton primaryText label="ADD FILTER" icon="plus" />
|
||||
{/* <IconButton primaryText label="ADD STEP" icon="plus" /> */}
|
||||
<Button
|
||||
variant="text-primary"
|
||||
className="mr-2"
|
||||
// onClick={() => setshowModal(true)}
|
||||
icon="plus">
|
||||
ADD STEP
|
||||
</Button>
|
||||
</FilterSelection>
|
||||
</div>
|
||||
<div className="ml-auto flex items-center">
|
||||
{/* <SaveFunnelButton /> */}
|
||||
{/* <SaveFilterButton /> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : <></>;
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
saveRequestPayloads: state.getIn(['site', 'active', 'saveRequestPayloads']),
|
||||
appliedFilter: state.getIn([ 'liveSearch', 'instance' ]),
|
||||
list: state.getIn(['sessions', 'liveSessions']),
|
||||
}), { edit, addFilter, addFilterByKeyAndValue })(LiveSessionSearch);
|
||||
}), { edit, addFilter })(LiveSessionSearch);
|
||||
|
|
@ -2,20 +2,31 @@ import React from 'react';
|
|||
import SessionSearchField from 'Shared/SessionSearchField';
|
||||
import SavedSearch from 'Shared/SavedSearch';
|
||||
import { Button } from 'UI';
|
||||
import { clearSearch } from 'Duck/search';
|
||||
// import { clearSearch } from 'Duck/search';
|
||||
import { connect } from 'react-redux';
|
||||
import { edit as editFilter, addFilterByKeyAndValue, clearSearch, fetchFilterSearch } from 'Duck/search';
|
||||
|
||||
interface Props {
|
||||
clearSearch: () => void;
|
||||
appliedFilter: any;
|
||||
optionsReady: boolean;
|
||||
editFilter: any,
|
||||
addFilterByKeyAndValue: any,
|
||||
fetchFilterSearch: any,
|
||||
}
|
||||
const MainSearchBar = (props: Props) => {
|
||||
const { appliedFilter, optionsReady } = props;
|
||||
const { appliedFilter } = props;
|
||||
const hasFilters = appliedFilter && appliedFilter.filters && appliedFilter.filters.size > 0;
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<div style={{ width: "60%", marginRight: "10px"}}><SessionSearchField /></div>
|
||||
<div style={{ width: "60%", marginRight: "10px"}}>
|
||||
<SessionSearchField
|
||||
editFilter={props.editFilter}
|
||||
addFilterByKeyAndValue={props.addFilterByKeyAndValue}
|
||||
clearSearch={props.clearSearch}
|
||||
fetchFilterSearch={props.fetchFilterSearch}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center" style={{ width: "40%"}}>
|
||||
<SavedSearch />
|
||||
<Button
|
||||
|
|
@ -33,4 +44,9 @@ const MainSearchBar = (props: Props) => {
|
|||
export default connect(state => ({
|
||||
appliedFilter: state.getIn(['search', 'instance']),
|
||||
optionsReady: state.getIn(['customFields', 'optionsReady'])
|
||||
}), { clearSearch })(MainSearchBar);
|
||||
}), {
|
||||
clearSearch,
|
||||
editFilter,
|
||||
addFilterByKeyAndValue,
|
||||
fetchFilterSearch
|
||||
})(MainSearchBar);
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ function SessionItem(props: RouteComponentProps<Props>) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ width: "20%", height: "38px" }} className="px-2 flex flex-col justify-between">
|
||||
<div style={{ width: "20%" }} className="px-2 flex flex-col justify-between">
|
||||
<div>{formatTimeOrDate(startedAt, timezone) }</div>
|
||||
<div className="flex items-center color-gray-medium">
|
||||
{!isAssist && (
|
||||
|
|
@ -133,8 +133,8 @@ function SessionItem(props: RouteComponentProps<Props>) {
|
|||
<div>{ live ? <Counter startTime={startedAt} /> : formattedDuration }</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ width: "30%", height: "38px" }} className="px-2 flex flex-col justify-between">
|
||||
<CountryFlag country={ userCountry } className="mr-2" label />
|
||||
<div style={{ width: "30%" }} className="px-2 flex flex-col justify-between">
|
||||
<div style={{ height: '21px'}}><CountryFlag country={ userCountry } style={{ paddingTop: '4px' }} label /></div>
|
||||
<div className="color-gray-medium flex items-center">
|
||||
<span className="capitalize" style={{ maxWidth: '70px'}}>
|
||||
<TextEllipsis text={ capitalize(userBrowser) } popupProps={{ inverted: true, size: "tiny" }} />
|
||||
|
|
|
|||
|
|
@ -1,44 +1,39 @@
|
|||
import React, { useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import stl from './SessionSearchField.module.css';
|
||||
import { Input } from 'UI';
|
||||
import FilterModal from 'Shared/Filters/FilterModal';
|
||||
import { fetchFilterSearch } from 'Duck/search';
|
||||
import { debounce } from 'App/utils';
|
||||
import { edit as editFilter, addFilterByKeyAndValue } from 'Duck/search';
|
||||
import { assist as assistRoute, isRoute } from "App/routes";
|
||||
const ASSIST_ROUTE = assistRoute();
|
||||
|
||||
interface Props {
|
||||
fetchFilterSearch: (query: any) => void;
|
||||
editFilter: typeof editFilter;
|
||||
addFilterByKeyAndValue: (key: string, value: string) => void;
|
||||
filterList: any;
|
||||
filterListLive: any;
|
||||
}
|
||||
function SessionSearchField(props: Props) {
|
||||
const debounceFetchFilterSearch = React.useCallback(debounce(props.fetchFilterSearch, 1000), []);
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
|
||||
const onSearchChange = (e, { value }) => {
|
||||
const onSearchChange = ({ target: { value } }: any) => {
|
||||
setSearchQuery(value)
|
||||
debounceFetchFilterSearch({ q: value });
|
||||
}
|
||||
|
||||
const onAddFilter = (filter) => {
|
||||
const onAddFilter = (filter: any) => {
|
||||
props.addFilterByKeyAndValue(filter.key, filter.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Input
|
||||
// inputProps={ { "data-openreplay-label": "Search", "autocomplete": "off" } }
|
||||
// className={stl.searchField}
|
||||
icon="search"
|
||||
onFocus={ () => setShowModal(true) }
|
||||
onBlur={ () => setTimeout(setShowModal, 200, false) }
|
||||
onChange={ onSearchChange }
|
||||
// icon="search"
|
||||
// iconPosition="left"
|
||||
placeholder={ 'Search sessions using any captured event (click, input, page, error...)'}
|
||||
// fluid
|
||||
id="search"
|
||||
type="search"
|
||||
autoComplete="off"
|
||||
|
|
@ -50,6 +45,7 @@ function SessionSearchField(props: Props) {
|
|||
searchQuery={searchQuery}
|
||||
isMainSearch={true}
|
||||
onFilterClick={onAddFilter}
|
||||
filters={isRoute(ASSIST_ROUTE, window.location.pathname) ? props.filterListLive : props.filterList }
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -57,4 +53,7 @@ function SessionSearchField(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default connect(null, { fetchFilterSearch, editFilter, addFilterByKeyAndValue })(SessionSearchField);
|
||||
export default connect((state: any) => ({
|
||||
filterList: state.getIn([ 'search', 'filterList' ]),
|
||||
filterListLive: state.getIn([ 'search', 'filterListLive' ]),
|
||||
}), { })(SessionSearchField);
|
||||
|
|
@ -12,7 +12,7 @@ const CountryFlag = React.memo(({ country, className, style = {}, label = false
|
|||
return (
|
||||
<div className="flex items-center" style={style}>
|
||||
{knownCountry
|
||||
? <div className={ cn(`flag flag-${ countryFlag }`, className, stl.default) } />
|
||||
? <div className={ cn(`mr-1 flag flag-${ countryFlag }`, className, stl.default) } />
|
||||
: (
|
||||
<div className="flex items-center w-full">
|
||||
<Icon name="flag-na" size="22" className="" />
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Icon } from 'UI';
|
||||
import cn from "classnames";
|
||||
import stl from './listItem.module.css';
|
||||
|
||||
const ListItem = ({icon, label, onClick, onRemove }) => {
|
||||
return (
|
||||
<div className={ cn(stl.wrapper, 'flex items-center capitalize') } onClick={ onClick }>
|
||||
<div className="flex items-center mr-auto">
|
||||
<Icon name={ icon } color="teal" size="16" />
|
||||
<span className="ml-3">{ label }</span>
|
||||
</div>
|
||||
<div className={ cn(stl.actionWrapper, "p-2")} onClick={onRemove}>
|
||||
<Icon name="trash" color="teal" size="12" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListItem;
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import stl from './savedSearchList.module.css';
|
||||
import cn from 'classnames';
|
||||
import { Icon, IconButton, Loader, Button } from 'UI';
|
||||
import { confirm } from 'UI';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { addFilterByKeyAndValue } from 'Duck/search';
|
||||
import {
|
||||
fetchList as fetchFunnelsList,
|
||||
remove as deleteSearch,
|
||||
// clearEvents,
|
||||
init
|
||||
} from 'Duck/funnels';
|
||||
import { setActiveFlow, clearEvents } from 'Duck/filters';
|
||||
import { setActiveTab } from 'Duck/sessions';
|
||||
import { funnel as funnelRoute, withSiteId } from 'App/routes';
|
||||
import Event, { TYPES } from 'Types/filter/event';
|
||||
import FunnelMenuItem from 'Components/Funnels/FunnelMenuItem';
|
||||
import FunnelSaveModal from 'Components/Funnels/FunnelSaveModal';
|
||||
import { blink as setBlink } from 'Duck/funnels';
|
||||
import { FilterKey } from 'Types/filter/filterType';
|
||||
|
||||
const DEFAULT_VISIBLE = 3;
|
||||
@withRouter
|
||||
class SavedSearchList extends React.Component {
|
||||
state = { showMore: false, showSaveModal: false }
|
||||
|
||||
setFlow = flow => {
|
||||
this.props.setActiveTab({ name: 'All', type: 'all' });
|
||||
this.props.setActiveFlow(flow)
|
||||
if (flow && flow.type === 'flows') {
|
||||
this.props.clearEvents()
|
||||
}
|
||||
}
|
||||
|
||||
renameHandler = funnel => {
|
||||
this.props.init(funnel);
|
||||
this.setState({ showSaveModal: true })
|
||||
}
|
||||
|
||||
deleteSearch = async (e, funnel) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (await confirm({
|
||||
header: 'Delete Funnel',
|
||||
confirmButton: 'Delete',
|
||||
confirmation: `Are you sure you want to permanently delete "${funnel.name}"?`
|
||||
})) {
|
||||
this.props.deleteSearch(funnel.funnelId).then(function() {
|
||||
this.props.fetchFunnelsList();
|
||||
this.setState({ showSaveModal: false })
|
||||
}.bind(this));
|
||||
} else {}
|
||||
}
|
||||
|
||||
createHandler = () => {
|
||||
const { filters } = this.props;
|
||||
if (filters.size === 0) {
|
||||
this.props.addFilterByKeyAndValue(FilterKey.LOCATION, '');
|
||||
this.props.addFilterByKeyAndValue(FilterKey.LOCATION, '');
|
||||
this.props.addFilterByKeyAndValue(FilterKey.CLICK, '')
|
||||
} else {
|
||||
this.props.setBlink()
|
||||
}
|
||||
}
|
||||
|
||||
onFlowClick = ({ funnelId }) => {
|
||||
const { siteId, history } = this.props;
|
||||
history.push(withSiteId(funnelRoute(funnelId), siteId));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { funnels, activeFlow, activeTab, loading } = this.props;
|
||||
const { showMore, showSaveModal } = this.state;
|
||||
const shouldLimit = funnels.size > DEFAULT_VISIBLE;
|
||||
|
||||
return (
|
||||
<div className={ stl.wrapper }>
|
||||
<FunnelSaveModal
|
||||
show={showSaveModal}
|
||||
closeHandler={() => this.setState({ showSaveModal: false })}
|
||||
/>
|
||||
<Loader loading={loading} size="small">
|
||||
<div className={ cn(stl.header, 'mt-3') }>
|
||||
<div className={ cn(stl.label, 'flex items-center relative') }>
|
||||
<span className="mr-2">Funnels</span>
|
||||
|
||||
{ funnels.size > 0 && (
|
||||
<IconButton
|
||||
tooltip="Create Funnel"
|
||||
circle
|
||||
size="small"
|
||||
icon="plus"
|
||||
outline
|
||||
onClick={ this.createHandler }
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{ funnels.size === 0 &&
|
||||
<div className="flex flex-col">
|
||||
<div className="color-gray-medium text-justify font-light mb-2">
|
||||
Funnels makes it easy to uncover the most significant issues that impacted conversions.
|
||||
</div>
|
||||
<IconButton className="-ml-2" icon="plus" label="Create Funnel" primaryText onClick={ this.createHandler } />
|
||||
</div>
|
||||
}
|
||||
{ funnels.size > 0 &&
|
||||
<React.Fragment>
|
||||
{ funnels.take(showMore ? funnels.size : DEFAULT_VISIBLE).map(filter => (
|
||||
<div key={filter.key}>
|
||||
<FunnelMenuItem
|
||||
title={filter.name}
|
||||
isPublic={filter.isPublic}
|
||||
iconName="filter"
|
||||
active={activeFlow && activeFlow.funnelId === filter.funnelId && activeTab.type !== 'flows'}
|
||||
onClick={ () => this.onFlowClick(filter)}
|
||||
deleteHandler={ (e) => this.deleteSearch(e, filter) }
|
||||
renameHandler={() => this.renameHandler(filter)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{ shouldLimit &&
|
||||
<div
|
||||
onClick={() => this.setState({ showMore: !showMore})}
|
||||
className={cn(stl.showMore, 'cursor-pointer py-2 flex items-center')}
|
||||
>
|
||||
{/* <Icon name={showMore ? 'arrow-up' : 'arrow-down'} size="16"/> */}
|
||||
<span className="ml-4 color-teal text-sm">{ showMore ? 'SHOW LESS' : 'SHOW MORE' }</span>
|
||||
</div>
|
||||
}
|
||||
</React.Fragment>
|
||||
}
|
||||
</Loader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
funnels: state.getIn([ 'funnels', 'list' ]),
|
||||
loading: state.getIn(['funnels', 'fetchListRequest', 'loading']),
|
||||
activeFlow: state.getIn([ 'filters', 'activeFlow' ]),
|
||||
activeTab: state.getIn([ 'sessions', 'activeTab' ]),
|
||||
siteId: state.getIn([ 'site', 'siteId' ]),
|
||||
events: state.getIn([ 'filters', 'appliedFilter', 'events' ]),
|
||||
filters: state.getIn([ 'search', 'instance', 'filters' ]),
|
||||
}), {
|
||||
deleteSearch, setActiveTab,
|
||||
setActiveFlow, clearEvents,
|
||||
addFilterByKeyAndValue,
|
||||
init,
|
||||
fetchFunnelsList,
|
||||
setBlink
|
||||
})(SavedSearchList)
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './SavedSearchList'
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
.wrapper {
|
||||
padding: 4px 5px;
|
||||
cursor: pointer;
|
||||
border: solid thin transparent;
|
||||
border-radius: 3px;
|
||||
margin-left: -5px;
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
background-color: $active-blue;
|
||||
border-color: $active-blue-border;
|
||||
& .actionWrapper {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
& span {
|
||||
color: $teal
|
||||
}
|
||||
|
||||
& .actionWrapper {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
.header {
|
||||
margin-bottom: 15px;
|
||||
& .label {
|
||||
text-transform: uppercase;
|
||||
color: gray;
|
||||
letter-spacing: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.showMore {
|
||||
&:hover {
|
||||
color: $teal;
|
||||
& svg {
|
||||
fill: $teal;
|
||||
}
|
||||
& .actions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,6 @@ export { default as JSONTree } from './JSONTree';
|
|||
export { default as Tooltip } from './Tooltip';
|
||||
export { default as CountryFlag } from './CountryFlag';
|
||||
export { default as RandomElement } from './RandomElement';
|
||||
export { default as SavedSearchList } from './SavedSearchList';
|
||||
export { default as SplitButton } from './SplitButton';
|
||||
export { default as confirm } from './Confirmation';
|
||||
export { default as SideMenuitem } from './SideMenuitem';
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
export default [
|
||||
{ value: 'performance.dom_content_loaded.average', text: 'performance.dom_content_loaded.average', unit: 'ms' },
|
||||
{ value: 'performance.first_meaningful_paint.average', text: 'performance.first_meaningful_paint.average', unit: 'ms' },
|
||||
{ value: 'performance.page_load_time.average', text: 'performance.page_load_time.average', unit: 'ms' },
|
||||
{ value: 'performance.dom_build_time.average', text: 'performance.dom_build_time.average', unit: 'ms' },
|
||||
{ value: 'performance.speed_index.average', text: 'performance.speed_index.average', unit: 'ms' },
|
||||
{ value: 'performance.page_response_time.average', text: 'performance.page_response_time.average', unit: 'ms' },
|
||||
{ value: 'performance.ttfb.average', text: 'performance.ttfb.average', unit: 'ms' },
|
||||
{ value: 'performance.time_to_render.average', text: 'performance.time_to_render.average', unit: 'ms' },
|
||||
{ value: 'performance.image_load_time.average', text: 'performance.image_load_time.average', unit: 'ms' },
|
||||
{ value: 'performance.request_load_time.average', text: 'performance.request_load_time.average', unit: 'ms' },
|
||||
{ value: 'resources.load_time.average', text: 'resources.load_time.average', unit: 'ms' },
|
||||
{ value: 'resources.missing.count', text: 'resources.missing.count', unit: '' },
|
||||
{ value: 'errors.4xx_5xx.count', text: 'errors.4xx_5xx.count', unit: '' },
|
||||
{ value: 'errors.4xx.count', text: 'errors.4xx.count', unit: '' },
|
||||
{ value: 'errors.5xx.count', text: 'errors.5xx.count', unit: '' },
|
||||
{ value: 'errors.javascript.impacted_sessions.count', text: 'errors.javascript.impacted_sessions.count', unit: '' },
|
||||
{ value: 'performance.crashes.count', text: 'performance.crashes.count', unit: '' },
|
||||
{ value: 'errors.javascript.count', text: 'errors.javascript.count', unit: '' },
|
||||
{ value: 'errors.backend.count', text: 'errors.backend.count', unit: '' },
|
||||
{ value: 'performance.dom_content_loaded.average', label: 'performance.dom_content_loaded.average', unit: 'ms' },
|
||||
{ value: 'performance.first_meaningful_paint.average', label: 'performance.first_meaningful_paint.average', unit: 'ms' },
|
||||
{ value: 'performance.page_load_time.average', label: 'performance.page_load_time.average', unit: 'ms' },
|
||||
{ value: 'performance.dom_build_time.average', label: 'performance.dom_build_time.average', unit: 'ms' },
|
||||
{ value: 'performance.speed_index.average', label: 'performance.speed_index.average', unit: 'ms' },
|
||||
{ value: 'performance.page_response_time.average', label: 'performance.page_response_time.average', unit: 'ms' },
|
||||
{ value: 'performance.ttfb.average', label: 'performance.ttfb.average', unit: 'ms' },
|
||||
{ value: 'performance.time_to_render.average', label: 'performance.time_to_render.average', unit: 'ms' },
|
||||
{ value: 'performance.image_load_time.average', label: 'performance.image_load_time.average', unit: 'ms' },
|
||||
{ value: 'performance.request_load_time.average', label: 'performance.request_load_time.average', unit: 'ms' },
|
||||
{ value: 'resources.load_time.average', label: 'resources.load_time.average', unit: 'ms' },
|
||||
{ value: 'resources.missing.count', label: 'resources.missing.count', unit: '' },
|
||||
{ value: 'errors.4xx_5xx.count', label: 'errors.4xx_5xx.count', unit: '' },
|
||||
{ value: 'errors.4xx.count', label: 'errors.4xx.count', unit: '' },
|
||||
{ value: 'errors.5xx.count', label: 'errors.5xx.count', unit: '' },
|
||||
{ value: 'errors.javascript.impacted_sessions.count', label: 'errors.javascript.impacted_sessions.count', unit: '' },
|
||||
{ value: 'performance.crashes.count', label: 'performance.crashes.count', unit: '' },
|
||||
{ value: 'errors.javascript.count', label: 'errors.javascript.count', unit: '' },
|
||||
{ value: 'errors.backend.count', label: 'errors.backend.count', unit: '' },
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,34 +1,34 @@
|
|||
import { FilterKey, IssueType } from 'Types/filter/filterType';
|
||||
// TODO remove text property from options
|
||||
export const options = [
|
||||
{ key: 'on', text: 'on', label: 'on', value: 'on' },
|
||||
{ key: 'notOn', text: 'not on', label: 'not on', value: 'notOn' },
|
||||
{ key: 'onAny', text: 'on any', label: 'on any', value: 'onAny' },
|
||||
{ key: 'is', text: 'is', label: 'is', value: 'is' },
|
||||
{ key: 'isAny', text: 'is any', label: 'is any', value: 'isAny' },
|
||||
{ key: 'inAnyPage', text: 'in any page', label: 'in any page', value: 'isAny' },
|
||||
{ key: 'isNot', text: 'is not', label: 'is not', value: 'isNot' },
|
||||
{ key: 'startsWith', text: 'starts with', label: 'starts with', value: 'startsWith' },
|
||||
{ key: 'endsWith', text: 'ends with', label: 'ends with', value: 'endsWith' },
|
||||
{ key: 'contains', text: 'contains', label: 'contains', value: 'contains' },
|
||||
{ key: 'notContains', text: 'not contains', label: 'not contains', value: 'notContains' },
|
||||
{ key: 'hasAnyValue', text: 'has any value', label: 'has any value', value: 'hasAnyValue' },
|
||||
{ key: 'hasNoValue', text: 'has no value', label: 'has no value', value: 'hasNoValue' },
|
||||
{ key: 'isSignedUp', text: 'is signed up', label: 'is signed up', value: 'isSignedUp' },
|
||||
{ key: 'notSignedUp', text: 'not signed up', label: 'not signed up', value: 'notSignedUp' },
|
||||
{ key: 'before', text: 'before', label: 'before', value: 'before' },
|
||||
{ key: 'after', text: 'after', label: 'after', value: 'after' },
|
||||
{ key: 'inRage', text: 'in rage', label: 'in rage', value: 'inRage' },
|
||||
{ key: 'notInRage', text: 'not in rage', label: 'not in rage', value: 'notInRage' },
|
||||
{ key: 'withinLast', text: 'within last', label: 'within last', value: 'withinLast' },
|
||||
{ key: 'notWithinLast', text: 'not within last', label: 'not within last', value: 'notWithinLast' },
|
||||
{ key: 'greaterThan', text: 'greater than', label: 'greater than', value: 'greaterThan' },
|
||||
{ key: 'lessThan', text: 'less than', label: 'less than', value: 'lessThan' },
|
||||
{ key: 'equal', text: 'equal', label: 'equal', value: 'equal' },
|
||||
{ key: 'not equal', text: 'not equal', label: 'not equal', value: 'not equal' },
|
||||
{ key: 'onSelector', text: 'on selector', label: 'on selector', value: 'onSelector' },
|
||||
{ key: 'onText', text: 'on text', label: 'on text', value: 'onText' },
|
||||
{ key: 'onComponent', text: 'on component', label: 'on component', value: 'onComponent' },
|
||||
{ key: 'on', label: 'on', value: 'on' },
|
||||
{ key: 'notOn', label: 'not on', value: 'notOn' },
|
||||
{ key: 'onAny', label: 'on any', value: 'onAny' },
|
||||
{ key: 'is', label: 'is', value: 'is' },
|
||||
{ key: 'isAny', label: 'is any', value: 'isAny' },
|
||||
{ key: 'inAnyPage', label: 'in any page', value: 'isAny' },
|
||||
{ key: 'isNot', label: 'is not', value: 'isNot' },
|
||||
{ key: 'startsWith', label: 'starts with', value: 'startsWith' },
|
||||
{ key: 'endsWith', label: 'ends with', value: 'endsWith' },
|
||||
{ key: 'contains', label: 'contains', value: 'contains' },
|
||||
{ key: 'notContains', label: 'not contains', value: 'notContains' },
|
||||
{ key: 'hasAnyValue', label: 'has any value', value: 'hasAnyValue' },
|
||||
{ key: 'hasNoValue', label: 'has no value', value: 'hasNoValue' },
|
||||
{ key: 'isSignedUp', label: 'is signed up', value: 'isSignedUp' },
|
||||
{ key: 'notSignedUp', label: 'not signed up', value: 'notSignedUp' },
|
||||
{ key: 'before', label: 'before', value: 'before' },
|
||||
{ key: 'after', label: 'after', value: 'after' },
|
||||
{ key: 'inRage', label: 'in rage', value: 'inRage' },
|
||||
{ key: 'notInRage', label: 'not in rage', value: 'notInRage' },
|
||||
{ key: 'withinLast', label: 'within last', value: 'withinLast' },
|
||||
{ key: 'notWithinLast', label: 'not within last', value: 'notWithinLast' },
|
||||
{ key: 'greaterThan', label: 'greater than', value: 'greaterThan' },
|
||||
{ key: 'lessThan', label: 'less than', value: 'lessThan' },
|
||||
{ key: 'equal', label: 'equal', value: 'equal' },
|
||||
{ key: 'not equal', label: 'not equal', value: 'not equal' },
|
||||
{ key: 'onSelector', label: 'on selector', value: 'onSelector' },
|
||||
{ key: 'onText', label: 'on text', value: 'onText' },
|
||||
{ key: 'onComponent', label: 'on component', value: 'onComponent' },
|
||||
];
|
||||
|
||||
const filterKeys = ['is', 'isNot'];
|
||||
|
|
@ -47,21 +47,21 @@ export const stringOperators = options.filter(({key}) => stringFilterKeys.includ
|
|||
export const stringOperatorsPerformance = options.filter(({key}) => stringFilterKeysPerformance.includes(key));
|
||||
export const targetOperators = options.filter(({key}) => targetFilterKeys.includes(key));
|
||||
export const booleanOperators = [
|
||||
{ key: 'true', text: 'true', label: 'true', value: 'true' },
|
||||
{ key: 'false', text: 'false', label: 'false', value: 'false' },
|
||||
{ key: 'true', label: 'true', value: 'true' },
|
||||
{ key: 'false', label: 'false', value: 'false' },
|
||||
]
|
||||
|
||||
export const customOperators = [
|
||||
{ key: '=', text: '=', label: '=', value: '=' },
|
||||
{ key: '<', text: '<', label: '<', value: '<' },
|
||||
{ key: '>', text: '>', label: '>', value: '>' },
|
||||
{ key: '<=', text: '<=', label: '<=', value: '<=' },
|
||||
{ key: '>=', text: '>=', label: '>=', value: '>=' },
|
||||
{ key: '=', label: '=', value: '=' },
|
||||
{ key: '<', label: '<', value: '<' },
|
||||
{ key: '>', label: '>', value: '>' },
|
||||
{ key: '<=', label: '<=', value: '<=' },
|
||||
{ key: '>=', label: '>=', value: '>=' },
|
||||
]
|
||||
|
||||
export const metricTypes = [
|
||||
{ text: 'Timeseries', label: 'Timeseries', value: 'timeseries' },
|
||||
{ text: 'Table', label: 'Table', value: 'table' },
|
||||
{ label: 'Timeseries', value: 'timeseries' },
|
||||
{ label: 'Table', value: 'table' },
|
||||
{ label: 'Funnel', value: 'funnel' },
|
||||
// { label: 'Errors', value: 'errors' },
|
||||
// { label: 'Sessions', value: 'sessions' },
|
||||
|
|
@ -77,42 +77,42 @@ export const tableColumnName = {
|
|||
}
|
||||
|
||||
export const metricOf = [
|
||||
{ text: 'Session Count', label: 'Session Count', value: 'sessionCount', type: 'timeseries' },
|
||||
{ text: 'Users', label: 'Users', value: FilterKey.USERID, type: 'table' },
|
||||
{ text: 'Sessions', label: 'Sessions', value: FilterKey.SESSIONS, type: 'table' },
|
||||
{ text: 'JS Errors', label: 'JS Errors', value: FilterKey.ERRORS, type: 'table' },
|
||||
{ text: 'Issues', label: 'Issues', value: FilterKey.ISSUE, type: 'table' },
|
||||
{ text: 'Browsers', label: 'Browsers', value: FilterKey.USER_BROWSER, type: 'table' },
|
||||
{ text: 'Devices', label: 'Devices', value: FilterKey.USER_DEVICE, type: 'table' },
|
||||
{ text: 'Countries', label: 'Countries', value: FilterKey.USER_COUNTRY, type: 'table' },
|
||||
{ text: 'URLs', label: 'URLs', value: FilterKey.LOCATION, type: 'table' },
|
||||
{ label: 'Session Count', value: 'sessionCount', type: 'timeseries' },
|
||||
{ label: 'Users', value: FilterKey.USERID, type: 'table' },
|
||||
{ label: 'Sessions', value: FilterKey.SESSIONS, type: 'table' },
|
||||
{ label: 'JS Errors', value: FilterKey.ERRORS, type: 'table' },
|
||||
{ label: 'Issues', value: FilterKey.ISSUE, type: 'table' },
|
||||
{ label: 'Browsers', value: FilterKey.USER_BROWSER, type: 'table' },
|
||||
{ label: 'Devices', value: FilterKey.USER_DEVICE, type: 'table' },
|
||||
{ label: 'Countries', value: FilterKey.USER_COUNTRY, type: 'table' },
|
||||
{ label: 'URLs', value: FilterKey.LOCATION, type: 'table' },
|
||||
]
|
||||
|
||||
export const methodOptions = [
|
||||
{ text: 'GET', label: 'GET', value: 'GET' },
|
||||
{ text: 'POST', label: 'POST', value: 'POST' },
|
||||
{ text: 'PUT', label: 'PUT', value: 'PUT' },
|
||||
{ text: 'DELETE', label: 'DELETE', value: 'DELETE' },
|
||||
{ text: 'PATCH', label: 'PATCH', value: 'PATCH' },
|
||||
{ text: 'HEAD', label: 'HEAD', value: 'HEAD' },
|
||||
{ text: 'OPTIONS', label: 'OPTIONS', value: 'OPTIONS' },
|
||||
{ text: 'TRACE', label: 'TRACE', value: 'TRACE' },
|
||||
{ text: 'CONNECT', label: 'CONNECT', value: 'CONNECT' },
|
||||
{ label: 'GET', value: 'GET' },
|
||||
{ label: 'POST', value: 'POST' },
|
||||
{ label: 'PUT', value: 'PUT' },
|
||||
{ label: 'DELETE', value: 'DELETE' },
|
||||
{ label: 'PATCH', value: 'PATCH' },
|
||||
{ label: 'HEAD', value: 'HEAD' },
|
||||
{ label: 'OPTIONS', value: 'OPTIONS' },
|
||||
{ label: 'TRACE', value: 'TRACE' },
|
||||
{ label: 'CONNECT', value: 'CONNECT' },
|
||||
]
|
||||
|
||||
export const issueOptions = [
|
||||
{ text: 'Click Rage', label: 'Click Rage', value: IssueType.CLICK_RAGE },
|
||||
{ text: 'Dead Click', label: 'Dead Click', value: IssueType.DEAD_CLICK },
|
||||
{ text: 'Excessive Scrolling', label: 'Excessive Scrolling', value: IssueType.EXCESSIVE_SCROLLING },
|
||||
{ text: 'Bad Request', label: 'Bad Request', value: IssueType.BAD_REQUEST },
|
||||
{ text: 'Missing Resource', label: 'Missing Resource', value: IssueType.MISSING_RESOURCE },
|
||||
{ text: 'Memory', label: 'Memory', value: IssueType.MEMORY },
|
||||
{ text: 'CPU', label: 'CPU', value: IssueType.CPU },
|
||||
{ text: 'Slow Resource', label: 'Slow Resource', value: IssueType.SLOW_RESOURCE },
|
||||
{ text: 'Slow Page Load', label: 'Slow Page Load', value: IssueType.SLOW_PAGE_LOAD },
|
||||
{ text: 'Crash', label: 'Crash', value: IssueType.CRASH },
|
||||
{ text: 'Custom', label: 'Custom', value: IssueType.CUSTOM },
|
||||
{ text: 'Error', label: 'Error', value: IssueType.JS_EXCEPTION },
|
||||
{ label: 'Click Rage', value: IssueType.CLICK_RAGE },
|
||||
{ label: 'Dead Click', value: IssueType.DEAD_CLICK },
|
||||
{ label: 'Excessive Scrolling', value: IssueType.EXCESSIVE_SCROLLING },
|
||||
{ label: 'Bad Request', value: IssueType.BAD_REQUEST },
|
||||
{ label: 'Missing Resource', value: IssueType.MISSING_RESOURCE },
|
||||
{ label: 'Memory', value: IssueType.MEMORY },
|
||||
{ label: 'CPU', value: IssueType.CPU },
|
||||
{ label: 'Slow Resource', value: IssueType.SLOW_RESOURCE },
|
||||
{ label: 'Slow Page Load', value: IssueType.SLOW_PAGE_LOAD },
|
||||
{ label: 'Crash', value: IssueType.CRASH },
|
||||
{ label: 'Custom', value: IssueType.CUSTOM },
|
||||
{ label: 'Error', value: IssueType.JS_EXCEPTION },
|
||||
]
|
||||
|
||||
export default {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
export default [
|
||||
{ value: 'desktop', text: 'Desktop' },
|
||||
{ value: 'mobile', text: 'Mobile' },
|
||||
{ value: 'tablet', text: 'Tablet' },
|
||||
{ value: 'desktop', label: 'Desktop' },
|
||||
{ value: 'mobile', label: 'Mobile' },
|
||||
{ value: 'tablet', label: 'Tablet' },
|
||||
]
|
||||
|
|
@ -1,49 +1,22 @@
|
|||
export const MINUTES = [
|
||||
{ value: 5, text: '5 Minutes' },
|
||||
{ value: 15, text: '15 Minutes' },
|
||||
{ value: 30, text: '30 Minutes' },
|
||||
{ value: 60, text: '60 Minutes' },
|
||||
{ value: 5, label: '5 Minutes' },
|
||||
{ value: 15, label: '15 Minutes' },
|
||||
{ value: 30, label: '30 Minutes' },
|
||||
{ value: 60, label: '60 Minutes' },
|
||||
];
|
||||
|
||||
export const HOURS = [ ...Array(24).keys() ].map(i => ({ value: i, text: `${ i > 9 ? '' : '0' }${ i }:00` }));
|
||||
export const HOURS = [ ...Array(24).keys() ].map(i => ({ value: i, label: `${ i > 9 ? '' : '0' }${ i }:00` }));
|
||||
|
||||
export const DAYS = [
|
||||
{
|
||||
value: -2,
|
||||
text: 'Every',
|
||||
},
|
||||
{
|
||||
value: -1,
|
||||
text: 'Everyday',
|
||||
},
|
||||
{
|
||||
value: 6,
|
||||
text: 'Sunday',
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
text: 'Monday',
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
text: 'Tuesday',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
text: 'Wednesday',
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
text: 'Thursday',
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
text: 'Friday',
|
||||
},
|
||||
{
|
||||
value: 5,
|
||||
text: 'Saturday',
|
||||
},
|
||||
{ value: -2, label: 'Every', },
|
||||
{ value: -1, label: 'Everyday', },
|
||||
{ value: 6, label: 'Sunday', },
|
||||
{ value: 0, label: 'Monday', },
|
||||
{ value: 1, label: 'Tuesday', },
|
||||
{ value: 2, label: 'Wednesday', },
|
||||
{ value: 3, label: 'Thursday', },
|
||||
{ value: 4, label: 'Friday', },
|
||||
{ value: 5, label: 'Saturday', },
|
||||
];
|
||||
|
||||
export const EMAIL = 'email';
|
||||
|
|
@ -51,16 +24,7 @@ export const SLACK = 'slack';
|
|||
export const WEBHOOK = 'webhook';
|
||||
|
||||
export const CHANNEL = [
|
||||
{
|
||||
value: EMAIL,
|
||||
text: 'Email'
|
||||
},
|
||||
{
|
||||
value: SLACK,
|
||||
text: 'Slack'
|
||||
},
|
||||
{
|
||||
value: WEBHOOK,
|
||||
text: 'Webhook'
|
||||
}
|
||||
{ value: EMAIL, label: 'Email' },
|
||||
{ value: SLACK, label: 'Slack' },
|
||||
{ value: WEBHOOK, label: 'Webhook' },
|
||||
]
|
||||
|
|
@ -1,21 +1,24 @@
|
|||
import { List, Map } from 'immutable';
|
||||
import { fetchType, editType } from './funcTools/crud';
|
||||
import { fetchListType, fetchType, editType } from './funcTools/crud';
|
||||
import { createRequestReducer } from './funcTools/request';
|
||||
import { mergeReducers } from './funcTools/tools';
|
||||
import { mergeReducers, success } from './funcTools/tools';
|
||||
import Filter from 'Types/filter';
|
||||
import { fetchList as fetchSessionList } from './sessions';
|
||||
import { liveFiltersMap } from 'Types/filter/newFilter';
|
||||
// import { fetchList as fetchSessionList } from './sessions';
|
||||
import { liveFiltersMap, filtersMap } from 'Types/filter/newFilter';
|
||||
import { filterMap, checkFilterValue, hasFilterApplied } from './search';
|
||||
import Session from 'Types/session';
|
||||
|
||||
const name = "liveSearch";
|
||||
const idKey = "searchId";
|
||||
|
||||
const FETCH_FILTER_SEARCH = fetchListType(`${name}/FILTER_SEARCH`);
|
||||
const FETCH = fetchType(name);
|
||||
const EDIT = editType(name);
|
||||
const CLEAR_SEARCH = `${name}/CLEAR_SEARCH`;
|
||||
const APPLY = `${name}/APPLY`;
|
||||
const UPDATE_CURRENT_PAGE = `${name}/UPDATE_CURRENT_PAGE`;
|
||||
const UPDATE_SORT = `${name}/UPDATE_SORT`;
|
||||
const FETCH_SESSION_LIST = fetchListType(`${name}/FETCH_SESSION_LIST`);
|
||||
|
||||
const initialState = Map({
|
||||
list: List(),
|
||||
|
|
@ -36,6 +39,23 @@ function reducer(state = initialState, action = {}) {
|
|||
return state.set('currentPage', action.page);
|
||||
case UPDATE_SORT:
|
||||
return state.mergeIn(['sort'], action.sort);
|
||||
case FETCH_SESSION_LIST:
|
||||
const { sessions, total } = action.data;
|
||||
const list = List(sessions).map(Session);
|
||||
return state
|
||||
.set('list', list)
|
||||
.set('total', total);
|
||||
case success(FETCH_FILTER_SEARCH):
|
||||
const groupedList = action.data.reduce((acc, item) => {
|
||||
const { projectId, type, value } = item;
|
||||
const key = type;
|
||||
if (!acc[key]) {
|
||||
acc[key] = [];
|
||||
}
|
||||
acc[key].push({ projectId, value });
|
||||
return acc;
|
||||
}, {});
|
||||
return state.set('filterSearchList', groupedList);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
|
@ -44,21 +64,32 @@ export default mergeReducers(
|
|||
reducer,
|
||||
createRequestReducer({
|
||||
fetch: FETCH,
|
||||
fetchList: FETCH_SESSION_LIST,
|
||||
}),
|
||||
);
|
||||
|
||||
const reduceThenFetchResource = actionCreator => (...args) => (dispatch, getState) => {
|
||||
dispatch(actionCreator(...args));
|
||||
const filter = getState().getIn([ 'search', 'instance']).toData();
|
||||
const filter = getState().getIn([ 'liveSearch', 'instance']).toData();
|
||||
filter.filters = filter.filters.map(filterMap);
|
||||
|
||||
filter.limit = 10;
|
||||
filter.page = getState().getIn([ 'liveSearch', 'currentPage']);
|
||||
|
||||
return dispatch(fetchSessionList(filter));
|
||||
};
|
||||
|
||||
export const edit = (instance) => ({
|
||||
type: EDIT,
|
||||
instance,
|
||||
});
|
||||
export const fetchSessionList = (filter) => {
|
||||
return {
|
||||
types: FETCH_SESSION_LIST.array,
|
||||
call: client => client.post('/assist/sessions', filter),
|
||||
}
|
||||
}
|
||||
|
||||
export const edit = reduceThenFetchResource((instance) => ({
|
||||
type: EDIT,
|
||||
instance,
|
||||
}));
|
||||
|
||||
export const applyFilter = reduceThenFetchResource((filter, fromUrl=false) => ({
|
||||
type: APPLY,
|
||||
|
|
@ -67,7 +98,7 @@ export const applyFilter = reduceThenFetchResource((filter, fromUrl=false) => ({
|
|||
}));
|
||||
|
||||
export const fetchSessions = (filter) => (dispatch, getState) => {
|
||||
const _filter = filter ? filter : getState().getIn([ 'search', 'instance']);
|
||||
const _filter = filter ? filter : getState().getIn([ 'liveSearch', 'instance']);
|
||||
return dispatch(applyFilter(_filter));
|
||||
};
|
||||
|
||||
|
|
@ -93,9 +124,12 @@ export const addFilter = (filter) => (dispatch, getState) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const addFilterByKeyAndValue = (key, value) => (dispatch, getState) => {
|
||||
let defaultFilter = liveFiltersMap[key];
|
||||
export const addFilterByKeyAndValue = (key, value, operator = undefined) => (dispatch, getState) => {
|
||||
let defaultFilter = filtersMap[key];
|
||||
defaultFilter.value = value;
|
||||
if (operator) {
|
||||
defaultFilter.operator = operator;
|
||||
}
|
||||
dispatch(addFilter(defaultFilter));
|
||||
}
|
||||
|
||||
|
|
@ -111,4 +145,13 @@ export function updateSort(sort) {
|
|||
type: UPDATE_SORT,
|
||||
sort,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchFilterSearch(params) {
|
||||
params.live = true
|
||||
return {
|
||||
types: FETCH_FILTER_SEARCH.array,
|
||||
call: client => client.get('/events/search', params),
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ const updateInstance = (state, instance) => state.getIn([ "savedSearch", savedSe
|
|||
|
||||
const initialState = Map({
|
||||
filterList: generateFilterOptions(filtersMap),
|
||||
filterListLive: generateLiveFilterOptions(liveFiltersMap),
|
||||
filterListLive: generateFilterOptions(liveFiltersMap),
|
||||
list: List(),
|
||||
alertMetricId: null,
|
||||
instance: new Filter({ filters: [] }),
|
||||
|
|
@ -63,7 +63,7 @@ function reducer(state = initialState, action = {}) {
|
|||
switch (action.type) {
|
||||
case REFRESH_FILTER_OPTIONS:
|
||||
return state.set('filterList', generateFilterOptions(filtersMap))
|
||||
.set('filterListLive', generateLiveFilterOptions(liveFiltersMap));
|
||||
.set('filterListLive', generateFilterOptions(liveFiltersMap));
|
||||
case EDIT:
|
||||
return state.mergeIn(['instance'], action.instance).set('currentPage', 1);
|
||||
case APPLY:
|
||||
|
|
@ -171,7 +171,6 @@ export const reduceThenFetchResource = actionCreator => (...args) => (dispatch,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
return isRoute(ERRORS_ROUTE, window.location.pathname)
|
||||
? dispatch(fetchErrorsList(filter))
|
||||
: dispatch(fetchSessionList(filter));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { List, Map } from 'immutable';
|
||||
import Session from 'Types/session';
|
||||
import ErrorStack from 'Types/session/errorStack';
|
||||
import Watchdog, { getSessionWatchdogTypes } from 'Types/watchdog';
|
||||
import Watchdog from 'Types/watchdog';
|
||||
import { clean as cleanParams } from 'App/api_client';
|
||||
import withRequestState, { RequestTypes } from './requestStateCreator';
|
||||
import { getRE } from 'App/utils';
|
||||
|
|
@ -83,55 +83,11 @@ const reducer = (state = initialState, action = {}) => {
|
|||
const { sessions, total } = action.data;
|
||||
const list = List(sessions).map(Session);
|
||||
|
||||
const { params } = action;
|
||||
const eventProperties = {
|
||||
eventCount: 0,
|
||||
eventTypes: [],
|
||||
dateFilter: params.rangeValue,
|
||||
filterKeys: Object.keys(params)
|
||||
.filter(key => ![ 'custom', 'startDate', 'endDate', 'strict', 'key', 'events', 'rangeValue' ].includes(key)),
|
||||
returnedCount: list.size,
|
||||
totalSearchCount: total,
|
||||
};
|
||||
if (Array.isArray(params.events)) {
|
||||
eventProperties.eventCount = params.events.length;
|
||||
params.events.forEach(({ type }) => {
|
||||
if (!eventProperties.eventTypes.includes(type)) {
|
||||
eventProperties.eventTypes.push(type);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const keyMap = {}
|
||||
list.forEach(s => {
|
||||
s.issueTypes.forEach(k => {
|
||||
if(keyMap[k])
|
||||
keyMap[k] += 1
|
||||
else
|
||||
keyMap[k] = 1;
|
||||
})
|
||||
})
|
||||
|
||||
const wdTypeCount = {}
|
||||
try{
|
||||
list.forEach(s => {
|
||||
getSessionWatchdogTypes(s).forEach(wdtp => {
|
||||
wdTypeCount[wdtp] = wdTypeCount[wdtp] ? wdTypeCount[wdtp] + 1 : 1;
|
||||
})
|
||||
})
|
||||
} catch(e) {
|
||||
|
||||
}
|
||||
|
||||
const sessionIds = list.map(({ sessionId }) => sessionId ).toJS();
|
||||
|
||||
return state
|
||||
.set('list', list)
|
||||
.set('sessionIds', sessionIds)
|
||||
.set('sessionIds', list.map(({ sessionId }) => sessionId ).toJS())
|
||||
.set('favoriteList', list.filter(({ favorite }) => favorite))
|
||||
.set('total', total)
|
||||
.set('keyMap', keyMap)
|
||||
.set('wdTypeCount', wdTypeCount);
|
||||
.set('total', total);
|
||||
case SET_AUTOPLAY_VALUES: {
|
||||
const sessionIds = state.get('sessionIds')
|
||||
const currentSessionId = state.get('current').sessionId
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ export default class Filter implements IFilter {
|
|||
this.filters.splice(index, 1)
|
||||
}
|
||||
|
||||
fromJson(json) {
|
||||
fromJson(json: any) {
|
||||
this.name = json.name
|
||||
this.filters = json.filters.map(i => new FilterItem().fromJson(i))
|
||||
this.eventsOrder = json.eventsOrder
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ export default class MessageDistributor extends StatedScreen {
|
|||
|
||||
const r = new MFileReader(new Uint8Array(), this.sessionStart)
|
||||
const msgs: Array<Message> = []
|
||||
loadFiles([this.session.mobsUrl],
|
||||
loadFiles(this.session.mobsUrl,
|
||||
b => {
|
||||
r.append(b)
|
||||
let next: ReturnType<MFileReader['next']>
|
||||
|
|
|
|||
|
|
@ -3,67 +3,78 @@ import { FilterType, FilterKey, FilterCategory } from './filterType'
|
|||
import filterOptions, { countries, platformOptions } from 'App/constants';
|
||||
import { capitalize } from 'App/utils';
|
||||
|
||||
const countryOptions = Object.keys(countries).map(i => ({ text: countries[i], value: i }));
|
||||
const countryOptions = Object.keys(countries).map(i => ({ label: countries[i], value: i }));
|
||||
const containsFilters = [{ key: 'contains', label: 'contains', text: 'contains', value: 'contains' }]
|
||||
|
||||
export const metaFilter = { key: FilterKey.METADATA, type: FilterType.MULTIPLE, category: FilterCategory.METADATA, label: 'Metadata', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/metadata' };
|
||||
export const filtersMap = {
|
||||
// EVENTS
|
||||
[FilterKey.CLICK]: { key: FilterKey.CLICK, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Click', operator: 'on', operatorOptions: filterOptions.targetOperators, icon: 'filters/click', isEvent: true },
|
||||
[FilterKey.INPUT]: { key: FilterKey.INPUT, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Input', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/input', isEvent: true },
|
||||
[FilterKey.LOCATION]: { key: FilterKey.LOCATION, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Path', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/location', isEvent: true },
|
||||
[FilterKey.CUSTOM]: { key: FilterKey.CUSTOM, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Custom Events', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/custom', isEvent: true },
|
||||
// [FilterKey.REQUEST]: { key: FilterKey.REQUEST, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Fetch', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch', isEvent: true },
|
||||
[FilterKey.FETCH]: { key: FilterKey.FETCH, type: FilterType.SUB_FILTERS, category: FilterCategory.JAVASCRIPT, operator: 'is', label: 'Network Request', filters: [
|
||||
{ key: FilterKey.FETCH_URL, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with URL', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.FETCH_STATUS_CODE, type: FilterType.NUMBER_MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with status code', operator: '=', operatorOptions: filterOptions.customOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.FETCH_METHOD, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.PERFORMANCE, label: 'with method', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch', options: filterOptions.methodOptions },
|
||||
{ key: FilterKey.FETCH_DURATION, type: FilterType.NUMBER, category: FilterCategory.PERFORMANCE, label: 'with duration (ms)', operator: '=', operatorOptions: filterOptions.customOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.FETCH_REQUEST_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with request body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.FETCH_RESPONSE_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with response body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
], icon: 'filters/fetch', isEvent: true },
|
||||
[FilterKey.GRAPHQL]: { key: FilterKey.GRAPHQL, type: FilterType.SUB_FILTERS, category: FilterCategory.JAVASCRIPT, label: 'GraphQL', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/graphql', isEvent: true, filters: [
|
||||
{ key: FilterKey.GRAPHQL_NAME, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with name', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.GRAPHQL_METHOD, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.PERFORMANCE, label: 'with method', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch', options: filterOptions.methodOptions },
|
||||
{ key: FilterKey.GRAPHQL_REQUEST_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with request body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.GRAPHQL_RESPONSE_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with response body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
]},
|
||||
[FilterKey.STATEACTION]: { key: FilterKey.STATEACTION, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'StateAction', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/state-action', isEvent: true },
|
||||
[FilterKey.ERROR]: { key: FilterKey.ERROR, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Error', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/error', isEvent: true },
|
||||
// [FilterKey.METADATA]: { key: FilterKey.METADATA, type: FilterType.MULTIPLE, category: FilterCategory.METADATA, label: 'Metadata', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/metadata', isEvent: true },
|
||||
export const filters = [
|
||||
{ key: FilterKey.CLICK, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Click', operator: 'on', operatorOptions: filterOptions.targetOperators, icon: 'filters/click', isEvent: true },
|
||||
{ key: FilterKey.INPUT, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Input', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/input', isEvent: true },
|
||||
{ key: FilterKey.LOCATION, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Path', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/location', isEvent: true },
|
||||
{ key: FilterKey.CUSTOM, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Custom Events', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/custom', isEvent: true },
|
||||
{ key: FilterKey.REQUEST, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Fetch', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch', isEvent: true },
|
||||
{ key: FilterKey.FETCH, type: FilterType.SUB_FILTERS, category: FilterCategory.JAVASCRIPT, operator: 'is', label: 'Network Request', filters: [
|
||||
{ key: FilterKey.FETCH_URL, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with URL', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.FETCH_STATUS_CODE, type: FilterType.NUMBER_MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with status code', operator: '=', operatorOptions: filterOptions.customOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.FETCH_METHOD, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.PERFORMANCE, label: 'with method', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch', options: filterOptions.methodOptions },
|
||||
{ key: FilterKey.FETCH_DURATION, type: FilterType.NUMBER, category: FilterCategory.PERFORMANCE, label: 'with duration (ms)', operator: '=', operatorOptions: filterOptions.customOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.FETCH_REQUEST_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with request body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.FETCH_RESPONSE_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with response body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
], icon: 'filters/fetch', isEvent: true },
|
||||
{ key: FilterKey.GRAPHQL, type: FilterType.SUB_FILTERS, category: FilterCategory.JAVASCRIPT, label: 'GraphQL', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/graphql', isEvent: true, filters: [
|
||||
{ key: FilterKey.GRAPHQL_NAME, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with name', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.GRAPHQL_METHOD, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.PERFORMANCE, label: 'with method', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch', options: filterOptions.methodOptions },
|
||||
{ key: FilterKey.GRAPHQL_REQUEST_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with request body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.GRAPHQL_RESPONSE_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with response body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
]},
|
||||
{ key: FilterKey.STATEACTION, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'StateAction', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/state-action', isEvent: true },
|
||||
{ key: FilterKey.ERROR, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Error', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/error', isEvent: true },
|
||||
{ key: FilterKey.METADATA, type: FilterType.MULTIPLE, category: FilterCategory.METADATA, label: 'Metadata', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/metadata', isEvent: true },
|
||||
|
||||
// FILTERS
|
||||
{ key: FilterKey.USER_OS, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'User OS', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/os' },
|
||||
{ key: FilterKey.USER_BROWSER, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'User Browser', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/browser' },
|
||||
{ key: FilterKey.USER_DEVICE, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'User Device', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/device' },
|
||||
{ key: FilterKey.PLATFORM, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.GEAR, label: 'Platform', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/platform', options: platformOptions },
|
||||
{ key: FilterKey.REVID, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'Version ID', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'collection' },
|
||||
{ key: FilterKey.REFERRER, type: FilterType.MULTIPLE, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'Referrer', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/arrow-return-right' },
|
||||
{ key: FilterKey.DURATION, type: FilterType.DURATION, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'Duration', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is']), icon: 'filters/duration' },
|
||||
{ key: FilterKey.USER_COUNTRY, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.USER, label: 'User Country', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/country', options: countryOptions },
|
||||
{ key: FilterKey.CONSOLE, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Console', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/console' },
|
||||
{ key: FilterKey.USERID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User Id', operator: 'is', operatorOptions: filterOptions.stringOperators.concat([{ label: 'is undefined', value: 'isUndefined'}]), icon: 'filters/userid' },
|
||||
{ key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User AnonymousId', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' },
|
||||
|
||||
// PERFORMANCE
|
||||
{ key: FilterKey.DOM_COMPLETE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'DOM Complete', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/dom-complete', isEvent: true, hasSource: true, sourceOperator: '>=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
|
||||
{ key: FilterKey.LARGEST_CONTENTFUL_PAINT_TIME, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Largest Contentful Paint', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/lcpt', isEvent: true, hasSource: true, sourceOperator: '>=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
|
||||
{ key: FilterKey.TTFB, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Time to First Byte', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/ttfb', isEvent: true, hasSource: true, sourceOperator: '>=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
|
||||
{ key: FilterKey.AVG_CPU_LOAD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg CPU Load', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/cpu-load', isEvent: true, hasSource: true, sourceOperator: '>=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
|
||||
{ key: FilterKey.AVG_MEMORY_USAGE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg Memory Usage', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/memory-load', isEvent: true, hasSource: true, sourceOperator: '>=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
|
||||
{ key: FilterKey.FETCH_FAILED, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Failed Request', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, icon: 'filters/fetch-failed', isEvent: true },
|
||||
{ key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/click', options: filterOptions.issueOptions },
|
||||
];
|
||||
|
||||
// FILTERS
|
||||
[FilterKey.USER_OS]: { key: FilterKey.USER_OS, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'User OS', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/os' },
|
||||
[FilterKey.USER_BROWSER]: { key: FilterKey.USER_BROWSER, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'User Browser', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/browser' },
|
||||
[FilterKey.USER_DEVICE]: { key: FilterKey.USER_DEVICE, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'User Device', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/device' },
|
||||
[FilterKey.PLATFORM]: { key: FilterKey.PLATFORM, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.GEAR, label: 'Platform', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/platform', options: platformOptions },
|
||||
[FilterKey.REVID]: { key: FilterKey.REVID, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'Version ID', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'collection' },
|
||||
[FilterKey.REFERRER]: { key: FilterKey.REFERRER, type: FilterType.MULTIPLE, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'Referrer', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/arrow-return-right' },
|
||||
[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.USER, 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.concat([{ text: 'is undefined', value: 'isUndefined'}]), icon: 'filters/userid' },
|
||||
[FilterKey.USERANONYMOUSID]: { key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User AnonymousId', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' },
|
||||
export const filtersMap = filters.reduce((acc, filter) => {
|
||||
acc[filter.key] = filter;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// PERFORMANCE
|
||||
[FilterKey.DOM_COMPLETE]: { key: FilterKey.DOM_COMPLETE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'DOM Complete', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/dom-complete', isEvent: true, hasSource: true, sourceOperator: '>=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
|
||||
[FilterKey.LARGEST_CONTENTFUL_PAINT_TIME]: { key: FilterKey.LARGEST_CONTENTFUL_PAINT_TIME, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Largest Contentful Paint', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/lcpt', isEvent: true, hasSource: true, sourceOperator: '>=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
|
||||
[FilterKey.TTFB]: { key: FilterKey.TTFB, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Time to First Byte', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/ttfb', isEvent: true, hasSource: true, sourceOperator: '>=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
|
||||
[FilterKey.AVG_CPU_LOAD]: { key: FilterKey.AVG_CPU_LOAD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg CPU Load', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/cpu-load', isEvent: true, hasSource: true, sourceOperator: '>=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
|
||||
[FilterKey.AVG_MEMORY_USAGE]: { key: FilterKey.AVG_MEMORY_USAGE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg Memory Usage', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/memory-load', isEvent: true, hasSource: true, sourceOperator: '>=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
|
||||
[FilterKey.FETCH_FAILED]: { key: FilterKey.FETCH_FAILED, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Failed Request', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, icon: 'filters/fetch-failed', isEvent: true },
|
||||
[FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/click', options: filterOptions.issueOptions },
|
||||
}
|
||||
export const liveFiltersMap = filters.reduce((acc, filter) => {
|
||||
if (
|
||||
filter.category !== FilterCategory.INTERACTIONS &&
|
||||
filter.category !== FilterCategory.JAVASCRIPT &&
|
||||
filter.category !== FilterCategory.PERFORMANCE
|
||||
) {
|
||||
acc[filter.key] = filter;
|
||||
}
|
||||
return acc
|
||||
}, {});
|
||||
|
||||
export const filterLabelMap = Object.keys(filtersMap).reduce((acc, key) => {
|
||||
acc[key] = filtersMap[key].label
|
||||
export const filterLabelMap = filters.reduce((acc, filter) => {
|
||||
acc[filter.key] = filter.label
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
export const liveFiltersMap = {
|
||||
[FilterKey.USERID]: { key: FilterKey.USERID, type: FilterType.STRING, category: FilterCategory.USER, label: 'User Id', operator: 'contains', operatorOptions: containsFilters, icon: 'filters/userid', isLive: true },
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new filter to the filter list
|
||||
* @param {*} category
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue