change(ui): remove bugfinder components and store

This commit is contained in:
sylenien 2022-12-28 14:41:04 +01:00
parent 3dc3d736ac
commit 22cdb4f4e7
41 changed files with 4 additions and 1231 deletions

View file

@ -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);

View file

@ -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 />} />

View file

@ -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))

View file

@ -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;

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

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

View file

@ -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>
);
}
}

View file

@ -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 }
/>
);
}
}

View file

@ -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;

View file

@ -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} /> }
/>
);
}
}

View file

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

View file

@ -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;
}

View file

@ -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);

View file

@ -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;

View file

@ -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>
);
}
}

View file

@ -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);

View file

@ -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);

View file

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

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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);

View file

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

View file

@ -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;
}

View file

@ -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;

View file

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

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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]} />
))

View file

@ -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;
}
}

View file

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

View file

@ -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;
}

View file

@ -1,7 +0,0 @@
.header {
padding: 3px 10px;
letter-spacing: 1.5px;
color: $gray-medium;
font-size: 12px;
text-transform: uppercase;
}

View file

@ -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';

View file

@ -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)

View file

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

View file

@ -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;
}

View file

@ -11,7 +11,6 @@ export default Record({
filter: Filter(),
createdAt: undefined,
count: 0,
watchdogs: List(),
isPublic: false,
}, {
idKey: 'searchId',

View file

@ -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,

View file

@ -10,5 +10,4 @@ export interface SavedSearch {
projectId: number;
searchId: number;
userId: number;
watchdogs: List<any>
}