change(ui): remove bugfinder components and store
This commit is contained in:
parent
3dc3d736ac
commit
22cdb4f4e7
41 changed files with 4 additions and 1231 deletions
|
|
@ -11,14 +11,12 @@ const withProvider = (story) => (
|
|||
|
||||
// const req = require.context('../app/components/ui', true, /\.stories\.js$/);
|
||||
// const issues = require.context('../app/components/Session/Issues', true, /\.stories\.js$/);
|
||||
// const bugFinder = require.context('../app/components/BugFinder', true, /\.stories\.js$/);
|
||||
|
||||
addDecorator(withProvider);
|
||||
addDecorator(story => <MemoryRouter initialEntries={['/']}>{story()}</MemoryRouter>);
|
||||
|
||||
// function loadStories() {
|
||||
// req.keys().forEach(filename => req(filename));
|
||||
// bugFinder.keys().forEach(filename => bugFinder(filename));
|
||||
// }
|
||||
|
||||
// configure(loadStories, module);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ const LiveSessionPure = lazy(() => import('Components/Session/LiveSession'));
|
|||
const OnboardingPure = lazy(() => import('Components/Onboarding/Onboarding'));
|
||||
const ClientPure = lazy(() => import('Components/Client/Client'));
|
||||
const AssistPure = lazy(() => import('Components/Assist'));
|
||||
const BugFinderPure = lazy(() => import('Components/Overview'));
|
||||
const SessionsOverviewPure = lazy(() => import('Components/Overview'));
|
||||
const DashboardPure = lazy(() => import('Components/Dashboard/NewDashboard'));
|
||||
const ErrorsPure = lazy(() => import('Components/Errors/Errors'));
|
||||
const FunnelDetailsPure = lazy(() => import('Components/Funnels/FunnelDetails'));
|
||||
|
|
@ -37,7 +37,7 @@ const FunnelIssueDetails = lazy(() => import('Components/Funnels/FunnelIssueDeta
|
|||
const FunnelPagePure = lazy(() => import('Components/Funnels/FunnelPage'));
|
||||
const MultiviewPure = lazy(() => import('Components/Session_/Multiview/Multiview.tsx'));
|
||||
|
||||
const BugFinder = withSiteIdUpdater(BugFinderPure);
|
||||
const SessionsOverview = withSiteIdUpdater(SessionsOverviewPure);
|
||||
const Dashboard = withSiteIdUpdater(DashboardPure);
|
||||
const Session = withSiteIdUpdater(SessionPure);
|
||||
const LiveSession = withSiteIdUpdater(LiveSessionPure);
|
||||
|
|
@ -235,7 +235,7 @@ class Router extends React.Component {
|
|||
<Route exact strict path={withSiteId(FUNNEL_PATH, siteIdList)} component={FunnelPage} />
|
||||
<Route exact strict path={withSiteId(FUNNEL_CREATE_PATH, siteIdList)} component={FunnelsDetails} />
|
||||
<Route exact strict path={withSiteId(FUNNEL_ISSUE_PATH, siteIdList)} component={FunnelIssue} />
|
||||
<Route exact strict path={withSiteId(SESSIONS_PATH, siteIdList)} component={BugFinder} />
|
||||
<Route exact strict path={withSiteId(SESSIONS_PATH, siteIdList)} component={SessionsOverview} />
|
||||
<Route exact strict path={withSiteId(SESSION_PATH, siteIdList)} component={Session} />
|
||||
<Route exact strict path={withSiteId(LIVE_SESSION_PATH, siteIdList)} component={LiveSession} />
|
||||
<Route exact strict path={withSiteId(LIVE_SESSION_PATH, siteIdList)} render={(props) => <Session {...props} live />} />
|
||||
|
|
|
|||
|
|
@ -88,7 +88,6 @@ export default class APIClient {
|
|||
if (
|
||||
path !== '/targets_temp' &&
|
||||
!path.includes('/metadata/session_search') &&
|
||||
!path.includes('/watchdogs/rules') &&
|
||||
!path.includes('/assist/credentials') &&
|
||||
!!this.siteId &&
|
||||
siteIdRequiredPaths.some(sidPath => path.startsWith(sidPath))
|
||||
|
|
|
|||
|
|
@ -1,199 +0,0 @@
|
|||
import React from 'react';
|
||||
import APIClient from 'App/api_client';
|
||||
import cn from 'classnames';
|
||||
import { Input, Icon } from 'UI';
|
||||
import { debounce } from 'App/utils';
|
||||
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
|
||||
import EventSearchInput from 'Shared/EventSearchInput';
|
||||
import stl from './autoComplete.module.css';
|
||||
import FilterItem from '../CustomFilters/FilterItem';
|
||||
|
||||
const TYPE_TO_SEARCH_MSG = "Start typing to search...";
|
||||
const NO_RESULTS_MSG = "No results found.";
|
||||
const SOME_ERROR_MSG = "Some error occured.";
|
||||
const defaultValueToText = value => value;
|
||||
const defaultOptionMapping = (values, valueToText) => values.map(value => ({ text: valueToText(value), value }));
|
||||
|
||||
const hiddenStyle = {
|
||||
whiteSpace: 'pre-wrap',
|
||||
opacity: 0, position: 'fixed', left: '-3000px'
|
||||
};
|
||||
|
||||
let pasted = false;
|
||||
let changed = false;
|
||||
|
||||
class AutoComplete extends React.PureComponent {
|
||||
static defaultProps = {
|
||||
method: 'GET',
|
||||
params: {},
|
||||
}
|
||||
|
||||
state = {
|
||||
values: [],
|
||||
noResultsMessage: TYPE_TO_SEARCH_MSG,
|
||||
ddOpen: false,
|
||||
query: this.props.value,
|
||||
loading: false,
|
||||
error: false
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (this.props.value !== newProps.value) {
|
||||
this.setState({ query: newProps.value});
|
||||
}
|
||||
}
|
||||
|
||||
onClickOutside = () => {
|
||||
this.setState({ ddOpen: false });
|
||||
}
|
||||
|
||||
requestValues = (q) => {
|
||||
const { params, endpoint, method } = this.props;
|
||||
this.setState({
|
||||
loading: true,
|
||||
error: false,
|
||||
});
|
||||
return new APIClient()[ method.toLowerCase() ](endpoint, { ...params, q })
|
||||
.then(response => response.json())
|
||||
.then(({ errors, data }) => {
|
||||
if (errors) {
|
||||
this.setError();
|
||||
} else {
|
||||
this.setState({
|
||||
ddOpen: true,
|
||||
values: data,
|
||||
loading: false,
|
||||
noResultsMessage: NO_RESULTS_MSG,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(this.setError);
|
||||
}
|
||||
|
||||
debouncedRequestValues = debounce(this.requestValues, 1000)
|
||||
|
||||
setError = () => this.setState({
|
||||
loading: false,
|
||||
error: true,
|
||||
noResultsMessage: SOME_ERROR_MSG,
|
||||
})
|
||||
|
||||
|
||||
onInputChange = ({ target: { value } }) => {
|
||||
changed = true;
|
||||
this.setState({ query: value, updated: true })
|
||||
const _value = value ? value.trim() : undefined;
|
||||
if (_value !== '' && _value !== ' ') {
|
||||
this.debouncedRequestValues(_value)
|
||||
}
|
||||
}
|
||||
|
||||
onBlur = ({ target: { value } }) => {
|
||||
// to avoid sending unnecessary request on focus in/out without changing
|
||||
if (!changed && !pasted) return;
|
||||
|
||||
value = pasted ? this.hiddenInput.value : value;
|
||||
const { onSelect, name } = this.props;
|
||||
if (value !== this.props.value) {
|
||||
const _value = value ? value.trim() : undefined;
|
||||
onSelect(null, {name, value: _value});
|
||||
}
|
||||
|
||||
changed = false;
|
||||
pasted = false;
|
||||
}
|
||||
|
||||
onItemClick = (e, item) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
const { onSelect, name } = this.props;
|
||||
|
||||
this.setState({ query: item.value, ddOpen: false})
|
||||
onSelect(e, {name, ...item.toJS()});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { ddOpen, query, loading, values } = this.state;
|
||||
const {
|
||||
optionMapping = defaultOptionMapping,
|
||||
valueToText = defaultValueToText,
|
||||
placeholder = 'Type to search...',
|
||||
headerText = '',
|
||||
fullWidth = false,
|
||||
onRemoveValue = () => {},
|
||||
onAddValue = () => {},
|
||||
showCloseButton = false,
|
||||
} = this.props;
|
||||
|
||||
const options = optionMapping(values, valueToText)
|
||||
|
||||
return (
|
||||
<OutsideClickDetectingDiv
|
||||
className={ cn("relative flex items-center", { "flex-1" : fullWidth }) }
|
||||
onClickOutside={this.onClickOutside}
|
||||
>
|
||||
{/* <EventSearchInput /> */}
|
||||
<div className={stl.inputWrapper}>
|
||||
<input
|
||||
name="query"
|
||||
// className={cn(stl.input)}
|
||||
onFocus={ () => this.setState({ddOpen: true})}
|
||||
onChange={ this.onInputChange }
|
||||
onBlur={ this.onBlur }
|
||||
value={ query }
|
||||
autoFocus={ true }
|
||||
type="text"
|
||||
placeholder={ placeholder }
|
||||
onPaste={(e) => {
|
||||
const text = e.clipboardData.getData('Text');
|
||||
this.hiddenInput.value = text;
|
||||
pasted = true; // to use only the hidden input
|
||||
} }
|
||||
autocomplete="do-not-autofill-bad-chrome"
|
||||
/>
|
||||
<div className={stl.right} onClick={showCloseButton ? onRemoveValue : onAddValue}>
|
||||
{ showCloseButton ? <Icon name="close" size="14" /> : <span className="px-1">or</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showCloseButton && <div className='ml-2'>or</div>}
|
||||
{/* <Input
|
||||
className={ cn(stl.searchInput, { [ stl.fullWidth] : fullWidth }) }
|
||||
onChange={ this.onInputChange }
|
||||
onBlur={ this.onBlur }
|
||||
onFocus={ () => this.setState({ddOpen: true})}
|
||||
value={ query }
|
||||
// icon="search"
|
||||
label={{ basic: true, content: <div>test</div> }}
|
||||
labelPosition='right'
|
||||
loading={ loading }
|
||||
autoFocus={ true }
|
||||
type="search"
|
||||
placeholder={ placeholder }
|
||||
onPaste={(e) => {
|
||||
const text = e.clipboardData.getData('Text');
|
||||
this.hiddenInput.value = text;
|
||||
pasted = true; // to use only the hidden input
|
||||
} }
|
||||
/> */}
|
||||
<textarea style={hiddenStyle} ref={(ref) => this.hiddenInput = ref }></textarea>
|
||||
{ ddOpen && options.length > 0 &&
|
||||
<div className={ stl.menu }>
|
||||
{ headerText && headerText }
|
||||
{
|
||||
options.map(item => (
|
||||
<FilterItem
|
||||
label={ item.value }
|
||||
icon={ item.icon }
|
||||
onClick={ (e) => this.onItemClick(e, item) }
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</OutsideClickDetectingDiv>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AutoComplete;
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import React from 'react';
|
||||
import stl from './dropdownItem.module.css';
|
||||
|
||||
const DropdownItem = ({ value, onSelect }) => {
|
||||
return (
|
||||
<div className={ stl.wrapper } onClick={ onSelect } >{ value }</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DropdownItem;
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
.menu {
|
||||
border-radius: 0 0 3px 3px;
|
||||
box-shadow: 0 2px 10px 0 $gray-light;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
max-height: 350px;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
left: 0;
|
||||
width: 500px;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.searchInput {
|
||||
& input {
|
||||
font-size: 13px !important;
|
||||
padding: 5px !important;
|
||||
color: $gray-darkest !important;
|
||||
font-size: 14px !important;
|
||||
background-color: rgba(255, 255, 255, 0.8) !important;
|
||||
|
||||
& .label {
|
||||
padding: 0px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
height: 28px !important;
|
||||
width: 280px;
|
||||
color: $gray-darkest !important;
|
||||
}
|
||||
|
||||
.fullWidth {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.inputWrapper {
|
||||
border: solid thin $gray-light !important;
|
||||
border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
& input {
|
||||
height: 28px;
|
||||
font-size: 13px !important;
|
||||
padding: 0 5px !important;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
|
||||
& .right {
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 5px;
|
||||
background-color: $gray-lightest;
|
||||
border-left: solid thin $gray-light !important;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
.wrapper {
|
||||
padding: 8px;
|
||||
border-bottom: solid thin rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: $active-blue;
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './AutoComplete';
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
import { fetchFavoriteList as fetchFavoriteSessionList } from 'Duck/sessions';
|
||||
import { applyFilter, clearEvents, addAttribute } from 'Duck/filters';
|
||||
import { KEYS } from 'Types/filter/customFilter';
|
||||
import SessionList from './SessionList';
|
||||
import stl from './bugFinder.module.css';
|
||||
import withLocationHandlers from 'HOCs/withLocationHandlers';
|
||||
import { fetch as fetchFilterVariables } from 'Duck/sources';
|
||||
import { fetchSources } from 'Duck/customField';
|
||||
import { setActiveTab } from 'Duck/search';
|
||||
import SessionsMenu from './SessionsMenu/SessionsMenu';
|
||||
import NoSessionsMessage from 'Shared/NoSessionsMessage';
|
||||
import SessionSearch from 'Shared/SessionSearch';
|
||||
import MainSearchBar from 'Shared/MainSearchBar';
|
||||
import { clearSearch, fetchSessions, addFilterByKeyAndValue } from 'Duck/search';
|
||||
import { FilterKey } from 'Types/filter/filterType';
|
||||
|
||||
const weakEqual = (val1, val2) => {
|
||||
if (!!val1 === false && !!val2 === false) return true;
|
||||
if (!val1 !== !val2) return false;
|
||||
return `${val1}` === `${val2}`;
|
||||
};
|
||||
|
||||
const allowedQueryKeys = [
|
||||
'userOs',
|
||||
'userId',
|
||||
'userBrowser',
|
||||
'userDevice',
|
||||
'userCountry',
|
||||
'startDate',
|
||||
'endDate',
|
||||
'minDuration',
|
||||
'maxDuration',
|
||||
'referrer',
|
||||
'sort',
|
||||
'order',
|
||||
];
|
||||
|
||||
@withLocationHandlers()
|
||||
@connect(
|
||||
(state) => ({
|
||||
filter: state.getIn(['filters', 'appliedFilter']),
|
||||
variables: state.getIn(['customFields', 'list']),
|
||||
sources: state.getIn(['customFields', 'sources']),
|
||||
filterValues: state.get('filterValues'),
|
||||
favoriteList: state.getIn(['sessions', 'favoriteList']),
|
||||
currentProjectId: state.getIn(['site', 'siteId']),
|
||||
sites: state.getIn(['site', 'list']),
|
||||
watchdogs: state.getIn(['watchdogs', 'list']),
|
||||
activeFlow: state.getIn(['filters', 'activeFlow']),
|
||||
sessions: state.getIn(['sessions', 'list']),
|
||||
}),
|
||||
{
|
||||
fetchFavoriteSessionList,
|
||||
applyFilter,
|
||||
addAttribute,
|
||||
fetchFilterVariables,
|
||||
fetchSources,
|
||||
clearEvents,
|
||||
setActiveTab,
|
||||
clearSearch,
|
||||
fetchSessions,
|
||||
addFilterByKeyAndValue,
|
||||
}
|
||||
)
|
||||
@withPageTitle('Sessions - OpenReplay')
|
||||
export default class BugFinder extends React.PureComponent {
|
||||
state = { showRehydratePanel: false };
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// TODO should cache the response
|
||||
// props.fetchSources().then(() => {
|
||||
// defaultFilters[6] = {
|
||||
// category: 'Collaboration',
|
||||
// type: 'CUSTOM',
|
||||
// keys: this.props.sources.filter(({type}) => type === 'collaborationTool').map(({ label, key }) => ({ type: 'CUSTOM', source: key, label: label, key, icon: 'integrations/' + key, isFilter: false })).toJS()
|
||||
// };
|
||||
// defaultFilters[7] = {
|
||||
// category: 'Logging Tools',
|
||||
// type: 'ERROR',
|
||||
// keys: this.props.sources.filter(({type}) => type === 'logTool').map(({ label, key }) => ({ type: 'ERROR', source: key, label: label, key, icon: 'integrations/' + key, isFilter: false })).toJS()
|
||||
// };
|
||||
// });
|
||||
// if (props.sessions.size === 0) {
|
||||
// props.fetchSessions();
|
||||
// }
|
||||
|
||||
const queryFilter = this.props.query.all(allowedQueryKeys);
|
||||
if (queryFilter.hasOwnProperty('userId')) {
|
||||
props.addFilterByKeyAndValue(FilterKey.USERID, queryFilter.userId);
|
||||
} else {
|
||||
if (props.sessions.size === 0) {
|
||||
props.fetchSessions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleRehydratePanel = () => {
|
||||
this.setState({ showRehydratePanel: !this.state.showRehydratePanel });
|
||||
};
|
||||
|
||||
setActiveTab = (tab) => {
|
||||
this.props.setActiveTab(tab);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { showRehydratePanel } = this.state;
|
||||
|
||||
return (
|
||||
<div className="page-margin container-90 flex relative">
|
||||
<div className="flex-1 flex">
|
||||
<div className="side-menu">
|
||||
<SessionsMenu onMenuItemClick={this.setActiveTab} toggleRehydratePanel={this.toggleRehydratePanel} />
|
||||
</div>
|
||||
<div className={cn('side-menu-margined', stl.searchWrapper)}>
|
||||
<NoSessionsMessage />
|
||||
<div className="mb-5">
|
||||
<MainSearchBar />
|
||||
<SessionSearch />
|
||||
</div>
|
||||
<SessionList onMenuItemClick={this.setActiveTab} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { applyFilter } from 'Duck/search';
|
||||
import { fetchList as fetchFunnelsList } from 'Duck/funnels';
|
||||
import DateRangeDropdown from 'Shared/DateRangeDropdown';
|
||||
|
||||
@connect(state => ({
|
||||
filter: state.getIn([ 'search', 'instance' ]),
|
||||
}), {
|
||||
applyFilter, fetchFunnelsList
|
||||
})
|
||||
export default class DateRange extends React.PureComponent {
|
||||
onDateChange = (e) => {
|
||||
// this.props.fetchFunnelsList(e.rangeValue)
|
||||
this.props.applyFilter(e)
|
||||
}
|
||||
render() {
|
||||
const { filter: { rangeValue, startDate, endDate }, className } = this.props;
|
||||
|
||||
return (
|
||||
<DateRangeDropdown
|
||||
button
|
||||
onChange={ this.onDateChange }
|
||||
rangeValue={ rangeValue }
|
||||
startDate={ startDate }
|
||||
endDate={ endDate }
|
||||
className={ className }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Icon } from 'UI';
|
||||
import stl from './filterSelectionButton.module.css';
|
||||
|
||||
const FilterSelectionButton = ({ label }) => {
|
||||
return (
|
||||
<div className={ stl.wrapper }>
|
||||
<span className="capitalize">{ label } </span>
|
||||
<Icon name="chevron-down"/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterSelectionButton;
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Select from 'Shared/Select';
|
||||
import { Icon } from 'UI';
|
||||
import { sort } from 'Duck/sessions';
|
||||
import { applyFilter } from 'Duck/search';
|
||||
import stl from './sortDropdown.module.css';
|
||||
|
||||
@connect(null, { sort, applyFilter })
|
||||
export default class SortDropdown extends React.PureComponent {
|
||||
state = { value: null }
|
||||
sort = ({ value }) => {
|
||||
value = value.value
|
||||
this.setState({ value: value })
|
||||
const [ sort, order ] = value.split('-');
|
||||
const sign = order === 'desc' ? -1 : 1;
|
||||
this.props.applyFilter({ order, sort });
|
||||
|
||||
this.props.sort(sort, sign)
|
||||
setTimeout(() => this.props.sort(sort, sign), 3000); //AAA
|
||||
}
|
||||
|
||||
render() {
|
||||
const { options } = this.props;
|
||||
return (
|
||||
<Select
|
||||
name="sortSessions"
|
||||
plain
|
||||
right
|
||||
options={ options }
|
||||
onChange={ this.sort }
|
||||
defaultValue={ options[ 0 ].value }
|
||||
icon={ <Icon name="chevron-down" color="gray-dark" size="14" className={stl.dropdownIcon} /> }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './Filters';
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
.dropdown {
|
||||
display: flex !important;
|
||||
padding: 4px 6px;
|
||||
border-radius: 3px;
|
||||
color: $gray-darkest;
|
||||
font-weight: 500;
|
||||
&:hover {
|
||||
background-color: $gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdownTrigger {
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
&:hover {
|
||||
background-color: $gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdownIcon {
|
||||
margin-top: 2px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import styles from './insights.module.css';
|
||||
|
||||
const Insights = ({ insights }) => (
|
||||
<div className={ styles.notes }>
|
||||
<div className={ styles.tipText }>
|
||||
<i className={ styles.tipIcon } />
|
||||
{'This journey is only 2% of all the journeys but represents 20% of problems.'}
|
||||
</div>
|
||||
<div className={ styles.tipText }>
|
||||
<i className={ styles.tipIcon } />
|
||||
{'Lorem Ipsum 1290 events of 1500 events.'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Insights.displayName = 'Insights';
|
||||
|
||||
export default connect(state => ({
|
||||
insights: state.getIn([ 'sessions', 'insights' ]),
|
||||
}))(Insights);
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import React from 'react';
|
||||
import stl from './listHeader.module.css';
|
||||
|
||||
const ListHeader = ({ title }) => {
|
||||
return (
|
||||
<div className={ stl.header }>{ title }</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListHeader;
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Loader, NoContent, Pagination } from 'UI';
|
||||
import { applyFilter, addAttribute, addEvent } from 'Duck/filters';
|
||||
import { fetchSessions, addFilterByKeyAndValue, updateCurrentPage, setScrollPosition } from 'Duck/search';
|
||||
import SessionItem from 'Shared/SessionItem';
|
||||
import SessionListHeader from './SessionListHeader';
|
||||
import { FilterKey } from 'Types/filter/filterType';
|
||||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||
|
||||
// const ALL = 'all';
|
||||
const PER_PAGE = 10;
|
||||
const AUTOREFRESH_INTERVAL = 5 * 60 * 1000;
|
||||
var timeoutId;
|
||||
|
||||
@connect(state => ({
|
||||
shouldAutorefresh: state.getIn([ 'filters', 'appliedFilter', 'events' ]).size === 0,
|
||||
savedFilters: state.getIn([ 'filters', 'list' ]),
|
||||
loading: state.getIn([ 'sessions', 'loading' ]),
|
||||
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' ]),
|
||||
scrollY: state.getIn([ 'search', 'scrollY' ]),
|
||||
lastPlayedSessionId: state.getIn([ 'sessions', 'lastPlayedSessionId' ]),
|
||||
}), {
|
||||
applyFilter,
|
||||
addAttribute,
|
||||
addEvent,
|
||||
fetchSessions,
|
||||
addFilterByKeyAndValue,
|
||||
updateCurrentPage,
|
||||
setScrollPosition,
|
||||
})
|
||||
export default class SessionList extends React.PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.timeout();
|
||||
}
|
||||
|
||||
onUserClick = (userId, userAnonymousId) => {
|
||||
if (userId) {
|
||||
this.props.addFilterByKeyAndValue(FilterKey.USERID, userId);
|
||||
} else {
|
||||
this.props.addFilterByKeyAndValue(FilterKey.USERID, '', 'isUndefined');
|
||||
}
|
||||
}
|
||||
|
||||
timeout = () => {
|
||||
timeoutId = setTimeout(function () {
|
||||
if (this.props.shouldAutorefresh) {
|
||||
// this.props.applyFilter();
|
||||
this.props.fetchSessions();
|
||||
}
|
||||
this.timeout();
|
||||
}.bind(this), AUTOREFRESH_INTERVAL);
|
||||
}
|
||||
|
||||
getNoContentMessage = activeTab => {
|
||||
let str = "No recordings found";
|
||||
if (activeTab.type !== 'all') {
|
||||
str += ' with ' + activeTab.name;
|
||||
return str;
|
||||
}
|
||||
|
||||
return str + '!';
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.setScrollPosition(window.scrollY)
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { scrollY } = this.props;
|
||||
window.scrollTo(0, scrollY);
|
||||
}
|
||||
|
||||
renderActiveTabContent(list) {
|
||||
const {
|
||||
loading,
|
||||
filters,
|
||||
activeTab,
|
||||
metaList,
|
||||
currentPage,
|
||||
total,
|
||||
lastPlayedSessionId,
|
||||
} = this.props;
|
||||
const _filterKeys = filters.map(i => i.key);
|
||||
const hasUserFilter = _filterKeys.includes(FilterKey.USERID) || _filterKeys.includes(FilterKey.USERANONYMOUSID);
|
||||
|
||||
return (
|
||||
<div className="bg-white p-3 rounded border">
|
||||
<NoContent
|
||||
title={<div className="flex items-center justify-center flex-col">
|
||||
<AnimatedSVG name={ICONS.NO_RESULTS} size="170" />
|
||||
{this.getNoContentMessage(activeTab)}
|
||||
</div>}
|
||||
// subtext="Please try changing your search parameters."
|
||||
// animatedIcon="no-results"
|
||||
show={ !loading && list.size === 0}
|
||||
subtext={
|
||||
<div>
|
||||
<div>Please try changing your search parameters.</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
||||
<Loader loading={ loading }>
|
||||
{ list.map(session => (
|
||||
<React.Fragment key={ session.sessionId }>
|
||||
<SessionItem
|
||||
session={ session }
|
||||
hasUserFilter={hasUserFilter}
|
||||
onUserClick={this.onUserClick}
|
||||
metaList={metaList}
|
||||
lastPlayedSessionId={lastPlayedSessionId}
|
||||
/>
|
||||
<div className="border-b" />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Loader>
|
||||
<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>
|
||||
</NoContent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { activeTab, allList, total } = this.props;
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<SessionListHeader activeTab={activeTab} count={total}/>
|
||||
{ this.renderActiveTabContent(allList) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { Button } from 'UI';
|
||||
import styles from './sessionListFooter.module.css';
|
||||
|
||||
const SessionListFooter = ({
|
||||
displayedCount, totalCount, loading, onLoadMoreClick,
|
||||
}) => (
|
||||
<div className={ styles.pageLoading }>
|
||||
<div className={ styles.countInfo }>
|
||||
{ `Displaying ${ displayedCount } of ${ totalCount }` }
|
||||
</div>
|
||||
{ totalCount > displayedCount &&
|
||||
<Button
|
||||
onClick={ onLoadMoreClick }
|
||||
disabled={ loading }
|
||||
loading={ loading }
|
||||
outline
|
||||
>
|
||||
{ 'Load more...' }
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
SessionListFooter.displayName = 'SessionListFooter';
|
||||
|
||||
export default connect(state => ({
|
||||
loading: state.getIn([ 'sessions', 'loading' ])
|
||||
}))(SessionListFooter);
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import SortDropdown from '../Filters/SortDropdown';
|
||||
import { numberWithCommas } from 'App/utils';
|
||||
import SelectDateRange from 'Shared/SelectDateRange';
|
||||
import { applyFilter } from 'Duck/search';
|
||||
import Record from 'Types/app/period';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
import { moment } from 'App/dateRange';
|
||||
|
||||
const sortOptionsMap = {
|
||||
'startTs-desc': 'Newest',
|
||||
'startTs-asc': 'Oldest',
|
||||
'eventsCount-asc': 'Events Ascending',
|
||||
'eventsCount-desc': 'Events Descending',
|
||||
};
|
||||
const sortOptions = Object.entries(sortOptionsMap).map(([value, label]) => ({ value, label }));
|
||||
|
||||
function SessionListHeader({ activeTab, count, applyFilter, filter }) {
|
||||
const { settingsStore } = useStore();
|
||||
|
||||
const label = useObserver(() => settingsStore.sessionSettings.timezone.label);
|
||||
const getTimeZoneOffset = React.useCallback(() => {
|
||||
return label.slice(-6);
|
||||
}, [label]);
|
||||
|
||||
const { startDate, endDate, rangeValue } = filter;
|
||||
const period = new Record({ start: startDate, end: endDate, rangeName: rangeValue, timezoneOffset: getTimeZoneOffset() });
|
||||
|
||||
const onDateChange = (e) => {
|
||||
const dateValues = e.toJSON();
|
||||
dateValues.startDate = moment(dateValues.startDate).utcOffset(getTimeZoneOffset(), true).valueOf();
|
||||
dateValues.endDate = moment(dateValues.endDate).utcOffset(getTimeZoneOffset(), true).valueOf();
|
||||
applyFilter(dateValues);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (label) {
|
||||
const dateValues = period.toJSON();
|
||||
dateValues.startDate = moment(dateValues.startDate).startOf('day').utcOffset(getTimeZoneOffset(), true).valueOf();
|
||||
dateValues.endDate = moment(dateValues.endDate).endOf('day').utcOffset(getTimeZoneOffset(), true).valueOf();
|
||||
// applyFilter(dateValues);
|
||||
}
|
||||
}, [label]);
|
||||
|
||||
return (
|
||||
<div className="flex mb-2 justify-between items-end">
|
||||
<div className="flex items-baseline">
|
||||
<h3 className="text-2xl capitalize">
|
||||
<span>{activeTab.name}</span>
|
||||
<span className="ml-2 font-normal color-gray-medium">{count ? numberWithCommas(count) : 0}</span>
|
||||
</h3>
|
||||
{
|
||||
<div className="ml-3 flex items-center">
|
||||
<span className="mr-2 color-gray-medium">Sessions Captured in</span>
|
||||
<SelectDateRange period={period} onChange={onDateChange} timezone={getTimeZoneOffset()} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center ml-6">
|
||||
<span className="mr-2 color-gray-medium">Sort By</span>
|
||||
<SortDropdown options={sortOptions} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state) => ({
|
||||
activeTab: state.getIn(['search', 'activeTab']),
|
||||
period: state.getIn(['search', 'period']),
|
||||
filter: state.getIn(['search', 'instance']),
|
||||
}),
|
||||
{ applyFilter }
|
||||
)(SessionListHeader);
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './SessionList';
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
.customMessage {
|
||||
padding: 5px 10px !important;
|
||||
box-shadow: none !important;
|
||||
font-size: 12px !important;
|
||||
color: $gray-medium !important;
|
||||
font-weight: 300;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
& > div {
|
||||
flex: none !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
@import 'mixins.css';
|
||||
|
||||
.pageLoading {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
margin: 20px 0 30px;
|
||||
}
|
||||
|
||||
.loadMoreButton {
|
||||
@mixin basicButton;
|
||||
}
|
||||
|
||||
.countInfo {
|
||||
font-size: 10px;
|
||||
color: #999;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
import React from 'react'
|
||||
import { connect } from 'react-redux';
|
||||
import cn from 'classnames';
|
||||
import { SideMenuitem, Tooltip } from 'UI'
|
||||
import stl from './sessionMenu.module.css';
|
||||
import { clearEvents } from 'Duck/filters';
|
||||
import { issues_types } from 'Types/session/issue'
|
||||
import { fetchList as fetchSessionList } from 'Duck/sessions';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import SessionSettings from 'Shared/SessionSettings/SessionSettings'
|
||||
|
||||
function SessionsMenu(props) {
|
||||
const { activeTab, isEnterprise } = props;
|
||||
const { showModal } = useModal();
|
||||
|
||||
const onMenuItemClick = (filter) => {
|
||||
props.onMenuItemClick(filter)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={stl.wrapper}>
|
||||
<div className={ cn(stl.header, 'flex items-center') }>
|
||||
<div className={ stl.label }>
|
||||
<span>Sessions</span>
|
||||
</div>
|
||||
<span className={ cn(stl.manageButton, 'mr-2') } onClick={() => showModal(<SessionSettings />, { right: true })}>
|
||||
<Tooltip
|
||||
title={<span>Configure the percentage of sessions <br /> to be captured, timezone and more.</span>}
|
||||
>
|
||||
Settings
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<SideMenuitem
|
||||
active={activeTab.type === 'all'}
|
||||
title="All"
|
||||
iconName="play-circle"
|
||||
onClick={() => onMenuItemClick({ name: 'All', type: 'all' })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{ issues_types.filter(item => item.visible).map(item => (
|
||||
<SideMenuitem
|
||||
key={item.key}
|
||||
active={activeTab.type === item.type}
|
||||
title={item.name} iconName={item.icon}
|
||||
onClick={() => onMenuItemClick(item)}
|
||||
/>
|
||||
))}
|
||||
|
||||
<div className={cn(stl.divider, 'my-4')} />
|
||||
<SideMenuitem
|
||||
title={ isEnterprise ? "Vault" : "Bookmarks" }
|
||||
iconName={ isEnterprise ? "safe" : "star" }
|
||||
active={activeTab.type === 'bookmark'}
|
||||
onClick={() => onMenuItemClick({ name: isEnterprise ? 'Vault' : 'Bookmarks', type: 'bookmark', description: isEnterprise ? 'Sessions saved to vault never get\'s deleted from records.' : '' })}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
activeTab: state.getIn([ 'search', 'activeTab' ]),
|
||||
captureRate: state.getIn(['watchdogs', 'captureRate']),
|
||||
filters: state.getIn([ 'filters', 'appliedFilter' ]),
|
||||
sessionsLoading: state.getIn([ 'sessions', 'fetchLiveListRequest', 'loading' ]),
|
||||
isEnterprise: state.getIn([ 'user', 'account', 'edition' ]) === 'ee',
|
||||
}), {
|
||||
clearEvents, fetchSessionList
|
||||
})(SessionsMenu);
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './SessionsMenu';
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
.header {
|
||||
margin-bottom: 15px;
|
||||
& .label {
|
||||
text-transform: uppercase;
|
||||
color: gray;
|
||||
letter-spacing: 0.2em;
|
||||
}
|
||||
|
||||
& .manageButton {
|
||||
margin-left: 5px;
|
||||
font-size: 12px;
|
||||
color: $teal;
|
||||
cursor: pointer;
|
||||
padding: 2px 5px;
|
||||
border: solid thin transparent;
|
||||
border-radius: 3px;
|
||||
margin-bottom: -3px;
|
||||
&:hover {
|
||||
background-color: $gray-light;
|
||||
color: $gray-darkest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: $gray-light;
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { Icon } from 'UI';
|
||||
import stl from './tabItem.module.css';
|
||||
|
||||
const TabItem = ({ icon, label, count, iconColor = 'teal', active = false, leading, ...rest }) => {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
count === 0 ? stl.disabled : '',
|
||||
cn(stl.wrapper,
|
||||
active ? stl.active : '',
|
||||
"flex items-center py-2 justify-between")
|
||||
}
|
||||
{ ...rest }
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{ icon && <Icon name={ icon } size="16" color={ iconColor } /> }
|
||||
<span className="ml-3 mr-1">{ label }</span>
|
||||
{ count && <span>({ count })</span>}
|
||||
</div>
|
||||
{ !!leading && leading }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TabItem;
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './TabItem'
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
.wrapper {
|
||||
color: $teal;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
border: solid thin transparent;
|
||||
border-radius: 3px;
|
||||
margin-left: -5px;
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
background-color: $active-blue;
|
||||
border-color: $active-blue-border;
|
||||
& .actionWrapper {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
@import 'mixins.css';
|
||||
|
||||
.searchWrapper {
|
||||
flex: 1;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-top: solid thin #EDEDED;
|
||||
& > div {
|
||||
cursor: pointer;
|
||||
padding: 0 10px;
|
||||
border-right: solid thin $gray-light;
|
||||
&:hover {
|
||||
background-color: $active-blue;
|
||||
}
|
||||
&:last-child {
|
||||
border-right: solid thin transparent;
|
||||
}
|
||||
&:first-child {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.savedSearchesWrapper {
|
||||
width: 200px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
|
||||
.header {
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
margin-bottom: 10px;
|
||||
letter-spacing: 1px;
|
||||
color: $gray-medium;
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
import { storiesOf } from '@storybook/react';
|
||||
import SessionsMenu from './SessionsMenu/SessionsMenu';
|
||||
import SessionItem from 'Shared/SessionItem';
|
||||
import SessionStack from 'Shared/SessionStack';
|
||||
import Session from 'Types/session';
|
||||
import SessionListHeader from './SessionList/SessionListHeader';
|
||||
import SavedFilter from 'Types/filter/savedFilter';
|
||||
import { List } from 'immutable';
|
||||
|
||||
var items = [
|
||||
{
|
||||
"watchdogId": 140,
|
||||
"projectId": 1,
|
||||
"type": "errors",
|
||||
"payload": {
|
||||
"threshold": 0,
|
||||
"captureAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"watchdogId": 139,
|
||||
"projectId": 1,
|
||||
"type": "bad_request",
|
||||
"payload": {
|
||||
"threshold": 0,
|
||||
"captureAll": true
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
var session = Session({
|
||||
"projectId": 1,
|
||||
"sessionId": "2236890417118217",
|
||||
"userUuid": "1e4bec88-fe8d-4f51-9806-716e92384ffc",
|
||||
"userId": null,
|
||||
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36",
|
||||
"userOs": "Mac OS X",
|
||||
"userBrowser": "Chrome",
|
||||
"userDevice": "Mac",
|
||||
"userCountry": "FR",
|
||||
"startTs": 1584132239030,
|
||||
"duration": 618469,
|
||||
"eventsCount": 24,
|
||||
"pagesCount": 18,
|
||||
"errorsCount": 0,
|
||||
"watchdogs": [
|
||||
137,
|
||||
143
|
||||
],
|
||||
"favorite": false,
|
||||
"viewed": false
|
||||
})
|
||||
|
||||
var savedFilters = [
|
||||
SavedFilter({filterId: 1, name: 'Something', count: 10, watchdogs: []})
|
||||
]
|
||||
|
||||
storiesOf('Bug Finder', module)
|
||||
.add('Sessions Menu', () => (
|
||||
<SessionsMenu items={ items } />
|
||||
))
|
||||
.add('Sessions Item', () => (
|
||||
<SessionItem key={1} session={session}/>
|
||||
))
|
||||
.add('Session List Header', () => (
|
||||
<SessionListHeader />
|
||||
))
|
||||
.add('Sessions Stack', () => (
|
||||
<SessionStack flow={savedFilters[0]} />
|
||||
))
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 28px;
|
||||
border: solid thin rgba(34, 36, 38, 0.15) !important;
|
||||
border-radius: 4px;
|
||||
padding: 0 10px;
|
||||
width: 150px;
|
||||
color: $gray-darkest;
|
||||
cursor: pointer;
|
||||
background-color: rgba(0, 0, 0, 0.1) !important;
|
||||
&:hover {
|
||||
background-color: white;
|
||||
}
|
||||
& span {
|
||||
margin-right: 5px;
|
||||
max-width: 140px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './BugFinder';
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
|
||||
@import 'icons.css';
|
||||
|
||||
.notes {
|
||||
margin: 15px 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
.tipIcon {
|
||||
@mixin icon lightbulb, $gray-medium, 13px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.tipText {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: $gray-medium;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
.header {
|
||||
padding: 3px 10px;
|
||||
letter-spacing: 1.5px;
|
||||
color: $gray-medium;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
|
@ -4,8 +4,7 @@ import { Set, List as ImmutableList } from "immutable";
|
|||
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 { IGNORED, UNRESOLVED } from 'Types/errorInfo';
|
||||
import Divider from 'Components/Errors/ui/Divider';
|
||||
import ListItem from './ListItem/ListItem';
|
||||
import { debounce } from 'App/utils';
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
import React from 'react'
|
||||
import stl from './sessionStack.module.css'
|
||||
import cn from 'classnames';
|
||||
import { Icon } from 'UI'
|
||||
import { names } from 'Types/watchdog'
|
||||
import { applySavedFilter, setActiveFlow } from 'Duck/filters';
|
||||
import { connect } from 'react-redux';
|
||||
import { setActiveTab } from 'Duck/sessions';
|
||||
|
||||
const IconLabel = ({ icon, label}) => (
|
||||
<div className="w-9/12 flex items-center justify-end">
|
||||
<Icon name={icon} size="20" color={label > 0 ? 'gray' : 'gray-medium'} />
|
||||
<div className={cn('ml-2 text-xl', label > 0 ? 'color-gray' : 'color-gray-medium')}>{label}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
function SessionStack({ flow = {}, applySavedFilter, setActiveTab, setActiveFlow }) {
|
||||
const onAllClick = (flow) => {
|
||||
setActiveFlow(flow)
|
||||
applySavedFilter(flow.filter)
|
||||
setActiveTab({ type: 'all', name: 'All'})
|
||||
}
|
||||
return (
|
||||
<div className={stl.wrapper}>
|
||||
<div
|
||||
className="text-xl mb-6 capitalize color-teal cursor-pointer"
|
||||
onClick={() => onAllClick(flow)}>
|
||||
{flow.name}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="w-2/12 text-xl"><span className="text-3xl">{flow.count}</span> Sessions</div>
|
||||
<div className="w-6/12 flex items-center ml-auto">
|
||||
{flow.watchdogs.map(({type, count}) => (
|
||||
<IconLabel key={type} icon={names[type].icon} label={count} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(null, { applySavedFilter, setActiveTab, setActiveFlow })(SessionStack)
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './SessionStack';
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
|
||||
@import 'mixins.css';
|
||||
|
||||
.wrapper {
|
||||
background: #fff;
|
||||
border: solid thin $gray-light;
|
||||
border-radius: 3px;
|
||||
@mixin defaultHover;
|
||||
box-shadow:
|
||||
/* The top layer shadow */
|
||||
/* 0 1px 1px rgba(0,0,0,0.15), */
|
||||
/* The second layer */
|
||||
4px 4px 1px 1px white,
|
||||
/* The second layer shadow */
|
||||
4px 4px 0px 1px rgba(0,0,0,0.4);
|
||||
/* Padding for demo purposes */
|
||||
padding: 16px;
|
||||
}
|
||||
|
|
@ -11,7 +11,6 @@ export default Record({
|
|||
filter: Filter(),
|
||||
createdAt: undefined,
|
||||
count: 0,
|
||||
watchdogs: List(),
|
||||
isPublic: false,
|
||||
}, {
|
||||
idKey: 'searchId',
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ export default Record(
|
|||
returningLocation: undefined,
|
||||
returningLocationTime: undefined,
|
||||
errorsCount: 0,
|
||||
watchdogs: [],
|
||||
issueTypes: [],
|
||||
issues: [],
|
||||
userDeviceHeapSize: 0,
|
||||
|
|
@ -145,7 +144,6 @@ export default Record(
|
|||
return {
|
||||
...session,
|
||||
isIOS: session.platform === 'ios',
|
||||
watchdogs: session.watchdogs || [],
|
||||
errors: exceptions,
|
||||
siteId: projectId,
|
||||
events,
|
||||
|
|
|
|||
|
|
@ -10,5 +10,4 @@ export interface SavedSearch {
|
|||
projectId: number;
|
||||
searchId: number;
|
||||
userId: number;
|
||||
watchdogs: List<any>
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue