remove errors reducer, drop old components

This commit is contained in:
nick-delirium 2024-09-16 14:33:10 +02:00
parent 3f9b485be6
commit 29576a775c
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
25 changed files with 399 additions and 1233 deletions

View file

@ -1,21 +0,0 @@
import React, { useEffect } from 'react';
import ErrorListItem from '../ErrorListItem';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
function ErrorsList(props) {
const { errorStore, metricStore } = useStore();
const metric = useObserver(() => metricStore.instance);
useEffect(() => {
errorStore.fetchErrors();
}, []);
return (
<div>
Errors List
<ErrorListItem error={{}} />
</div>
);
}
export default ErrorsList;

View file

@ -1 +0,0 @@
export { default } from './ErrorsList';

View file

@ -1,12 +0,0 @@
import React from 'react';
import ErrorsList from '../ErrorsList';
function ErrorsWidget(props) {
return (
<div>
<ErrorsList />
</div>
);
}
export default ErrorsWidget;

View file

@ -1 +0,0 @@
export { default } from './ErrorsWidget';

View file

@ -1,89 +1,48 @@
import { observer } from 'mobx-react-lite';
import React from 'react';
import { connect } from 'react-redux';
import withSiteIdRouter from 'HOCs/withSiteIdRouter';
import { error as errorRoute } from 'App/routes';
import { NoContent, Loader } from 'UI';
import { fetch, fetchTrace } from 'Duck/errors';
import MainSection from './MainSection';
import SideSection from './SideSection';
import { useStore } from 'App/mstore';
import { Loader, NoContent } from 'UI';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
@connect(
(state) => ({
errorIdInStore: state.getIn(['errors', 'instance']).errorId,
list: state.getIn(['errors', 'instanceTrace']),
loading:
state.getIn(['errors', 'fetch', 'loading']) ||
state.getIn(['errors', 'fetchTrace', 'loading']),
errorOnFetch:
state.getIn(['errors', 'fetch', 'errors']) || state.getIn(['errors', 'fetchTrace', 'errors']),
}),
{
fetch,
fetchTrace,
}
)
@withSiteIdRouter
export default class ErrorInfo extends React.PureComponent {
ensureInstance() {
const { errorId, loading, errorOnFetch } = this.props;
if (!loading && this.props.errorIdInStore !== errorId && errorId != null) {
this.props.fetch(errorId);
this.props.fetchTrace(errorId);
}
}
componentDidMount() {
this.ensureInstance();
}
componentDidUpdate(prevProps) {
if (prevProps.errorId !== this.props.errorId || prevProps.errorIdInStore !== this.props.errorIdInStore) {
this.ensureInstance();
}
}
next = () => {
const { list, errorId } = this.props;
const curIndex = list.findIndex((e) => e.errorId === errorId);
const next = list.get(curIndex + 1);
if (next != null) {
this.props.history.push(errorRoute(next.errorId));
}
};
prev = () => {
const { list, errorId } = this.props;
const curIndex = list.findIndex((e) => e.errorId === errorId);
const prev = list.get(curIndex - 1);
if (prev != null) {
this.props.history.push(errorRoute(prev.errorId));
}
};
render() {
const { loading, errorIdInStore, list, errorId } = this.props;
import MainSection from './MainSection';
import SideSection from './SideSection';
let nextDisabled = true,
prevDisabled = true;
if (list.size > 0) {
nextDisabled = loading || list.last().errorId === errorId;
prevDisabled = loading || list.first().errorId === errorId;
}
function ErrorInfo(props) {
const { errorStore } = useStore();
return (
<NoContent
title={
<div className="flex flex-col items-center justify-center">
<AnimatedSVG name={ICONS.EMPTY_STATE} size="170" />
<div className="mt-4">No Error Found!</div>
</div>
}
subtext="Please try to find existing one."
show={!loading && errorIdInStore == null}
>
<div className="flex w-full">
<Loader loading={loading} className="w-full">
<MainSection className="w-9/12" />
<SideSection className="w-3/12" />
</Loader>
const ensureInstance = () => {
if (errorStore.isLoading) return;
errorStore.fetch(props.errorId);
errorStore.fetchTrace(props.errorId);
};
useEffect(() => {
ensureInstance();
}, [props.errorId]);
const errorIdInStore = errorStore.instance?.errorId;
const loading = errorStore.isLoading;
return (
<NoContent
title={
<div className="flex flex-col items-center justify-center">
<AnimatedSVG name={ICONS.EMPTY_STATE} size="170" />
<div className="mt-4">No Error Found!</div>
</div>
</NoContent>
);
}
}
subtext="Please try to find existing one."
show={!loading && errorIdInStore == null}
>
<div className="flex w-full">
<Loader loading={loading} className="w-full">
<MainSection className="w-9/12" />
<SideSection className="w-3/12" />
</Loader>
</div>
</NoContent>
);
}
export default observer(ErrorInfo);

View file

@ -1,154 +1,134 @@
import { RESOLVED } from 'Types/errorInfo';
import { FilterKey } from 'Types/filter/filterType';
import cn from 'classnames';
import { observer } from 'mobx-react-lite';
import React from 'react';
import { connect } from 'react-redux';
import cn from 'classnames';
import withSiteIdRouter from 'HOCs/withSiteIdRouter';
import { ErrorDetails, Icon, Loader, Button } from 'UI';
import { sessions as sessionsRoute } from 'App/routes';
import { RESOLVED } from 'Types/errorInfo';
import { addFilterByKeyAndValue } from 'Duck/search';
import { resolve, unresolve, ignore, toggleFavorite } from 'Duck/errors';
import { withRouter } from 'react-router-dom';
import { resentOrDate } from 'App/date';
import { useStore } from 'App/mstore';
import { sessions as sessionsRoute } from 'App/routes';
import Divider from 'Components/Errors/ui/Divider';
import ErrorName from 'Components/Errors/ui/ErrorName';
import Label from 'Components/Errors/ui/Label';
import { FilterKey } from 'Types/filter/filterType';
import { addFilterByKeyAndValue } from 'Duck/search';
import { Button, ErrorDetails, Icon, Loader } from 'UI';
import SessionBar from './SessionBar';
@withSiteIdRouter
@connect(
(state) => ({
error: state.getIn(['errors', 'instance']),
trace: state.getIn(['errors', 'instanceTrace']),
sourcemapUploaded: state.getIn(['errors', 'sourcemapUploaded']),
resolveToggleLoading:
state.getIn(['errors', 'resolve', 'loading']) ||
state.getIn(['errors', 'unresolve', 'loading']),
ignoreLoading: state.getIn(['errors', 'ignore', 'loading']),
toggleFavoriteLoading: state.getIn(['errors', 'toggleFavorite', 'loading']),
traceLoading: state.getIn(['errors', 'fetchTrace', 'loading']),
}),
{
resolve,
unresolve,
ignore,
toggleFavorite,
addFilterByKeyAndValue,
}
)
export default class MainSection extends React.PureComponent {
resolve = () => {
const { error } = this.props;
this.props.resolve(error.errorId);
};
function MainSection(props) {
const { errorStore } = useStore();
const error = errorStore.instance;
const trace = errorStore.instanceTrace;
const sourcemapUploaded = errorStore.sourcemapUploaded;
const loading = errorStore.isLoading;
const addFilterByKeyAndValue = props.addFilterByKeyAndValue;
const className = props.className;
unresolve = () => {
const { error } = this.props;
this.props.unresolve(error.errorId);
};
ignore = () => {
const { error } = this.props;
this.props.ignore(error.errorId);
};
bookmark = () => {
const { error } = this.props;
this.props.toggleFavorite(error.errorId);
};
findSessions = () => {
this.props.addFilterByKeyAndValue(FilterKey.ERROR, this.props.error.message);
const findSessions = () => {
addFilterByKeyAndValue(FilterKey.ERROR, error.message);
this.props.history.push(sessionsRoute());
};
render() {
const {
error,
trace,
sourcemapUploaded,
ignoreLoading,
resolveToggleLoading,
toggleFavoriteLoading,
className,
traceLoading,
} = this.props;
const isPlayer = window.location.pathname.includes('/session/');
return (
<div className={cn(className, 'bg-white border-radius-3 thin-gray-border mb-6')}>
<div className="m-4">
<ErrorName
className="text-lg leading-relaxed"
name={error.name}
message={error.stack0InfoString}
lineThrough={error.status === RESOLVED}
/>
<div className="flex flex-col">
<div
className="flex items-center color-gray-dark font-semibold"
style={{ wordBreak: 'break-all' }}
>
{error.message}
return (
<div
className={cn(
className,
'bg-white border-radius-3 thin-gray-border mb-6'
)}
>
<div className="m-4">
<ErrorName
className="text-lg leading-relaxed"
name={error.name}
message={error.stack0InfoString}
lineThrough={error.status === RESOLVED}
/>
<div className="flex flex-col">
<div
className="flex items-center color-gray-dark font-semibold"
style={{ wordBreak: 'break-all' }}
>
{error.message}
</div>
<div className="flex items-center mt-2">
<div className="flex">
<Label
topValue={error.sessions}
horizontal
topValueSize="text-lg"
bottomValue="Sessions"
/>
<Label
topValue={error.users}
horizontal
topValueSize="text-lg"
bottomValue="Users"
/>
</div>
<div className="flex items-center mt-2">
<div className="flex">
<Label
topValue={error.sessions}
horizontal
topValueSize="text-lg"
bottomValue="Sessions"
/>
<Label
topValue={error.users}
horizontal
topValueSize="text-lg"
bottomValue="Users"
/>
</div>
<div className="text-xs color-gray-medium">Over the past 30 days</div>
<div className="text-xs color-gray-medium">
Over the past 30 days
</div>
</div>
</div>
<Divider />
<div className="m-4">
<div className="flex items-center">
<h3 className="text-xl inline-block mr-2">Last session with this error</h3>
<span className="font-thin text-sm">{resentOrDate(error.lastOccurrence)}</span>
<Button className="ml-auto" variant="text-primary" onClick={this.findSessions}>
Find all sessions with this error
<Icon className="ml-1" name="next1" color="teal" />
</Button>
</div>
<SessionBar className="my-4" session={error.lastHydratedSession} />
{error.customTags.length > 0 ? (
<div className="flex items-start flex-col">
<div>
<span className="font-semibold">More Info</span> <span className="text-disabled-text">(most recent call)</span>
</div>
<div className="mt-4 flex items-center gap-3 w-full flex-wrap">
{error.customTags.map((tag) => (
<div className="flex items-center rounded overflow-hidden bg-gray-lightest">
<div className="bg-gray-light-shade py-1 px-2 text-disabled-text">{Object.entries(tag)[0][0]}</div> <div className="py-1 px-2 text-gray-dark">{Object.entries(tag)[0][1]}</div>
</div>
))}
</div>
</div>
) : null}
</div>
<Divider />
<div className="m-4">
<Loader loading={traceLoading}>
<ErrorDetails
name={error.name}
message={error.message}
errorStack={trace}
error={error}
sourcemapUploaded={sourcemapUploaded}
/>
</Loader>
</div>
</div>
);
}
<Divider />
<div className="m-4">
<div className="flex items-center">
<h3 className="text-xl inline-block mr-2">
Last session with this error
</h3>
<span className="font-thin text-sm">
{resentOrDate(error.lastOccurrence)}
</span>
<Button
className="ml-auto"
variant="text-primary"
onClick={findSessions}
>
Find all sessions with this error
<Icon className="ml-1" name="next1" color="teal" />
</Button>
</div>
<SessionBar className="my-4" session={error.lastHydratedSession} />
{error.customTags.length > 0 ? (
<div className="flex items-start flex-col">
<div>
<span className="font-semibold">More Info</span>{' '}
<span className="text-disabled-text">(most recent call)</span>
</div>
<div className="mt-4 flex items-center gap-3 w-full flex-wrap">
{error.customTags.map((tag) => (
<div className="flex items-center rounded overflow-hidden bg-gray-lightest">
<div className="bg-gray-light-shade py-1 px-2 text-disabled-text">
{Object.entries(tag)[0][0]}
</div>{' '}
<div className="py-1 px-2 text-gray-dark">
{Object.entries(tag)[0][1]}
</div>
</div>
))}
</div>
</div>
) : null}
</div>
<Divider />
<div className="m-4">
<Loader loading={loading}>
<ErrorDetails
name={error.name}
message={error.message}
errorStack={trace}
error={error}
sourcemapUploaded={sourcemapUploaded}
/>
</Loader>
</div>
</div>
);
}
export default withRouter(
connect(null, { addFilterByKeyAndValue })(observer(MainSection))
);

View file

@ -1,154 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import withSiteIdRouter from 'HOCs/withSiteIdRouter';
import withPermissions from 'HOCs/withPermissions'
import { UNRESOLVED, RESOLVED, IGNORED, BOOKMARK } from "Types/errorInfo";
import { fetchBookmarks, editOptions } from "Duck/errors";
import { applyFilter } from 'Duck/search';
import { errors as errorsRoute, isRoute } from "App/routes";
import withPageTitle from 'HOCs/withPageTitle';
import cn from 'classnames';
import SelectDateRange from 'Shared/SelectDateRange';
import Period from 'Types/app/period';
import List from './List/List';
import ErrorInfo from './Error/ErrorInfo';
import Header from './Header';
import SideMenuSection from './SideMenu/SideMenuSection';
import SideMenuDividedItem from './SideMenu/SideMenuDividedItem';
const ERRORS_ROUTE = errorsRoute();
function getStatusLabel(status) {
switch(status) {
case UNRESOLVED:
return "Unresolved";
case RESOLVED:
return "Resolved";
case IGNORED:
return "Ignored";
default:
return "";
}
}
@withPermissions(['ERRORS'], 'page-margin container-90')
@withSiteIdRouter
@connect(state => ({
list: state.getIn([ "errors", "list" ]),
status: state.getIn([ "errors", "options", "status" ]),
filter: state.getIn([ 'search', 'instance' ]),
}), {
fetchBookmarks,
applyFilter,
editOptions,
})
@withPageTitle("Errors - OpenReplay")
export default class Errors extends React.PureComponent {
constructor(props) {
super(props)
this.state = {
filter: '',
}
}
ensureErrorsPage() {
const { history } = this.props;
if (!isRoute(ERRORS_ROUTE, history.location.pathname)) {
history.push(ERRORS_ROUTE);
}
}
onStatusItemClick = ({ key }) => {
this.props.editOptions({ status: key });
}
onBookmarksClick = () => {
this.props.editOptions({ status: BOOKMARK });
}
onDateChange = (e) => {
const dateValues = e.toJSON();
this.props.applyFilter(dateValues);
};
render() {
const {
count,
match: {
params: { errorId }
},
status,
list,
history,
filter,
} = this.props;
const { startDate, endDate, rangeValue } = filter;
const period = new Period({ start: startDate, end: endDate, rangeName: rangeValue });
return (
<div className="page-margin container-90" >
<div className={cn("side-menu", {'disabled' : !isRoute(ERRORS_ROUTE, history.location.pathname)})}>
<SideMenuSection
title="Errors"
onItemClick={this.onStatusItemClick}
items={[
{
key: UNRESOLVED,
icon: "exclamation-circle",
label: getStatusLabel(UNRESOLVED),
active: status === UNRESOLVED,
},
{
key: RESOLVED,
icon: "check",
label: getStatusLabel(RESOLVED),
active: status === RESOLVED,
},
{
key: IGNORED,
icon: "ban",
label: getStatusLabel(IGNORED),
active: status === IGNORED,
}
]}
/>
<SideMenuDividedItem
className="mt-3 mb-4"
iconName="star"
title="Bookmarks"
active={ status === BOOKMARK }
onClick={ this.onBookmarksClick }
/>
</div>
<div className="side-menu-margined">
{ errorId == null ?
<>
<div className="mb-5 flex items-baseline">
<Header
text={ status === BOOKMARK ? "Bookmarks" : getStatusLabel(status) }
count={ list.size }
/>
<div className="ml-3 flex items-center">
<span className="mr-2 color-gray-medium">Seen in</span>
<SelectDateRange
period={period}
onChange={this.onDateChange}
/>
</div>
</div>
<List
status={ status }
list={ list }
/>
</>
:
<ErrorInfo errorId={ errorId } list={ list } />
}
</div>
</div>
);
}
}

View file

@ -1,14 +0,0 @@
import React from 'react';
function Header({ text, count }) {
return (
<h3 className="text-2xl capitalize">
<span>{ text }</span>
{ count != null && <span className="ml-2 font-normal color-gray-medium">{ count }</span> }
</h3>
);
}
Header.displayName = "Header";
export default Header;

View file

@ -1,259 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import { Set } from "immutable";
import { NoContent, Loader, Checkbox, IconButton, Input, Pagination } from 'UI';
import { merge, resolve, unresolve, ignore, updateCurrentPage, editOptions } from "Duck/errors";
import { applyFilter } from 'Duck/filters';
import { IGNORED, UNRESOLVED } from 'Types/errorInfo';
import Divider from 'Components/Errors/ui/Divider';
import ListItem from './ListItem/ListItem';
import { debounce } from 'App/utils';
import Select from 'Shared/Select';
import EmptyStateSvg from '../../../svg/no-results.svg';
const sortOptionsMap = {
'occurrence-desc': 'Last Occurrence',
'occurrence-desc': 'First Occurrence',
'sessions-asc': 'Sessions Ascending',
'sessions-desc': 'Sessions Descending',
'users-asc': 'Users Ascending',
'users-desc': 'Users Descending',
};
const sortOptions = Object.entries(sortOptionsMap)
.map(([ value, label ]) => ({ value, label }));
@connect(state => ({
loading: state.getIn([ "errors", "loading" ]),
resolveToggleLoading: state.getIn(["errors", "resolve", "loading"]) ||
state.getIn(["errors", "unresolve", "loading"]),
ignoreLoading: state.getIn([ "errors", "ignore", "loading" ]),
mergeLoading: state.getIn([ "errors", "merge", "loading" ]),
currentPage: state.getIn(["errors", "currentPage"]),
limit: state.getIn(["errors", "limit"]),
total: state.getIn([ 'errors', 'totalCount' ]),
sort: state.getIn([ 'errors', 'options', 'sort' ]),
order: state.getIn([ 'errors', 'options', 'order' ]),
query: state.getIn([ "errors", "options", "query" ]),
}), {
merge,
resolve,
unresolve,
ignore,
applyFilter,
updateCurrentPage,
editOptions,
})
export default class List extends React.PureComponent {
constructor(props) {
super(props)
this.state = {
checkedAll: false,
checkedIds: Set(),
query: props.query,
}
this.debounceFetch = debounce(this.props.editOptions, 1000);
}
componentDidMount() {
this.props.applyFilter({ });
}
check = ({ errorId }) => {
const { checkedIds } = this.state;
const newCheckedIds = checkedIds.contains(errorId)
? checkedIds.remove(errorId)
: checkedIds.add(errorId);
this.setState({
checkedAll: newCheckedIds.size === this.props.list.size,
checkedIds: newCheckedIds
});
}
checkAll = () => {
if (this.state.checkedAll) {
this.setState({
checkedAll: false,
checkedIds: Set(),
});
} else {
this.setState({
checkedAll: true,
checkedIds: this.props.list.map(({ errorId }) => errorId).toSet(),
});
}
}
resetChecked = () => {
this.setState({
checkedAll: false,
checkedIds: Set(),
});
}
currentCheckedIds() {
return this.state.checkedIds
.intersect(this.props.list.map(({ errorId }) => errorId).toSet());
}
merge = () => {
this.props.merge(currentCheckedIds().toJS()).then(this.resetChecked);
}
applyToAllChecked(f) {
return Promise.all(this.currentCheckedIds().map(f).toJS()).then(this.resetChecked);
}
resolve = () => {
this.applyToAllChecked(this.props.resolve);
}
unresolve = () => {
this.applyToAllChecked(this.props.unresolve);
}
ignore = () => {
this.applyToAllChecked(this.props.ignore);
}
addPage = () => this.props.updateCurrentPage(this.props.currentPage + 1)
writeOption = ({ name, value }) => {
const [ sort, order ] = value.split('-');
if (name === 'sort') {
this.props.editOptions({ sort, order });
}
}
// onQueryChange = ({ target: { value, name } }) => props.edit({ [ name ]: value })
onQueryChange = ({ target: { value, name } }) => {
this.setState({ query: value });
this.debounceFetch({ query: value });
}
render() {
const {
list,
status,
loading,
ignoreLoading,
resolveToggleLoading,
mergeLoading,
currentPage,
total,
sort,
order,
limit,
} = this.props;
const {
checkedAll,
checkedIds,
query,
} = this.state;
const someLoading = loading || ignoreLoading || resolveToggleLoading || mergeLoading;
const currentCheckedIds = this.currentCheckedIds();
return (
<div className="bg-white p-5 border-radius-3 thin-gray-border">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center" style={{ height: "36px" }}>
<Checkbox
className="mr-3"
checked={ checkedAll }
onChange={ this.checkAll }
/>
{ status === UNRESOLVED
? <IconButton
outline
className="mr-3"
label="Resolve"
icon="check"
size="small"
loading={ resolveToggleLoading }
onClick={ this.resolve }
disabled={ someLoading || currentCheckedIds.size === 0}
/>
: <IconButton
outline
className="mr-3"
label="Unresolve"
icon="exclamation-circle"
size="small"
loading={ resolveToggleLoading }
onClick={ this.unresolve }
disabled={ someLoading || currentCheckedIds.size === 0}
/>
}
{ status !== IGNORED &&
<IconButton
outline
className="mr-3"
label="Ignore"
icon="ban"
size="small"
loading={ ignoreLoading }
onClick={ this.ignore }
disabled={ someLoading || currentCheckedIds.size === 0}
/>
}
</div>
<div className="flex items-center ml-6">
<span className="mr-2 color-gray-medium">Sort By</span>
<Select
defaultValue={ `${sort}-${order}` }
name="sort"
plain
options={ sortOptions }
onChange={ this.writeOption }
/>
<Input
style={{ width: '350px'}}
wrapperClassName="ml-3"
placeholder="Filter by name or message"
icon="search"
name="filter"
onChange={ this.onQueryChange }
value={query}
/>
</div>
</div>
<Divider />
<NoContent
title={
<div className="flex flex-col items-center justify-center">
<object style={{ width: "180px"}} type="image/svg+xml" data={EmptyStateSvg} />
<span className="mr-2">No Errors Found!</span>
</div>
}
subtext="Please try to change your search parameters."
// animatedIcon="empty-state"
show={ !loading && list.size === 0}
>
<Loader loading={ loading }>
{ list.map(e =>
<div key={e.errorId} style={{ opacity: e.disabled ? 0.5 : 1}}>
<ListItem
disabled={someLoading || e.disabled}
key={e.errorId}
error={e}
checked={ checkedIds.contains(e.errorId) }
onCheck={ this.check }
/>
<Divider/>
</div>
)}
<div className="w-full flex items-center justify-center mt-4">
<Pagination
page={currentPage}
total={total}
onPageChange={(page) => this.props.updateCurrentPage(page)}
limit={limit}
debounceRequest={500}
/>
</div>
</Loader>
</NoContent>
</div>
);
}
}

View file

@ -1,87 +0,0 @@
import React from 'react';
import { BarChart, Bar, YAxis, Tooltip, XAxis } from 'recharts';
import cn from 'classnames';
import { DateTime } from 'luxon'
import { diffFromNowString } from 'App/date';
import { error as errorRoute } from 'App/routes';
import { IGNORED, RESOLVED } from 'Types/errorInfo';
import { Checkbox, Link } from 'UI';
import ErrorName from 'Components/Errors/ui/ErrorName';
import Label from 'Components/Errors/ui/Label';
import stl from './listItem.module.css';
import { Styles } from '../../../Dashboard/Widgets/common';
const CustomTooltip = ({ active, payload, label }) => {
if (active) {
const p = payload[0].payload;
const dateStr = p.timestamp ? DateTime.fromMillis(p.timestamp).toFormat('l') : ''
return (
<div className="rounded border bg-white p-2">
<p className="label text-sm color-gray-medium">{dateStr}</p>
<p className="text-sm">Sessions: {p.count}</p>
</div>
);
}
return null;
};
function ListItem({ className, onCheck, checked, error, disabled }) {
const getDateFormat = val => {
const d = new Date(val);
return (d.getMonth()+ 1) + '/' + d.getDate()
}
return (
<div className={ cn("flex justify-between cursor-pointer py-4", className) } id="error-item">
<Checkbox
disabled={disabled}
checked={ checked }
onChange={ () => onCheck(error) }
/>
<div className={ cn("ml-3 flex-1 leading-tight", stl.name) } >
<Link to={errorRoute(error.errorId)} >
<ErrorName
icon={error.status === IGNORED ? 'ban' : null }
lineThrough={error.status === RESOLVED}
name={ error.name }
message={ error.stack0InfoString }
bold={ !error.viewed }
/>
<div
className={ cn("truncate color-gray-medium", { "line-through" : error.status === RESOLVED}) }
>
{ error.message }
</div>
</Link>
</div>
<BarChart width={ 150 } height={ 40 } data={ error.chart }>
<XAxis hide dataKey="timestamp" />
<YAxis hide domain={[0, 'dataMax + 8']} />
<Tooltip {...Styles.tooltip} label="Sessions" content={<CustomTooltip />} />
<Bar name="Sessions" minPointSize={1} dataKey="count" fill="#A8E0DA" />
</BarChart>
<Label
className={stl.sessions}
topValue={ error.sessions }
bottomValue="Sessions"
/>
<Label
className={stl.users}
topValue={ error.users }
bottomValue="Users"
/>
<Label
className={stl.occurrence}
topValue={ `${diffFromNowString(error.lastOccurrence)} ago` }
bottomValue="Last Seen"
/>
</div>
);
}
ListItem.displayName = "ListItem";
export default ListItem;

View file

@ -1,16 +0,0 @@
.name {
min-width: 55%;
}
.sessions {
width: 6%;
}
.users {
width: 5%;
}
.occurrence {
width: 15%;
min-width: 152px;
}

View file

@ -1,20 +0,0 @@
import React from 'react';
import { SideMenuitem } from "UI";
import Divider from 'Components/Errors/ui/Divider';
function SideMenuDividedItem({ className, noTopDivider = false, noBottomDivider = false, ...props }) {
return (
<div className={className}>
{ !noTopDivider && <Divider /> }
<SideMenuitem
className="my-3"
{ ...props }
/>
{ !noBottomDivider && <Divider /> }
</div>
);
}
SideMenuDividedItem.displayName = "SideMenuDividedItem";
export default SideMenuDividedItem;

View file

@ -1,14 +0,0 @@
import React from 'react';
import cn from 'classnames';
import stl from './sideMenuHeader.module.css';
function SideMenuHeader({ text, className }) {
return (
<div className={ cn(className, stl.label, "uppercase color-gray") }>
{ text }
</div>
)
}
SideMenuHeader.displayName = "SideMenuHeader";
export default SideMenuHeader;

View file

@ -1,24 +0,0 @@
import React from 'react';
import { SideMenuitem } from 'UI';
import SideMenuHeader from './SideMenuHeader';
function SideMenuSection({ title, items, onItemClick }) {
return (
<>
<SideMenuHeader className="mb-4" text={ title }/>
{ items.map(item =>
<SideMenuitem
key={ item.key }
active={ item.active }
title={ item.label }
iconName={ item.icon }
onClick={() => onItemClick(item)}
/>
)}
</>
);
}
SideMenuSection.displayName = "SideMenuSection";
export default SideMenuSection;

View file

@ -1,4 +0,0 @@
.label {
letter-spacing: 0.2em;
color: gray;
}

View file

@ -1,34 +1,40 @@
import React, { useEffect } from 'react'
import { fetchNewErrorsCount } from 'Duck/errors'
import { connect } from 'react-redux'
import stl from './errorsBadge.module.css'
import {
getDateRangeFromValue,
DATE_RANGE_VALUES,
} from 'App/dateRange';
import { observer } from 'mobx-react-lite';
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { DATE_RANGE_VALUES, getDateRangeFromValue } from 'App/dateRange';
import { useStore } from 'App/mstore';
import stl from './errorsBadge.module.css';
const AUTOREFRESH_INTERVAL = 5 * 60 * 1000;
const weekRange = getDateRangeFromValue(DATE_RANGE_VALUES.LAST_7_DAYS);
let intervalId = null
let intervalId = null;
function ErrorsBadge({ errorsStats = {}, fetchNewErrorsCount, projects }) {
function ErrorsBadge({ projects }) {
const { errorsStore } = useStore();
const errorsStats = errorsStore.stats;
useEffect(() => {
if (projects.size === 0 || !!intervalId) return;
const params = { startTimestamp: weekRange.start.ts, endTimestamp: weekRange.end.ts };
fetchNewErrorsCount(params)
const params = {
startTimestamp: weekRange.start.ts,
endTimestamp: weekRange.end.ts,
};
errorsStore.fetchNewErrorsCount(params);
intervalId = setInterval(() => {
fetchNewErrorsCount(params);
errorsStore.fetchNewErrorsCount(params);
}, AUTOREFRESH_INTERVAL);
}, [projects])
}, [projects]);
return errorsStats.unresolvedAndUnviewed > 0 ? (
<div>{<div className={stl.badge} /> }</div>
) : ''
<div>{<div className={stl.badge} />}</div>
) : (
''
);
}
export default connect(state => ({
errorsStats: state.getIn([ 'errors', 'stats' ]),
projects: state.getIn([ 'site', 'list' ]),
}), { fetchNewErrorsCount })(ErrorsBadge)
export default connect((state) => ({
projects: state.getIn(['site', 'list']),
}))(observer(ErrorsBadge));

View file

@ -1,239 +0,0 @@
import { List, Map } from 'immutable';
import { clean as cleanParams } from 'App/api_client';
import ErrorInfo, { RESOLVED, UNRESOLVED, IGNORED, BOOKMARK } from 'Types/errorInfo';
import { fetchListType, fetchType } from './funcTools/crud';
import { createRequestReducer, ROOT_KEY } from './funcTools/request';
import { array, success, failure, createListUpdater, mergeReducers } from './funcTools/tools';
import { reduceThenFetchResource } from './search'
const name = "error";
const idKey = "errorId";
const PER_PAGE = 10;
const DEFAULT_SORT = 'occurrence';
const DEFAULT_ORDER = 'desc';
const EDIT_OPTIONS = `${name}/EDIT_OPTIONS`;
const FETCH_LIST = fetchListType(name);
const FETCH = fetchType(name);
const FETCH_NEW_ERRORS_COUNT = fetchType('errors/FETCH_NEW_ERRORS_COUNT');
const RESOLVE = "errors/RESOLVE";
const UNRESOLVE = "errors/UNRESOLVE";
const IGNORE = "errors/IGNORE";
const MERGE = "errors/MERGE";
const TOGGLE_FAVORITE = "errors/TOGGLE_FAVORITE";
const FETCH_TRACE = "errors/FETCH_TRACE";
const UPDATE_CURRENT_PAGE = "errors/UPDATE_CURRENT_PAGE";
const UPDATE_KEY = `${name}/UPDATE_KEY`;
function chartWrapper(chart = []) {
return chart.map(point => ({ ...point, count: Math.max(point.count, 0) }));
}
const updateItemInList = createListUpdater(idKey);
const updateInstance = (state, instance) => state.getIn([ "instance", idKey ]) === instance[ idKey ]
? state.mergeIn([ "instance" ], instance)
: state;
const initialState = Map({
totalCount: 0,
list: List(),
instance: ErrorInfo(),
instanceTrace: List(),
stats: Map(),
sourcemapUploaded: true,
currentPage: 1,
limit: PER_PAGE,
options: Map({
sort: DEFAULT_SORT,
order: DEFAULT_ORDER,
status: UNRESOLVED,
query: '',
}),
// sort: DEFAULT_SORT,
// order: DEFAULT_ORDER,
});
function reducer(state = initialState, action = {}) {
let updError;
switch (action.type) {
case EDIT_OPTIONS:
return state.mergeIn(["options"], action.instance).set('currentPage', 1);
case success(FETCH):
if (state.get("list").find(e => e.get("errorId") === action.id)) {
return updateItemInList(state, { errorId: action.data.errorId, viewed: true })
.set("instance", ErrorInfo(action.data));
} else {
return state.set("instance", ErrorInfo(action.data));
}
case failure(FETCH):
return state.set("instance", ErrorInfo());
case success(FETCH_TRACE):
return state.set("instanceTrace", List(action.data.trace)).set('sourcemapUploaded', action.data.sourcemapUploaded);
case success(FETCH_LIST):
const { data } = action;
return state
.set("totalCount", data ? data.total : 0)
.set("list", List(data && data.errors).map(ErrorInfo)
.filter(e => e.parentErrorId == null)
.map(e => e.update("chart", chartWrapper)))
case success(RESOLVE):
updError = { errorId: action.id, status: RESOLVED, disabled: true };
return updateItemInList(updateInstance(state, updError), updError);
case success(UNRESOLVE):
updError = { errorId: action.id, status: UNRESOLVED, disabled: true };
return updateItemInList(updateInstance(state, updError), updError);
case success(IGNORE):
updError = { errorId: action.id, status: IGNORED, disabled: true };
return updateItemInList(updateInstance(state, updError), updError);
case success(TOGGLE_FAVORITE):
return state.mergeIn([ "instance" ], { favorite: !state.getIn([ "instance", "favorite" ]) })
case success(MERGE):
const ids = action.ids.slice(1);
return state.update("list", list => list.filter(e => !ids.includes(e.errorId)));
case success(FETCH_NEW_ERRORS_COUNT):
return state.set('stats', action.data);
case UPDATE_KEY:
return state.set(action.key, action.value);
case UPDATE_CURRENT_PAGE:
return state.set('currentPage', action.page);
}
return state;
}
export default mergeReducers(
reducer,
createRequestReducer({
[ ROOT_KEY ]: FETCH_LIST,
fetch: FETCH,
fetchTrace: FETCH_TRACE,
resolve: RESOLVE,
unresolve: UNRESOLVE,
ignore: IGNORE,
merge: MERGE,
toggleFavorite: TOGGLE_FAVORITE,
}),
);
export function fetch(id) {
return {
id,
types: array(FETCH),
call: c => c.get(`/errors/${id}`),
}
}
export function fetchTrace(id) {
return {
id,
types: array(FETCH_TRACE),
call: c => c.get(`/errors/${id}/sourcemaps`),
}
}
export const fetchList = (params = {}, clear = false) => (dispatch, getState) => {
params.page = getState().getIn(['errors', 'currentPage']);
params.limit = PER_PAGE;
const options = getState().getIn(['errors', 'options']).toJS();
if (options.status === BOOKMARK) {
options.bookmarked = true;
options.status = 'all';
}
return dispatch({
types: array(FETCH_LIST),
call: client => client.post('/errors/search', { ...params, ...options }),
clear,
params: cleanParams(params),
});
};
// export function fetchList(params = {}, clear = false) {
// return {
// types: array(FETCH_LIST),
// call: client => client.post('/errors/search', params),
// clear,
// params: cleanParams(params),
// };
// }
export function fetchBookmarks() {
return {
types: array(FETCH_LIST),
call: client => client.post('/errors/search?favorite', {})
}
}
export const resolve = (id) => (dispatch, getState) => {
const list = getState().getIn(['errors', 'list']);
const index = list.findIndex(e => e.get('errorId') === id);
const error = list.get(index);
if (error.get('status') === RESOLVED) return;
return dispatch({
types: array(RESOLVE),
id,
call: client => client.get(`/errors/${ id }/solve`),
})
}
export const unresolve = (id) => (dispatch, getState) => {
const list = getState().getIn(['errors', 'list']);
const index = list.findIndex(e => e.get('errorId') === id);
const error = list.get(index);
if (error.get('status') === UNRESOLVED) return;
return dispatch({
types: array(UNRESOLVE),
id,
call: client => client.get(`/errors/${ id }/unsolve`),
})
}
export const ignore = (id) => (dispatch, getState) => {
const list = getState().getIn(['errors', 'list']);
const index = list.findIndex(e => e.get('errorId') === id);
const error = list.get(index);
if (error.get('status') === IGNORED) return;
return dispatch({
types: array(IGNORE),
id,
call: client => client.get(`/errors/${ id }/ignore`),
})
}
export function merge(ids) {
return {
types: array(MERGE),
ids,
call: client => client.post(`/errors/merge`, { errors: ids }),
}
}
export function toggleFavorite(id) {
return {
types: array(TOGGLE_FAVORITE),
id,
call: client => client.get(`/errors/${ id }/favorite`),
}
}
export function fetchNewErrorsCount(params = {}) {
return {
types: array(FETCH_NEW_ERRORS_COUNT),
call: client => client.get(`/errors/stats`, params),
}
}
export const updateCurrentPage = reduceThenFetchResource((page) => ({
type: UPDATE_CURRENT_PAGE,
page,
}));
export const editOptions = reduceThenFetchResource((instance) => ({
type: EDIT_OPTIONS,
instance
}));

View file

@ -6,7 +6,6 @@ import Event from 'Types/filter/event';
import CustomFilter, { KEYS } from 'Types/filter/customFilter';
import withRequestState, { RequestTypes } from './requestStateCreator';
import { fetchList as fetchSessionList } from './sessions';
import { fetchList as fetchErrorsList } from './errors';
const ERRORS_ROUTE = errorsRoute();
@ -85,7 +84,7 @@ const reducer = (state = initialState, action = {}) => {
case FETCH_LIST.SUCCESS:
const flows = List(action.data).map(SavedFilter)
let _state = state.set('list', flows)
if (!hasFilterOptions) {
const tmp = {}
flows.forEach(i => {
@ -119,8 +118,8 @@ const reducer = (state = initialState, action = {}) => {
case SET_ACTIVE_KEY:
return state.set('activeFilterKey', action.filterKey);
case APPLY:
return action.fromUrl
? state.set('appliedFilter',
return action.fromUrl
? state.set('appliedFilter',
Filter(action.filter)
.set('events', state.getIn([ 'appliedFilter', 'events' ]))
)
@ -148,7 +147,7 @@ const reducer = (state = initialState, action = {}) => {
if (action.index >= 0) // replacing an event
return state.setIn([ 'appliedFilter', 'events', action.index ], event)
else
return state.updateIn([ 'appliedFilter', 'events' ], list => action.single
return state.updateIn([ 'appliedFilter', 'events' ], list => action.single
? List([ event ])
: list.push(event));
case REMOVE_EVENT:
@ -166,7 +165,7 @@ const reducer = (state = initialState, action = {}) => {
return state.setIn([ 'appliedFilter', 'events' ], List())
.setIn([ 'appliedFilter', 'filters' ], List())
.set('searchQuery', '');
case ADD_ATTRIBUTE:
const filter = CustomFilter(action.filter);
@ -174,7 +173,7 @@ const reducer = (state = initialState, action = {}) => {
return state.setIn([ 'appliedFilter', 'filters', action.index], filter);
else
return state.updateIn([ 'appliedFilter', 'filters'], filters => filters.push(filter));
case EDIT_ATTRIBUTE:
return state.setIn([ 'appliedFilter', 'filters', action.index, action.key ], action.value );
case REMOVE_ATTRIBUTE:
@ -209,7 +208,7 @@ const reduceThenFetchResource = actionCreator => (...args) => (dispatch, getStat
// Hello AGILE!
return isRoute(ERRORS_ROUTE, window.location.pathname)
? dispatch(fetchErrorsList(filter))
? null
: dispatch(fetchSessionList(filter));
}
@ -386,7 +385,7 @@ export const edit = instance => {
export const updateValue = (filterType, index, value) => {
return {
type: UPDATE_VALUE,
filterType,
filterType,
index,
value
}

View file

@ -9,7 +9,6 @@ import sources from './sources';
import site from './site';
import customFields from './customField';
import integrations from './integrations';
import errors from './errors';
import funnels from './funnels';
import customMetrics from './customMetrics';
import search from './search';
@ -22,7 +21,6 @@ const rootReducer = combineReducers({
funnelFilters,
site,
customFields,
errors,
funnels,
customMetrics,
search,

View file

@ -14,7 +14,6 @@ import { List, Map } from 'immutable';
import { DURATION_FILTER } from 'App/constants/storageKeys';
import { errors as errorsRoute, isRoute } from 'App/routes';
import { fetchList as fetchErrorsList } from './errors';
import {
editType,
fetchListType,
@ -252,7 +251,7 @@ export const reduceThenFetchResource =
dispatch(updateLatestRequestTime());
return isRoute(ERRORS_ROUTE, window.location.pathname)
? dispatch(fetchErrorsList(filter))
? null
: dispatch(fetchSessionList(filter, forceFetch));
};
@ -470,7 +469,7 @@ export const refreshFilterOptions = () => (dispatch, getState) => {
const currentProject = getState().getIn(['site', 'instance']);
return dispatch({
type: REFRESH_FILTER_OPTIONS,
isMobile: currentProject?.platform === 'ios'
isMobile: currentProject?.platform === 'ios',
});
};

View file

@ -1,55 +1,91 @@
import { makeAutoObservable } from "mobx"
import { errorService } from "App/services"
import Error from "./types/error"
import { makeAutoObservable } from 'mobx';
import apiClient from 'App/api_client';
import { errorService } from 'App/services';
import { ErrorInfo } from './types/error';
export default class ErrorStore {
isLoading: boolean = false
isSaving: boolean = false
instance: ErrorInfo | null = null;
instanceTrace: Record<string, any> = [];
stats: Record<string, any> = {};
sourcemapUploaded = false;
isLoading = false;
errorStates: Record<string, any> = {};
errors: any[] = []
instance: Error | null = null
constructor() {
makeAutoObservable(this);
}
constructor() {
makeAutoObservable(this, {
})
setLoadingState(value: boolean) {
this.isLoading = value;
}
setErrorState(actionKey: string, error: any) {
this.errorStates[actionKey] = error;
}
setInstance(errorData: ErrorInfo | null) {
this.instance = errorData ? new ErrorInfo(errorData) : null;
}
setInstanceTrace(trace: any) {
this.instanceTrace = trace || [];
}
setSourcemapUploaded(value: boolean) {
this.sourcemapUploaded = value;
}
setStats(stats: any) {
this.stats = stats;
}
async fetchError(id: string) {
const actionKey = 'fetchError';
this.setLoadingState(true);
this.setErrorState(actionKey, null);
try {
const response = await errorService.fetchError(id);
const errorData = response.data;
this.setInstance(errorData);
} catch (error) {
this.setInstance(null);
this.setErrorState(actionKey, error);
} finally {
this.setLoadingState(false);
}
}
updateKey(key: string, value: any) {
this[key] = value
}
async fetchErrorTrace(id: string) {
const actionKey = 'fetchErrorTrace';
this.setLoadingState(true);
this.setErrorState(actionKey, null);
fetchErrors(): Promise<any> {
this.isLoading = true
return new Promise((resolve, reject) => {
errorService.all()
.then(response => {
const errors = response.map(e => new Error().fromJSON(e));
this.errors = errors
resolve(errors)
}).catch(error => {
reject(error)
}).finally(() => {
this.isLoading = false
}
)
})
try {
const response = await errorService.fetchErrorTrace(id);
this.setInstanceTrace(response.data.trace);
this.setSourcemapUploaded(response.data.sourcemapUploaded);
} catch (error) {
this.setErrorState(actionKey, error);
} finally {
this.setLoadingState(false);
}
}
fetchError(errorId: string): Promise<any> {
this.isLoading = true
return new Promise((resolve, reject) => {
errorService.one(errorId)
.then(response => {
const error = new Error().fromJSON(response);
this.instance = error
resolve(error)
}).catch(error => {
reject(error)
}).finally(() => {
this.isLoading = false
}
)
})
async fetchNewErrorsCount(params: any) {
const actionKey = 'fetchNewErrorsCount';
this.setLoadingState(true);
this.setErrorState(actionKey, null);
try {
const response = await errorService.fetchNewErrorsCount(params);
this.setStats(response.data);
} catch (error) {
this.setErrorState(actionKey, error);
} finally {
this.setLoadingState(false);
}
}
}
}

View file

@ -1,56 +1,102 @@
import Session from './session';
export default class Error {
sessionId: string = ''
messageId: string = ''
errorId: string = ''
projectId: string = ''
source: string = ''
name: string = ''
message: string = ''
time: string = ''
function: string = '?'
stack0InfoString: string = ''
status: string = ''
chart: any = []
sessions: number = 0
users: number = 0
firstOccurrence: string = ''
lastOccurrence: string = ''
timestamp: string = ''
sessionId: string = '';
messageId: string = '';
errorId: string = '';
projectId: string = '';
source: string = '';
name: string = '';
message: string = '';
time: string = '';
function: string = '?';
stack0InfoString: string = '';
status: string = '';
constructor() {
}
chart: any = [];
sessions: number = 0;
users: number = 0;
firstOccurrence: string = '';
lastOccurrence: string = '';
timestamp: string = '';
fromJSON(json: any) {
this.sessionId = json.sessionId
this.messageId = json.messageId
this.errorId = json.errorId
this.projectId = json.projectId
this.source = json.source
this.name = json.name
this.message = json.message
this.time = json.time
this.function = json.function
this.stack0InfoString = getStck0InfoString(json.stack || [])
this.status = json.status
this.chart = json.chart
this.sessions = json.sessions
this.users = json.users
this.firstOccurrence = json.firstOccurrence
this.lastOccurrence = json.lastOccurrence
this.timestamp = json.timestamp
return this
}
constructor() {}
}
function getStck0InfoString(stack: any) {
const stack0 = stack[0];
if (!stack0) return "";
let s = stack0.function || "";
if (stack0.url) {
s += ` (${stack0.url})`;
const stack0 = stack[0];
if (!stack0) return '';
let s = stack0.function || '';
if (stack0.url) {
s += ` (${stack0.url})`;
}
return s;
}
export interface ErrorInfoData {
errorId?: string;
favorite: boolean;
viewed: boolean;
source: string;
name: string;
message: string;
stack0InfoString: string;
status: string;
parentErrorId?: string;
users: number;
sessions: number;
lastOccurrence: number;
firstOccurrence: number;
chart: any[];
chart24: any[];
chart30: any[];
tags: string[];
customTags: string[];
lastHydratedSession: Session;
disabled: boolean;
}
export class ErrorInfo implements ErrorInfoData {
errorId?: string;
favorite = false;
viewed = false;
source = '';
name = '';
message = '';
stack0InfoString = '';
status = '';
parentErrorId?: string;
users = 0;
sessions = 0;
lastOccurrence = Date.now();
firstOccurrence = Date.now();
chart: any[] = [];
chart24: any[] = [];
chart30: any[] = [];
tags: string[] = [];
customTags: string[] = [];
lastHydratedSession: Session;
disabled = false;
constructor(data?: Partial<ErrorInfoData>) {
if (data) {
Object.assign(this, data);
}
return s;
}
if (data?.lastHydratedSession) {
this.lastHydratedSession = new Session().fromJson(
data.lastHydratedSession
);
} else {
this.lastHydratedSession = new Session();
}
}
static fromJS(data: any): ErrorInfo {
const { stack, lastHydratedSession, ...other } = data;
return new ErrorInfo({
...other,
lastHydratedSession: new Session().fromJson(data.lastHydratedSession),
stack0InfoString: getStck0InfoString(stack || []),
});
}
}

View file

@ -1,17 +1,27 @@
import BaseService from './BaseService';
export default class ErrorService extends BaseService {
all(params: any = {}): Promise<any[]> {
return this.client.post('/errors/search', params)
.then(r => r.json())
.then((response: { data: any; }) => response.data || [])
.catch(e => Promise.reject(e))
}
fetchError = async (id: string) => {
const r = await this.client.get(`/errors/${id}`);
one(id: string): Promise<any> {
return this.client.get(`/errors/${id}`)
.then(r => r.json())
.then((response: { data: any; }) => response.data || {})
.catch(e => Promise.reject(e))
}
}
return await r.json();
};
fetchErrorList = async (params: Record<string, any>) => {
const r = await this.client.post('/errors/search', params);
return await r.json();
};
fetchErrorTrace = async (id: string) => {
const r = await this.client.get(`/errors/${id}/sourcemaps`);
return await r.json();
};
fetchNewErrorsCount = async (params: any) => {
const r = await this.client.get('/errors/stats', params);
return await r.json();
};
}

View file

@ -75,7 +75,7 @@ export const KEYS = {
UTM_SOURCE,
UTM_MEDIUM,
UTM_CAMPAIGN,
DOM_COMPLETE,
LARGEST_CONTENTFUL_PAINT_TIME,
TIME_BETWEEN_EVENTS,
@ -89,7 +89,7 @@ const getOperatorDefault = (type) => {
if (type === KEYS.SLOW_SESSION) return 'true';
if (type === KEYS.CLICK_RAGE) return 'true';
if (type === KEYS.CLICK) return 'on';
return 'is';
}

View file

@ -2,7 +2,7 @@ import { List, Map } from 'immutable';
import Record from 'Types/Record';
import { KEYS } from 'Types/filter/customFilter';
import { TYPES } from 'Types/filter/event';
import {
import {
DATE_RANGE_VALUES,
CUSTOM_RANGE,
getDateRangeFromValue
@ -52,7 +52,7 @@ export default Record({
const js = this.toJS();
js.filters = js.filters.map(filter => {
filter.type = filter.key
delete filter.category
delete filter.icon
delete filter.operatorOptions
@ -156,7 +156,6 @@ export const defaultFilters = [
{ label: 'UTM Source', key: KEYS.UTM_SOURCE, type: KEYS.UTM_SOURCE, filterKey: KEYS.UTM_SOURCE, icon: 'exclamation-circle', isFilter: true },
{ label: 'UTM Medium', key: KEYS.UTM_MEDIUM, type: KEYS.UTM_MEDIUM, filterKey: KEYS.UTM_MEDIUM, icon: 'exclamation-circle', isFilter: true },
{ label: 'UTM Campaign', key: KEYS.UTM_CAMPAIGN, type: KEYS.UTM_CAMPAIGN, filterKey: KEYS.UTM_CAMPAIGN, icon: 'exclamation-circle', isFilter: true },
{ label: 'Fetch Requests', key: KEYS.FETCH, type: KEYS.FETCH, filterKey: KEYS.FETCH, icon: 'fetch', isFilter: false },
{ label: 'GraphQL Queries', key: KEYS.GRAPHQL, type: KEYS.GRAPHQL, filterKey: KEYS.GRAPHQL, icon: 'vendors/graphql', isFilter: false },
{ label: 'Store Actions', key: KEYS.STATEACTION, type: KEYS.STATEACTION, filterKey: KEYS.STATEACTION, icon: 'store', isFilter: false },
@ -205,4 +204,4 @@ export const getEventIcon = (filter) => {
return 'integrations/' + source;
}
return '';
}
}