commit
2bbf9cd8e7
22 changed files with 411 additions and 232 deletions
|
|
@ -13,7 +13,8 @@ import withLocationHandlers from "HOCs/withLocationHandlers";
|
|||
import { fetch as fetchFilterVariables } from 'Duck/sources';
|
||||
import { fetchSources } from 'Duck/customField';
|
||||
import { RehydrateSlidePanel } from './WatchDogs/components';
|
||||
import { setActiveTab, setFunnelPage } from 'Duck/sessions';
|
||||
import { setFunnelPage } from 'Duck/sessions';
|
||||
import { setActiveTab } from 'Duck/search';
|
||||
import SessionsMenu from './SessionsMenu/SessionsMenu';
|
||||
import { LAST_7_DAYS } from 'Types/app/period';
|
||||
import { resetFunnel } from 'Duck/funnels';
|
||||
|
|
@ -51,12 +52,12 @@ const allowedQueryKeys = [
|
|||
variables: state.getIn([ 'customFields', 'list' ]),
|
||||
sources: state.getIn([ 'customFields', 'sources' ]),
|
||||
filterValues: state.get('filterValues'),
|
||||
activeTab: state.getIn([ 'sessions', 'activeTab' ]),
|
||||
favoriteList: state.getIn([ 'sessions', 'favoriteList' ]),
|
||||
currentProjectId: state.getIn([ 'user', 'siteId' ]),
|
||||
sites: state.getIn([ 'site', 'list' ]),
|
||||
watchdogs: state.getIn(['watchdogs', 'list']),
|
||||
activeFlow: state.getIn([ 'filters', 'activeFlow' ]),
|
||||
sessions: state.getIn([ 'sessions', 'list' ]),
|
||||
}), {
|
||||
fetchFavoriteSessionList,
|
||||
applyFilter,
|
||||
|
|
@ -91,7 +92,9 @@ export default class BugFinder extends React.PureComponent {
|
|||
// keys: this.props.sources.filter(({type}) => type === 'logTool').map(({ label, key }) => ({ type: 'ERROR', source: key, label: label, key, icon: 'integrations/' + key, isFilter: false })).toJS()
|
||||
// };
|
||||
// });
|
||||
props.fetchSessions();
|
||||
if (props.sessions.size === 0) {
|
||||
props.fetchSessions();
|
||||
}
|
||||
props.resetFunnel();
|
||||
props.resetFunnelFilters();
|
||||
props.fetchFunnelsList(LAST_7_DAYS)
|
||||
|
|
@ -115,7 +118,6 @@ export default class BugFinder extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { activeFlow, activeTab } = this.props;
|
||||
const { showRehydratePanel } = this.state;
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { Loader, NoContent, Button, LoadMoreButton } from 'UI';
|
||||
import { Loader, NoContent, Button, LoadMoreButton, Pagination } from 'UI';
|
||||
import { applyFilter, addAttribute, addEvent } from 'Duck/filters';
|
||||
import { fetchSessions, addFilterByKeyAndValue } from 'Duck/search';
|
||||
import { fetchSessions, addFilterByKeyAndValue, updateCurrentPage } from 'Duck/search';
|
||||
import SessionItem from 'Shared/SessionItem';
|
||||
import SessionListHeader from './SessionListHeader';
|
||||
import { FilterKey } from 'Types/filter/filterType';
|
||||
|
|
@ -15,17 +15,19 @@ var timeoutId;
|
|||
shouldAutorefresh: state.getIn([ 'filters', 'appliedFilter', 'events' ]).size === 0,
|
||||
savedFilters: state.getIn([ 'filters', 'list' ]),
|
||||
loading: state.getIn([ 'sessions', 'loading' ]),
|
||||
activeTab: state.getIn([ 'sessions', 'activeTab' ]),
|
||||
activeTab: state.getIn([ 'search', 'activeTab' ]),
|
||||
allList: state.getIn([ 'sessions', 'list' ]),
|
||||
total: state.getIn([ 'sessions', 'total' ]),
|
||||
filters: state.getIn([ 'search', 'instance', 'filters' ]),
|
||||
metaList: state.getIn(['customFields', 'list']).map(i => i.key),
|
||||
currentPage: state.getIn([ 'search', 'currentPage' ]),
|
||||
}), {
|
||||
applyFilter,
|
||||
addAttribute,
|
||||
addEvent,
|
||||
fetchSessions,
|
||||
addFilterByKeyAndValue,
|
||||
updateCurrentPage,
|
||||
})
|
||||
export default class SessionList extends React.PureComponent {
|
||||
state = {
|
||||
|
|
@ -76,6 +78,8 @@ export default class SessionList extends React.PureComponent {
|
|||
clearTimeout(timeoutId)
|
||||
}
|
||||
|
||||
|
||||
|
||||
renderActiveTabContent(list) {
|
||||
const {
|
||||
loading,
|
||||
|
|
@ -84,6 +88,8 @@ export default class SessionList extends React.PureComponent {
|
|||
allList,
|
||||
activeTab,
|
||||
metaList,
|
||||
currentPage,
|
||||
total,
|
||||
} = this.props;
|
||||
const _filterKeys = filters.map(i => i.key);
|
||||
const hasUserFilter = _filterKeys.includes(FilterKey.USERID) || _filterKeys.includes(FilterKey.USERANONYMOUSID);
|
||||
|
|
@ -93,28 +99,28 @@ export default class SessionList extends React.PureComponent {
|
|||
return (
|
||||
<NoContent
|
||||
title={this.getNoContentMessage(activeTab)}
|
||||
subtext="Please try changing your search parameters."
|
||||
// subtext="Please try changing your search parameters."
|
||||
icon="exclamation-circle"
|
||||
show={ !loading && list.size === 0}
|
||||
subtext={
|
||||
<div>
|
||||
<div>Please try changing your search parameters.</div>
|
||||
{allList.size > 0 && (
|
||||
<div className="pt-2">
|
||||
However, we found other sessions based on your search parameters.
|
||||
<div>
|
||||
<Button
|
||||
plain
|
||||
onClick={() => onMenuItemClick({ name: 'All', type: 'all' })}
|
||||
>See All</Button>
|
||||
<div>
|
||||
<div>Please try changing your search parameters.</div>
|
||||
{allList.size > 0 && (
|
||||
<div className="pt-2">
|
||||
However, we found other sessions based on your search parameters.
|
||||
<div>
|
||||
<Button
|
||||
plain
|
||||
onClick={() => onMenuItemClick({ name: 'All', type: 'all' })}
|
||||
>See All</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Loader loading={ loading }>
|
||||
{ list.take(displayedCount).map(session => (
|
||||
{ list.map(session => (
|
||||
<SessionItem
|
||||
key={ session.sessionId }
|
||||
session={ session }
|
||||
|
|
@ -124,7 +130,16 @@ export default class SessionList extends React.PureComponent {
|
|||
/>
|
||||
))}
|
||||
</Loader>
|
||||
<LoadMoreButton
|
||||
<div className="w-full flex items-center justify-center py-6">
|
||||
<Pagination
|
||||
page={currentPage}
|
||||
totalPages={Math.ceil(total / PER_PAGE)}
|
||||
onPageChange={(page) => this.props.updateCurrentPage(page)}
|
||||
limit={PER_PAGE}
|
||||
debounceRequest={1000}
|
||||
/>
|
||||
</div>
|
||||
{/* <LoadMoreButton
|
||||
className="mt-12 mb-12"
|
||||
displayedCount={displayedCount}
|
||||
totalCount={list.size}
|
||||
|
|
@ -135,7 +150,7 @@ export default class SessionList extends React.PureComponent {
|
|||
Haven't found the session in the above list? <br/>Try being a bit more specific by setting a specific time frame or simply use different filters
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
/> */}
|
||||
</NoContent>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,5 +64,5 @@ function SessionListHeader({
|
|||
};
|
||||
|
||||
export default connect(state => ({
|
||||
activeTab: state.getIn([ 'sessions', 'activeTab' ]),
|
||||
activeTab: state.getIn([ 'search', 'activeTab' ]),
|
||||
}), { applyFilter })(SessionListHeader);
|
||||
|
|
|
|||
|
|
@ -1,30 +1,20 @@
|
|||
import React, { useEffect } from 'react'
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux';
|
||||
import cn from 'classnames';
|
||||
import { SideMenuitem, SavedSearchList, Progress, Popup, Icon, CircularLoader } from 'UI'
|
||||
import { SideMenuitem, SavedSearchList, Progress, Popup } from 'UI'
|
||||
import stl from './sessionMenu.css';
|
||||
import { fetchWatchdogStatus } from 'Duck/watchdogs';
|
||||
import { setActiveFlow, clearEvents } from 'Duck/filters';
|
||||
import { setActiveTab } from 'Duck/sessions';
|
||||
import { clearEvents } from 'Duck/filters';
|
||||
import { issues_types } from 'Types/session/issue'
|
||||
import { fetchList as fetchSessionList } from 'Duck/sessions';
|
||||
|
||||
function SessionsMenu(props) {
|
||||
const {
|
||||
activeFlow, activeTab, watchdogs = [], keyMap, wdTypeCount,
|
||||
fetchWatchdogStatus, toggleRehydratePanel, filters, sessionsLoading } = props;
|
||||
const { activeTab, keyMap, wdTypeCount, toggleRehydratePanel } = props;
|
||||
|
||||
const onMenuItemClick = (filter) => {
|
||||
props.onMenuItemClick(filter)
|
||||
|
||||
if (activeFlow && activeFlow.type === 'flows') {
|
||||
props.setActiveFlow(null)
|
||||
}
|
||||
}
|
||||
|
||||
// useEffect(() => {
|
||||
// fetchWatchdogStatus()
|
||||
// }, [])
|
||||
|
||||
|
||||
const capturingAll = props.captureRate && props.captureRate.get('captureAll');
|
||||
|
||||
|
|
@ -66,36 +56,13 @@ function SessionsMenu(props) {
|
|||
{ issues_types.filter(item => item.visible).map(item => (
|
||||
<SideMenuitem
|
||||
key={item.key}
|
||||
disabled={!keyMap[item.type] && !wdTypeCount[item.type]}
|
||||
// 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={
|
||||
<div className="flex items-center">
|
||||
<div>Assist</div>
|
||||
{ activeTab.type === 'live' && (
|
||||
<div
|
||||
className="ml-4 h-5 w-6 flex items-center justify-center"
|
||||
onClick={() => !sessionsLoading && props.fetchSessionList(filters.toJS())}
|
||||
>
|
||||
{ sessionsLoading ? <CircularLoader className="ml-1" /> : <Icon name="sync-alt" size="14" />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
iconName="person"
|
||||
active={activeTab.type === 'live'}
|
||||
onClick={() => onMenuItemClick({ name: 'Assist', type: 'live' })}
|
||||
/>
|
||||
|
||||
</div> */}
|
||||
|
||||
<div className={stl.divider} />
|
||||
<div className="my-3">
|
||||
<SideMenuitem
|
||||
|
|
@ -113,13 +80,12 @@ function SessionsMenu(props) {
|
|||
}
|
||||
|
||||
export default connect(state => ({
|
||||
activeTab: state.getIn([ 'sessions', 'activeTab' ]),
|
||||
activeTab: state.getIn([ 'search', 'activeTab' ]),
|
||||
keyMap: state.getIn([ 'sessions', 'keyMap' ]),
|
||||
wdTypeCount: state.getIn([ 'sessions', 'wdTypeCount' ]),
|
||||
activeFlow: state.getIn([ 'filters', 'activeFlow' ]),
|
||||
captureRate: state.getIn(['watchdogs', 'captureRate']),
|
||||
filters: state.getIn([ 'filters', 'appliedFilter' ]),
|
||||
sessionsLoading: state.getIn([ 'sessions', 'fetchLiveListRequest', 'loading' ]),
|
||||
}), {
|
||||
fetchWatchdogStatus, setActiveFlow, clearEvents, setActiveTab, fetchSessionList
|
||||
fetchWatchdogStatus, clearEvents, fetchSessionList
|
||||
})(SessionsMenu);
|
||||
|
|
|
|||
|
|
@ -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 PER_PAGE = 10;
|
||||
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',
|
||||
'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, 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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { fetchLiveList } from 'Duck/sessions';
|
||||
import { connect } from 'react-redux';
|
||||
import { NoContent, Loader, LoadMoreButton } from 'UI';
|
||||
import { NoContent, Loader, LoadMoreButton, Pagination } from 'UI';
|
||||
import { List, Map } from 'immutable';
|
||||
import SessionItem from 'Shared/SessionItem';
|
||||
import withPermissions from 'HOCs/withPermissions'
|
||||
|
|
@ -12,11 +12,11 @@ import { addFilterByKeyAndValue, updateCurrentPage, updateSort } from 'Duck/live
|
|||
import DropdownPlain from 'Shared/DropdownPlain';
|
||||
import SortOrderButton from 'Shared/SortOrderButton';
|
||||
import { TimezoneDropdown } from 'UI';
|
||||
import { capitalize } from 'App/utils';
|
||||
import { capitalize, sliceListPerPage } from 'App/utils';
|
||||
import LiveSessionReloadButton from 'Shared/LiveSessionReloadButton';
|
||||
|
||||
const AUTOREFRESH_INTERVAL = .5 * 60 * 1000
|
||||
const PER_PAGE = 20;
|
||||
const PER_PAGE = 10;
|
||||
|
||||
interface Props {
|
||||
loading: Boolean,
|
||||
|
|
@ -42,9 +42,8 @@ function LiveSessionList(props: Props) {
|
|||
text: capitalize(i), value: i
|
||||
})).toJS();
|
||||
|
||||
const displayedCount = Math.min(currentPage * PER_PAGE, sessions.size);
|
||||
|
||||
const addPage = () => props.updateCurrentPage(props.currentPage + 1)
|
||||
// const displayedCount = Math.min(currentPage * PER_PAGE, sessions.size);
|
||||
// const addPage = () => props.updateCurrentPage(props.currentPage + 1)
|
||||
|
||||
useEffect(() => {
|
||||
if (filters.size === 0) {
|
||||
|
|
@ -135,6 +134,7 @@ function LiveSessionList(props: Props) {
|
|||
<SortOrderButton onChange={(state) => props.updateSort({ order: state })} sortOrder={sort.order} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NoContent
|
||||
title={"No live sessions."}
|
||||
subtext={
|
||||
|
|
@ -147,9 +147,9 @@ function LiveSessionList(props: Props) {
|
|||
show={ !loading && sessions && sessions.size === 0}
|
||||
>
|
||||
<Loader loading={ loading }>
|
||||
{sessions && sessions.sortBy(i => i.metadata[sort.field]).update(list => {
|
||||
{sessions && sliceListPerPage(sessions.sortBy(i => i.metadata[sort.field]).update(list => {
|
||||
return sort.order === 'desc' ? list.reverse() : list;
|
||||
}).take(displayedCount).map(session => (
|
||||
}), currentPage - 1).map(session => (
|
||||
<SessionItem
|
||||
key={ session.sessionId }
|
||||
session={ session }
|
||||
|
|
@ -160,12 +160,14 @@ function LiveSessionList(props: Props) {
|
|||
/>
|
||||
))}
|
||||
|
||||
<LoadMoreButton
|
||||
className="my-6"
|
||||
displayedCount={displayedCount}
|
||||
totalCount={sessions.size}
|
||||
onClick={addPage}
|
||||
<div className="w-full flex items-center justify-center py-6">
|
||||
<Pagination
|
||||
page={currentPage}
|
||||
totalPages={Math.ceil(sessions.size / PER_PAGE)}
|
||||
onPageChange={(page) => props.updateCurrentPage(page)}
|
||||
limit={PER_PAGE}
|
||||
/>
|
||||
</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} /> }
|
||||
/>
|
||||
|
|
|
|||
77
frontend/app/components/ui/Pagination/Pagination.tsx
Normal file
77
frontend/app/components/ui/Pagination/Pagination.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import React from 'react'
|
||||
import { Icon } from 'UI'
|
||||
import cn from 'classnames'
|
||||
import { debounce } from 'App/utils';
|
||||
import { Tooltip } from 'react-tippy';
|
||||
interface Props {
|
||||
page: number
|
||||
totalPages: number
|
||||
onPageChange: (page: number) => void
|
||||
limit?: number
|
||||
debounceRequest?: number
|
||||
}
|
||||
export default function Pagination(props: Props) {
|
||||
const { page, totalPages, onPageChange, limit = 5, debounceRequest = 0 } = props;
|
||||
const [currentPage, setCurrentPage] = React.useState(page);
|
||||
React.useMemo(
|
||||
() => setCurrentPage(page),
|
||||
[page],
|
||||
);
|
||||
|
||||
const debounceChange = React.useCallback(debounce(onPageChange, debounceRequest), []);
|
||||
|
||||
const changePage = (page: number) => {
|
||||
if (page > 0 && page <= totalPages) {
|
||||
setCurrentPage(page);
|
||||
debounceChange(page);
|
||||
}
|
||||
}
|
||||
|
||||
const isFirstPage = currentPage === 1;
|
||||
const isLastPage = currentPage === totalPages;
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Tooltip
|
||||
arrow
|
||||
sticky
|
||||
title="Previous Page"
|
||||
trigger="mouseenter"
|
||||
hideOnClick={true}
|
||||
>
|
||||
<button
|
||||
className={cn("py-2 px-3", { "opacity-50 cursor-default": isFirstPage })}
|
||||
disabled={isFirstPage}
|
||||
onClick={() => changePage(currentPage - 1)}
|
||||
>
|
||||
<Icon name="chevron-left" size="18" color={isFirstPage ? 'gray-medium' : 'teal'} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span className="mr-2 color-gray-medium">Page</span>
|
||||
<input
|
||||
type="number"
|
||||
className={cn("py-1 px-2 bg-white border border-gray-light rounded w-16", { "opacity-50 cursor-default": totalPages === 1 })}
|
||||
value={currentPage}
|
||||
min={1}
|
||||
max={totalPages}
|
||||
onChange={(e) => changePage(parseInt(e.target.value))}
|
||||
/>
|
||||
<span className="mx-3 color-gray-medium">of</span>
|
||||
<span >{totalPages}</span>
|
||||
<Tooltip
|
||||
arrow
|
||||
sticky
|
||||
title="Next Page"
|
||||
trigger="mouseenter"
|
||||
hideOnClick={true}
|
||||
>
|
||||
<button
|
||||
className={cn("py-2 px-3", { "opacity-50 cursor-default": isLastPage })}
|
||||
disabled={isLastPage}
|
||||
onClick={() => changePage(currentPage + 1)}
|
||||
>
|
||||
<Icon name="chevron-right" size="18" color={isLastPage ? 'gray-medium' : 'teal'} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
1
frontend/app/components/ui/Pagination/index.ts
Normal file
1
frontend/app/components/ui/Pagination/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from './Pagination';
|
||||
|
|
@ -55,5 +55,6 @@ export { default as HighlightCode } from './HighlightCode';
|
|||
export { default as NoPermission } from './NoPermission';
|
||||
export { default as NoSessionPermission } from './NoSessionPermission';
|
||||
export { default as HelpText } from './HelpText';
|
||||
export { default as Pagination } from './Pagination';
|
||||
|
||||
export { Input, Modal, Form, Message, Card } from 'semantic-ui-react';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { FilterKey } from 'Types/filter/filterType';
|
||||
import { FilterKey, IssueType } from 'Types/filter/filterType';
|
||||
|
||||
export const options = [
|
||||
{ key: 'on', text: 'on', value: 'on' },
|
||||
|
|
@ -93,18 +93,18 @@ export const methodOptions = [
|
|||
]
|
||||
|
||||
export const issueOptions = [
|
||||
{ text: 'Click Rage', value: 'click_rage' },
|
||||
{ text: 'Dead Click', value: 'dead_click' },
|
||||
{ text: 'Excessive Scrolling', value: 'excessive_scrolling' },
|
||||
{ text: 'Bad Request', value: 'bad_request' },
|
||||
{ text: 'Missing Resource', value: 'missing_resource' },
|
||||
{ text: 'Memory', value: 'memory' },
|
||||
{ text: 'CPU', value: 'cpu' },
|
||||
{ text: 'Slow Resource', value: 'slow_resource' },
|
||||
{ text: 'Slow Page Load', value: 'slow_page_load' },
|
||||
{ text: 'Crash', value: 'crash' },
|
||||
{ text: 'Custom', value: 'custom' },
|
||||
{ text: 'JS Exception', value: 'js_exception' },
|
||||
{ text: 'Click Rage', value: IssueType.CLICK_RAGE },
|
||||
{ text: 'Dead Click', value: IssueType.DEAD_CLICK },
|
||||
{ text: 'Excessive Scrolling', value: IssueType.EXCESSIVE_SCROLLING },
|
||||
{ text: 'Bad Request', value: IssueType.BAD_REQUEST },
|
||||
{ text: 'Missing Resource', value: IssueType.MISSING_RESOURCE },
|
||||
{ text: 'Memory', value: IssueType.MEMORY },
|
||||
{ text: 'CPU', value: IssueType.CPU },
|
||||
{ text: 'Slow Resource', value: IssueType.SLOW_RESOURCE },
|
||||
{ text: 'Slow Page Load', value: IssueType.SLOW_PAGE_LOAD },
|
||||
{ text: 'Crash', value: IssueType.CRASH },
|
||||
{ text: 'Custom', value: IssueType.CUSTOM },
|
||||
{ text: 'Error', value: IssueType.JS_EXCEPTION },
|
||||
]
|
||||
|
||||
export default {
|
||||
|
|
|
|||
|
|
@ -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 = 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');
|
||||
|
|
@ -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
|
||||
}));
|
||||
|
|
@ -7,7 +7,7 @@ import SavedFilter from 'Types/filter/savedFilter';
|
|||
import { errors as errorsRoute, isRoute } from "App/routes";
|
||||
import { fetchList as fetchSessionList } from './sessions';
|
||||
import { fetchList as fetchErrorsList } from './errors';
|
||||
import { FilterCategory, FilterKey } from 'Types/filter/filterType';
|
||||
import { FilterCategory, FilterKey, IssueType } from 'Types/filter/filterType';
|
||||
import { filtersMap, liveFiltersMap, generateFilterOptions, generateLiveFilterOptions } from 'Types/filter/newFilter';
|
||||
|
||||
const ERRORS_ROUTE = errorsRoute();
|
||||
|
|
@ -28,6 +28,8 @@ const CLEAR_SEARCH = `${name}/CLEAR_SEARCH`;
|
|||
const UPDATE = `${name}/UPDATE`;
|
||||
const APPLY = `${name}/APPLY`;
|
||||
const SET_ALERT_METRIC_ID = `${name}/SET_ALERT_METRIC_ID`;
|
||||
const UPDATE_CURRENT_PAGE = `${name}/UPDATE_CURRENT_PAGE`;
|
||||
const SET_ACTIVE_TAB = `${name}/SET_ACTIVE_TAB`;
|
||||
|
||||
const REFRESH_FILTER_OPTIONS = 'filters/REFRESH_FILTER_OPTIONS';
|
||||
|
||||
|
|
@ -49,6 +51,8 @@ const initialState = Map({
|
|||
instance: new Filter({ filters: [] }),
|
||||
savedSearch: new SavedFilter({}),
|
||||
filterSearchList: {},
|
||||
currentPage: 1,
|
||||
activeTab: {name: 'All', type: 'all' },
|
||||
});
|
||||
|
||||
// Metric - Series - [] - filters
|
||||
|
|
@ -62,7 +66,7 @@ function reducer(state = initialState, action = {}) {
|
|||
case APPLY:
|
||||
return action.fromUrl
|
||||
? state.set('instance', Filter(action.filter))
|
||||
: state.mergeIn(['instance'], action.filter);
|
||||
: state.mergeIn(['instance'], action.filter).set('currentPage', 1);
|
||||
case success(FETCH):
|
||||
return state.set("instance", action.data);
|
||||
case success(FETCH_LIST):
|
||||
|
|
@ -83,6 +87,10 @@ function reducer(state = initialState, action = {}) {
|
|||
return state.set('savedSearch', action.filter);
|
||||
case EDIT_SAVED_SEARCH:
|
||||
return state.mergeIn([ 'savedSearch' ], action.instance);
|
||||
case UPDATE_CURRENT_PAGE:
|
||||
return state.set('currentPage', action.page);
|
||||
case SET_ACTIVE_TAB:
|
||||
return state.set('activeTab', action.tab).set('currentPage', 1);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
|
@ -118,10 +126,24 @@ 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();
|
||||
|
||||
const activeTab = getState().getIn([ 'search', 'activeTab']);
|
||||
if (activeTab.type !== 'all' && activeTab.type !== 'bookmark') {
|
||||
const tmpFilter = filtersMap[FilterKey.ISSUE];
|
||||
tmpFilter.value = [activeTab.type]
|
||||
filter.filters = filter.filters.concat(tmpFilter)
|
||||
}
|
||||
|
||||
if (activeTab.type === 'bookmark') {
|
||||
filter.bookmarked = true
|
||||
}
|
||||
|
||||
filter.filters = filter.filters.map(filterMap);
|
||||
filter.limit = 10;
|
||||
filter.page = getState().getIn([ 'search', 'currentPage']);
|
||||
|
||||
return isRoute(ERRORS_ROUTE, window.location.pathname)
|
||||
? dispatch(fetchErrorsList(filter))
|
||||
|
|
@ -133,6 +155,11 @@ export const edit = reduceThenFetchResource((instance) => ({
|
|||
instance,
|
||||
}));
|
||||
|
||||
export const setActiveTab = reduceThenFetchResource((tab) => ({
|
||||
type: SET_ACTIVE_TAB,
|
||||
tab
|
||||
}));
|
||||
|
||||
export const remove = (id) => (dispatch, getState) => {
|
||||
return dispatch({
|
||||
types: REMOVE.array,
|
||||
|
|
@ -152,6 +179,11 @@ export const applyFilter = reduceThenFetchResource((filter, fromUrl=false) => ({
|
|||
fromUrl,
|
||||
}));
|
||||
|
||||
export const updateCurrentPage = reduceThenFetchResource((page) => ({
|
||||
type: UPDATE_CURRENT_PAGE,
|
||||
page,
|
||||
}));
|
||||
|
||||
export const applySavedSearch = (filter) => (dispatch, getState) => {
|
||||
dispatch(edit({ filters: filter ? filter.filter.filters : [] }));
|
||||
return dispatch({
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
.color-white { color: $white }
|
||||
.color-borderColor { color: $borderColor }
|
||||
|
||||
/* color */
|
||||
/* hover color */
|
||||
.hover-main:hover { color: $main }
|
||||
.hover-gray-light-shade:hover { color: $gray-light-shade }
|
||||
.hover-gray-lightest:hover { color: $gray-lightest }
|
||||
|
|
@ -92,3 +92,33 @@
|
|||
.hover-pink:hover { color: $pink }
|
||||
.hover-white:hover { color: $white }
|
||||
.hover-borderColor:hover { color: $borderColor }
|
||||
|
||||
.border-main { border-color: $main }
|
||||
.border-gray-light-shade { border-color: $gray-light-shade }
|
||||
.border-gray-lightest { border-color: $gray-lightest }
|
||||
.border-gray-light { border-color: $gray-light }
|
||||
.border-gray-medium { border-color: $gray-medium }
|
||||
.border-gray-dark { border-color: $gray-dark }
|
||||
.border-gray-darkest { border-color: $gray-darkest }
|
||||
.border-teal { border-color: $teal }
|
||||
.border-teal-dark { border-color: $teal-dark }
|
||||
.border-teal-light { border-color: $teal-light }
|
||||
.border-tealx { border-color: $tealx }
|
||||
.border-tealx-light { border-color: $tealx-light }
|
||||
.border-tealx-light-border { border-color: $tealx-light-border }
|
||||
.border-orange { border-color: $orange }
|
||||
.border-yellow { border-color: $yellow }
|
||||
.border-yellow2 { border-color: $yellow2 }
|
||||
.border-orange-dark { border-color: $orange-dark }
|
||||
.border-green { border-color: $green }
|
||||
.border-green2 { border-color: $green2 }
|
||||
.border-green-dark { border-color: $green-dark }
|
||||
.border-red { border-color: $red }
|
||||
.border-red2 { border-color: $red2 }
|
||||
.border-blue { border-color: $blue }
|
||||
.border-blue2 { border-color: $blue2 }
|
||||
.border-active-blue { border-color: $active-blue }
|
||||
.border-active-blue-border { border-color: $active-blue-border }
|
||||
.border-pink { border-color: $pink }
|
||||
.border-white { border-color: $white }
|
||||
.border-borderColor { border-color: $borderColor }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-double-left" viewBox="0 0 16 16">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-chevron-double-left" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M8.354 1.646a.5.5 0 0 1 0 .708L2.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"/>
|
||||
<path fill-rule="evenodd" d="M12.354 1.646a.5.5 0 0 1 0 .708L6.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 447 B After Width: | Height: | Size: 424 B |
3
frontend/app/svg/icons/chevron-left.svg
Normal file
3
frontend/app/svg/icons/chevron-left.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-chevron-left" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 246 B |
3
frontend/app/svg/icons/chevron-right.svg
Normal file
3
frontend/app/svg/icons/chevron-right.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-chevron-right" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 248 B |
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,21 @@ export enum FilterCategory {
|
|||
PERFORMANCE = "Performance",
|
||||
};
|
||||
|
||||
export enum IssueType {
|
||||
CLICK_RAGE = "click_rage",
|
||||
DEAD_CLICK = "dead_click",
|
||||
EXCESSIVE_SCROLLING = "excessive_scrolling",
|
||||
BAD_REQUEST = "bad_request",
|
||||
MISSING_RESOURCE = "missing_resource",
|
||||
MEMORY = "memory",
|
||||
CPU = "cpu",
|
||||
SLOW_RESOURCE = "slow_resource",
|
||||
SLOW_PAGE_LOAD = "slow_page_load",
|
||||
CRASH = "crash",
|
||||
CUSTOM = "custom",
|
||||
JS_EXCEPTION = "js_exception",
|
||||
}
|
||||
|
||||
export enum FilterType {
|
||||
STRING = "STRING",
|
||||
ISSUE = "ISSUE",
|
||||
|
|
|
|||
|
|
@ -232,4 +232,10 @@ export const isGreaterOrEqualVersion = (version, compareTo) => {
|
|||
const [major, minor, patch] = version.split("-")[0].split('.');
|
||||
const [majorC, minorC, patchC] = compareTo.split("-")[0].split('.');
|
||||
return (major > majorC) || (major === majorC && minor > minorC) || (major === majorC && minor === minorC && patch >= patchC);
|
||||
}
|
||||
|
||||
export const sliceListPerPage = (list, page, perPage = 10) => {
|
||||
const start = page * perPage;
|
||||
const end = start + perPage;
|
||||
return list.slice(start, end);
|
||||
}
|
||||
|
|
@ -12,7 +12,9 @@ ${ colors.map(color => `.fill-${ color } { fill: $${ color } }`).join('\n') }
|
|||
/* color */
|
||||
${ colors.map(color => `.color-${ color } { color: $${ color } }`).join('\n') }
|
||||
|
||||
/* color */
|
||||
/* hover color */
|
||||
${ colors.map(color => `.hover-${ color }:hover { color: $${ color } }`).join('\n') }
|
||||
|
||||
${ colors.map(color => `.border-${ color } { border-color: $${ color } }`).join('\n') }
|
||||
`)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue