feat(ui) - errors pagination
This commit is contained in:
parent
4807d980f9
commit
7168e660cc
7 changed files with 156 additions and 133 deletions
|
|
@ -1,23 +1,18 @@
|
|||
import { connect } from 'react-redux';
|
||||
import withSiteIdRouter from 'HOCs/withSiteIdRouter';
|
||||
import withPermissions from 'HOCs/withPermissions'
|
||||
import { UNRESOLVED, RESOLVED, IGNORED } from "Types/errorInfo";
|
||||
import { getRE } from 'App/utils';
|
||||
import { fetchBookmarks } from "Duck/errors";
|
||||
import { UNRESOLVED, RESOLVED, IGNORED, BOOKMARK } from "Types/errorInfo";
|
||||
import { fetchBookmarks, editOptions } from "Duck/errors";
|
||||
import { applyFilter } from 'Duck/filters';
|
||||
import { fetchList as fetchSlackList } from 'Duck/integrations/slack';
|
||||
import { errors as errorsRoute, isRoute } from "App/routes";
|
||||
import EventFilter from 'Components/BugFinder/EventFilter';
|
||||
import DateRange from 'Components/BugFinder/DateRange';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
|
||||
import { SavedSearchList } from 'UI';
|
||||
|
||||
import List from './List/List';
|
||||
import ErrorInfo from './Error/ErrorInfo';
|
||||
import Header from './Header';
|
||||
import SideMenuSection from './SideMenu/SideMenuSection';
|
||||
import SideMenuHeader from './SideMenu/SideMenuHeader';
|
||||
import SideMenuDividedItem from './SideMenu/SideMenuDividedItem';
|
||||
|
||||
const ERRORS_ROUTE = errorsRoute();
|
||||
|
|
@ -39,44 +34,26 @@ function getStatusLabel(status) {
|
|||
@withSiteIdRouter
|
||||
@connect(state => ({
|
||||
list: state.getIn([ "errors", "list" ]),
|
||||
status: state.getIn([ "errors", "options", "status" ]),
|
||||
}), {
|
||||
fetchBookmarks,
|
||||
applyFilter,
|
||||
fetchSlackList,
|
||||
editOptions,
|
||||
})
|
||||
@withPageTitle("Errors - OpenReplay")
|
||||
export default class Errors extends React.PureComponent {
|
||||
state = {
|
||||
status: UNRESOLVED,
|
||||
bookmarksActive: false,
|
||||
currentList: this.props.list.filter(e => e.status === UNRESOLVED),
|
||||
filter: '',
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
filter: '',
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchSlackList(); // Delete after implementing cache
|
||||
}
|
||||
|
||||
onFilterChange = ({ target: { value } }) => this.setState({ filter: value })
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { bookmarksActive, status, filter } = this.state;
|
||||
const { list } = this.props;
|
||||
if (prevProps.list !== list
|
||||
|| prevState.status !== status
|
||||
|| prevState.bookmarksActive !== bookmarksActive
|
||||
|| prevState.filter !== filter) {
|
||||
const unfiltered = bookmarksActive
|
||||
? list
|
||||
: list.filter(e => e.status === status);
|
||||
const filterRE = getRE(filter);
|
||||
this.setState({
|
||||
currentList: unfiltered
|
||||
.filter(e => filterRE.test(e.name) || filterRE.test(e.message)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ensureErrorsPage() {
|
||||
const { history } = this.props;
|
||||
if (!isRoute(ERRORS_ROUTE, history.location.pathname)) {
|
||||
|
|
@ -85,22 +62,11 @@ export default class Errors extends React.PureComponent {
|
|||
}
|
||||
|
||||
onStatusItemClick = ({ key }) => {
|
||||
if (this.state.bookmarksActive) {
|
||||
this.props.applyFilter();
|
||||
}
|
||||
this.setState({
|
||||
status: key,
|
||||
bookmarksActive: false,
|
||||
});
|
||||
this.ensureErrorsPage();
|
||||
this.props.editOptions({ status: key });
|
||||
}
|
||||
|
||||
onBookmarksClick = () => {
|
||||
this.setState({
|
||||
bookmarksActive: true,
|
||||
});
|
||||
this.props.fetchBookmarks();
|
||||
this.ensureErrorsPage();
|
||||
this.props.editOptions({ status: BOOKMARK });
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -110,8 +76,9 @@ export default class Errors extends React.PureComponent {
|
|||
match: {
|
||||
params: { errorId }
|
||||
},
|
||||
status,
|
||||
list,
|
||||
} = this.props;
|
||||
const { status, bookmarksActive, currentList } = this.state;
|
||||
|
||||
return (
|
||||
<div className="page-margin container-90" >
|
||||
|
|
@ -137,14 +104,14 @@ export default class Errors extends React.PureComponent {
|
|||
icon: "ban",
|
||||
label: getStatusLabel(IGNORED),
|
||||
active: status === IGNORED,
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<SideMenuDividedItem
|
||||
className="mt-3 mb-4"
|
||||
iconName="star"
|
||||
title="Bookmarks"
|
||||
active={ bookmarksActive }
|
||||
active={ status === BOOKMARK }
|
||||
onClick={ this.onBookmarksClick }
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -154,8 +121,8 @@ export default class Errors extends React.PureComponent {
|
|||
<>
|
||||
<div className="mb-5 flex">
|
||||
<Header
|
||||
text={ bookmarksActive ? "Bookmarks" : getStatusLabel(status) }
|
||||
count={ currentList.size }
|
||||
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>
|
||||
|
|
@ -164,12 +131,11 @@ export default class Errors extends React.PureComponent {
|
|||
</div>
|
||||
<List
|
||||
status={ status }
|
||||
list={ currentList }
|
||||
onFilterChange={this.onFilterChange}
|
||||
list={ list }
|
||||
/>
|
||||
</>
|
||||
:
|
||||
<ErrorInfo errorId={ errorId } list={ currentList } />
|
||||
<ErrorInfo errorId={ errorId } list={ list } />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,53 +1,62 @@
|
|||
import cn from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import { Set, List as ImmutableList } from "immutable";
|
||||
import { NoContent, Loader, Checkbox, LoadMoreButton, IconButton, Input, DropdownPlain } from 'UI';
|
||||
import { merge, resolve, unresolve, ignore, updateCurrentPage } from "Duck/errors";
|
||||
import { NoContent, Loader, Checkbox, LoadMoreButton, IconButton, Input, DropdownPlain, Pagination } from 'UI';
|
||||
import { merge, resolve, unresolve, ignore, updateCurrentPage, editOptions } from "Duck/errors";
|
||||
import { applyFilter } from 'Duck/filters';
|
||||
import { IGNORED, RESOLVED, UNRESOLVED } from 'Types/errorInfo';
|
||||
import SortDropdown from 'Components/BugFinder/Filters/SortDropdown';
|
||||
import Divider from 'Components/Errors/ui/Divider';
|
||||
import ListItem from './ListItem/ListItem';
|
||||
import { debounce } from 'App/utils';
|
||||
|
||||
const PER_PAGE = 5;
|
||||
const DEFAULT_SORT = 'lastOccurrence';
|
||||
const DEFAULT_ORDER = 'desc';
|
||||
const sortOptionsMap = {
|
||||
'lastOccurrence-desc': 'Last Occurrence',
|
||||
'firstOccurrence-desc': 'First Occurrence',
|
||||
'sessions-asc': 'Sessions Ascending',
|
||||
'sessions-desc': 'Sessions Descending',
|
||||
'users-asc': 'Users Ascending',
|
||||
'users-desc': 'Users Descending',
|
||||
'firstOccurrence-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, text ]) => ({ value, text }));
|
||||
|
||||
|
||||
@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"]),
|
||||
currentPage: state.getIn(["errors", "currentPage"]),
|
||||
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,
|
||||
updateCurrentPage,
|
||||
editOptions,
|
||||
})
|
||||
export default class List extends React.PureComponent {
|
||||
state = {
|
||||
checkedAll: false,
|
||||
checkedIds: Set(),
|
||||
sort: {}
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
checkedAll: false,
|
||||
checkedIds: Set(),
|
||||
query: props.query,
|
||||
}
|
||||
this.debounceFetch = debounce(this.props.editOptions, 1000);
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
this.props.applyFilter({ sort: DEFAULT_SORT, order: DEFAULT_ORDER, events: ImmutableList(), filters: ImmutableList() });
|
||||
if (this.props.list.size === 0) {
|
||||
this.props.applyFilter({ });
|
||||
}
|
||||
}
|
||||
|
||||
check = ({ errorId }) => {
|
||||
|
|
@ -111,8 +120,14 @@ export default class List extends React.PureComponent {
|
|||
|
||||
writeOption = (e, { name, value }) => {
|
||||
const [ sort, order ] = value.split('-');
|
||||
const sign = order === 'desc' ? -1 : 1;
|
||||
this.setState({ sort: { sort, order }})
|
||||
if (name === 'sort') {
|
||||
this.props.editOptions({ sort, order });
|
||||
}
|
||||
}
|
||||
|
||||
onQueryChange = (e, { value }) => {
|
||||
this.setState({ query: value });
|
||||
this.debounceFetch({ query: value });
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
@ -123,19 +138,18 @@ export default class List extends React.PureComponent {
|
|||
ignoreLoading,
|
||||
resolveToggleLoading,
|
||||
mergeLoading,
|
||||
onFilterChange,
|
||||
currentPage,
|
||||
currentPage,
|
||||
total,
|
||||
sort,
|
||||
order,
|
||||
} = this.props;
|
||||
const {
|
||||
checkedAll,
|
||||
checkedIds,
|
||||
sort
|
||||
query,
|
||||
} = this.state;
|
||||
const someLoading = loading || ignoreLoading || resolveToggleLoading || mergeLoading;
|
||||
const currentCheckedIds = this.currentCheckedIds();
|
||||
const displayedCount = Math.min(currentPage * PER_PAGE, list.size);
|
||||
let _list = sort.sort ? list.sortBy(i => i[sort.sort]) : list;
|
||||
_list = sort.order === 'desc' ? _list.reverse() : _list;
|
||||
|
||||
return (
|
||||
<div className="bg-white p-5 border-radius-3 thin-gray-border">
|
||||
|
|
@ -182,33 +196,35 @@ export default class List extends React.PureComponent {
|
|||
}
|
||||
</div>
|
||||
<div className="flex items-center ml-6">
|
||||
<span className="mr-2 color-gray-medium">Sort By</span>
|
||||
<span className="mr-2 color-gray-medium">Sort By</span>
|
||||
<DropdownPlain
|
||||
name="type"
|
||||
options={ sortOptions }
|
||||
onChange={ this.writeOption }
|
||||
/>
|
||||
<Input
|
||||
defaultValue={ `${sort}-${order}` }
|
||||
name="sort"
|
||||
options={ sortOptions }
|
||||
onChange={ this.writeOption }
|
||||
/>
|
||||
<Input
|
||||
style={{ width: '350px'}}
|
||||
className="input-small ml-3"
|
||||
placeholder="Filter by Name or Message"
|
||||
icon="search"
|
||||
iconPosition="left"
|
||||
name="filter"
|
||||
onChange={ onFilterChange }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Divider />
|
||||
<NoContent
|
||||
title="No Errors Found!"
|
||||
subtext="Please try to change your search parameters."
|
||||
icon="exclamation-circle"
|
||||
show={ !loading && list.size === 0}
|
||||
>
|
||||
<Loader loading={ loading }>
|
||||
{ _list.take(displayedCount).map(e =>
|
||||
<>
|
||||
className="input-small ml-3"
|
||||
placeholder="Filter by Name or Message"
|
||||
icon="search"
|
||||
iconPosition="left"
|
||||
name="filter"
|
||||
onChange={ this.onQueryChange }
|
||||
value={query}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Divider />
|
||||
<NoContent
|
||||
title="No Errors Found!"
|
||||
subtext="Please try to change your search parameters."
|
||||
icon="exclamation-circle"
|
||||
show={ !loading && list.size === 0}
|
||||
>
|
||||
<Loader loading={ loading }>
|
||||
{ list.map(e =>
|
||||
<div key={e.errorId}>
|
||||
<ListItem
|
||||
disabled={someLoading}
|
||||
key={e.errorId}
|
||||
|
|
@ -217,16 +233,19 @@ export default class List extends React.PureComponent {
|
|||
onCheck={ this.check }
|
||||
/>
|
||||
<Divider/>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
<LoadMoreButton
|
||||
className="mt-3"
|
||||
displayedCount={displayedCount}
|
||||
totalCount={list.size}
|
||||
onClick={this.addPage}
|
||||
/>
|
||||
</Loader>
|
||||
</NoContent>
|
||||
<div className="w-full flex items-center justify-center mt-4">
|
||||
<Pagination
|
||||
page={currentPage}
|
||||
totalPages={Math.ceil(total / PER_PAGE)}
|
||||
onPageChange={(page) => this.props.updateCurrentPage(page)}
|
||||
limit={PER_PAGE}
|
||||
debounceRequest={500}
|
||||
/>
|
||||
</div>
|
||||
</Loader>
|
||||
</NoContent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ function DropdownPlain({ name, label, options, onChange, defaultValue, wrapperSt
|
|||
options={ options }
|
||||
onChange={ onChange }
|
||||
defaultValue={ defaultValue || options[ 0 ].value }
|
||||
icon={null}
|
||||
// icon={null}
|
||||
disabled={disabled}
|
||||
icon={ <Icon name="chevron-down" color="gray-dark" size="14" className={stl.dropdownIcon} /> }
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
import { List, Map } from 'immutable';
|
||||
import { clean as cleanParams } from 'App/api_client';
|
||||
import ErrorInfo, { RESOLVED, UNRESOLVED, IGNORED } from 'Types/errorInfo';
|
||||
import ErrorInfo, { RESOLVED, UNRESOLVED, IGNORED, BOOKMARK } from 'Types/errorInfo';
|
||||
import { createFetch, fetchListType, fetchType } from './funcTools/crud';
|
||||
import { createRequestReducer, ROOT_KEY } from './funcTools/request';
|
||||
import { array, request, success, failure, createListUpdater, mergeReducers } from './funcTools/tools';
|
||||
import { reduceThenFetchResource } from './search'
|
||||
|
||||
const name = "error";
|
||||
const idKey = "errorId";
|
||||
const PER_PAGE = 5;
|
||||
const DEFAULT_SORT = 'lastOccurrence';
|
||||
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');
|
||||
|
|
@ -18,6 +23,7 @@ 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) }));
|
||||
|
|
@ -35,13 +41,23 @@ const initialState = Map({
|
|||
instanceTrace: List(),
|
||||
stats: Map(),
|
||||
sourcemapUploaded: true,
|
||||
currentPage: 1,
|
||||
currentPage: 1,
|
||||
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);
|
||||
case success(FETCH):
|
||||
return state.set("instance", ErrorInfo(action.data));
|
||||
case success(FETCH_TRACE):
|
||||
|
|
@ -69,8 +85,10 @@ function reducer(state = initialState, action = {}) {
|
|||
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_CURRENT_PAGE:
|
||||
return state.set('currentPage', action.page);
|
||||
case UPDATE_KEY:
|
||||
return state.set(action.key, action.value);
|
||||
case UPDATE_CURRENT_PAGE:
|
||||
return state.set('currentPage', action.page);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
|
@ -106,14 +124,31 @@ export function fetchTrace(id) {
|
|||
}
|
||||
}
|
||||
|
||||
export function fetchList(params = {}, clear = false) {
|
||||
return {
|
||||
types: array(FETCH_LIST),
|
||||
call: client => client.post('/errors/search', params),
|
||||
clear,
|
||||
params: cleanParams(params),
|
||||
};
|
||||
}
|
||||
export const fetchList = (params = {}, clear = false) => (dispatch, getState) => {
|
||||
params.page = getState().getIn(['errors', 'currentPage']);
|
||||
params.limit = PER_PAGE;
|
||||
|
||||
const options = getState().getIn(['errors', 'options']);
|
||||
if (options.get("status") === BOOKMARK) {
|
||||
options.bookmarked = true;
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -169,9 +204,12 @@ export function fetchNewErrorsCount(params = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
export function updateCurrentPage(page) {
|
||||
return {
|
||||
type: 'errors/UPDATE_CURRENT_PAGE',
|
||||
export const updateCurrentPage = reduceThenFetchResource((page) => ({
|
||||
type: UPDATE_CURRENT_PAGE,
|
||||
page,
|
||||
};
|
||||
}
|
||||
}));
|
||||
|
||||
export const editOptions = reduceThenFetchResource((instance) => ({
|
||||
type: EDIT_OPTIONS,
|
||||
instance
|
||||
}));
|
||||
|
|
@ -126,7 +126,7 @@ export const filterMap = ({category, value, key, operator, sourceOperator, sourc
|
|||
filters: filters ? filters.map(filterMap) : [],
|
||||
});
|
||||
|
||||
const reduceThenFetchResource = actionCreator => (...args) => (dispatch, getState) => {
|
||||
export const reduceThenFetchResource = actionCreator => (...args) => (dispatch, getState) => {
|
||||
dispatch(actionCreator(...args));
|
||||
const filter = getState().getIn([ 'search', 'instance']).toData();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import Session from './session';
|
|||
export const RESOLVED = "resolved";
|
||||
export const UNRESOLVED = "unresolved";
|
||||
export const IGNORED = "ignored";
|
||||
export const BOOKMARK = "bookmark";
|
||||
|
||||
|
||||
function getStck0InfoString(stack) {
|
||||
|
|
|
|||
|
|
@ -237,6 +237,5 @@ export const isGreaterOrEqualVersion = (version, compareTo) => {
|
|||
export const sliceListPerPage = (list, page, perPage = 10) => {
|
||||
const start = page * perPage;
|
||||
const end = start + perPage;
|
||||
console.log(start, end)
|
||||
return list.slice(start, end);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue