Webpack upgrade and dependency cleanup (#523)

* change(ui) - webpack update
* change(ui) - api optimize and other fixes
This commit is contained in:
Shekar Siri 2022-06-03 16:47:38 +02:00 committed by GitHub
parent f5e013329f
commit 2ed5cac986
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
993 changed files with 12905 additions and 38918 deletions

14
frontend/.babelrc Normal file
View file

@ -0,0 +1,14 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
[ "@babel/plugin-proposal-private-property-in-object", { "loose": true } ],
[ "@babel/plugin-transform-runtime", { "regenerator": true } ],
[ "@babel/plugin-proposal-decorators", { "legacy":true } ],
[ "@babel/plugin-proposal-class-properties", { "loose":true } ],
[ "@babel/plugin-proposal-private-methods", { "loose": true }]
]
}

26
frontend/.env.sample Normal file
View file

@ -0,0 +1,26 @@
NODE_ENV=production
SOURCEMAP = false
# END POINTS #
ORIGIN = ''
API_EDP = ''
ASSETS_HOST = ''
# SENTRY
SENTRY_ENABLED = false
SENTRY_URL = ''
# CAPTCHA
CAPTCHA_ENABLED = false
CAPTCHA_SITE_KEY = 'asdad'
# MINIO
MINIO_ENDPOINT = ''
MINIO_PORT = ''
MINIO_USE_SSL = ''
MINIO_ACCESS_KEY = ''
MINIO_SECRET_KEY = ''
# APP and TRACKER VERSIONS
VERSION = '1.6.0'
TRACKER_VERSION = '3.5.10'

View file

@ -21,12 +21,10 @@ const FunnelDetails = lazy(() => import('Components/Funnels/FunnelDetails'));
const FunnelIssueDetails = lazy(() => import('Components/Funnels/FunnelIssueDetails'));
import WidgetViewPure from 'Components/Dashboard/components/WidgetView';
import Header from 'Components/Header/Header';
// import ResultsModal from 'Shared/Results/ResultsModal';
import { fetchList as fetchMetadata } from 'Duck/customField';
import { fetchList as fetchSiteList } from 'Duck/site';
import { fetchList as fetchAnnouncements } from 'Duck/announcements';
import { fetchList as fetchAlerts } from 'Duck/alerts';
import { dashboardService } from "App/services";
import { withStore } from 'App/mstore'
import APIClient from './api_client';
@ -36,7 +34,6 @@ import Signup from './components/Signup/Signup';
import { fetchTenants } from 'Duck/user';
import { setSessionPath } from 'Duck/sessions';
import { ModalProvider } from './components/Modal';
import ModalRoot from './components/Modal/ModalRoot';
const BugFinder = withSiteIdUpdater(BugFinderPure);
const Dashboard = withSiteIdUpdater(DashboardPure);
@ -50,7 +47,7 @@ const Errors = withSiteIdUpdater(ErrorsPure);
const Funnels = withSiteIdUpdater(FunnelDetails);
const FunnelIssue = withSiteIdUpdater(FunnelIssueDetails);
const withSiteId = routes.withSiteId;
const withObTab = routes.withObTab;
// const withObTab = routes.withObTab;
const METRICS_PATH = routes.metrics();
const METRICS_DETAILS = routes.metricDetails();
@ -115,8 +112,9 @@ class Router extends React.Component {
super(props);
if (props.isLoggedIn) {
this.fetchInitialData();
} else {
props.fetchTenants();
}
props.fetchTenants();
}
fetchInitialData = () => {
@ -126,11 +124,11 @@ class Router extends React.Component {
const { mstore } = this.props
mstore.initClient();
setTimeout(() => {
this.props.fetchMetadata()
this.props.fetchAnnouncements();
this.props.fetchAlerts();
}, 100);
// setTimeout(() => {
// this.props.fetchMetadata()
// this.props.fetchAnnouncements();
// this.props.fetchAlerts();
// }, 100);
})
})
])
@ -166,12 +164,11 @@ class Router extends React.Component {
return isLoggedIn ?
<Loader loading={ loading } className="flex-1" >
{!hideHeader && <Header key="header"/>}
<Notification />
<Suspense fallback={<Loader loading={true} className="flex-1" />}>
<ModalProvider>
<ModalRoot />
{!hideHeader && <Header key="header"/>}
<Switch key="content" >
<Route path={ CLIENT_PATH } component={ Client } />
<Route path={ withSiteId(ONBOARDING_PATH, siteIdList)} component={ Onboarding } />

View file

@ -82,8 +82,7 @@ export default class APIClient {
let fetch = window.fetch;
let edp = window.ENV.API_EDP;
let edp = window.env.API_EDP || window.location.origin + '/api';
if (
path !== '/targets_temp' &&
!path.includes('/metadata/session_search') &&

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View file

@ -5,10 +5,9 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="slack-app-id" content="AA5LEB34M">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css" />
<link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png">
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="stylesheet">
</head>
<body>

View file

@ -1,27 +1,28 @@
import React, { useEffect } from 'react'
import { Button, Dropdown, Form, Input, SegmentSelection, Checkbox, Message, Link, Icon } from 'UI';
import { Button, Form, Input, SegmentSelection, Checkbox, Message, Link, Icon } from 'UI';
import { alertMetrics as metrics } from 'App/constants';
import { alertConditions as conditions } from 'App/constants';
import { client, CLIENT_TABS } from 'App/routes';
import { connect } from 'react-redux';
import stl from './alertForm.css';
import stl from './alertForm.module.css';
import DropdownChips from './DropdownChips';
import { validateEmail } from 'App/validate';
import cn from 'classnames';
import { fetchTriggerOptions } from 'Duck/alerts';
import Select from 'Shared/Select'
const thresholdOptions = [
{ text: '15 minutes', value: 15 },
{ text: '30 minutes', value: 30 },
{ text: '1 hour', value: 60 },
{ text: '2 hours', value: 120 },
{ text: '4 hours', value: 240 },
{ text: '1 day', value: 1440 },
{ label: '15 minutes', value: 15 },
{ label: '30 minutes', value: 30 },
{ label: '1 hour', value: 60 },
{ label: '2 hours', value: 120 },
{ label: '4 hours', value: 240 },
{ label: '1 day', value: 1440 },
];
const changeOptions = [
{ text: 'change', value: 'change' },
{ text: '% change', value: 'percent' },
{ label: 'change', value: 'change' },
{ label: '% change', value: 'percent' },
];
const Circle = ({ text }) => (
@ -50,7 +51,9 @@ const AlertForm = props => {
const { instance, slackChannels, webhooks, loading, onDelete, deleting, triggerOptions, metricId, style={ width: '580px', height: '100vh' } } = props;
const write = ({ target: { value, name } }) => props.edit({ [ name ]: value })
const writeOption = (e, { name, value }) => props.edit({ [ name ]: value });
const onChangeOption = (e, { checked, name }) => props.edit({ [ name ]: checked })
const onChangeCheck = ({ target: { checked, name }}) => props.edit({ [ name ]: checked })
// const onChangeOption = ({ checked, name }) => props.edit({ [ name ]: checked })
// const onChangeCheck = (e) => { console.log(e) }
useEffect(() => {
props.fetchTriggerOptions();
@ -75,7 +78,7 @@ const AlertForm = props => {
<div className={cn(stl.content, '-mx-6 px-6 pb-12')}>
<input
autoFocus={ true }
className="text-lg"
className="text-lg border border-gray-light rounded w-full"
name="name"
style={{ fontSize: '18px', padding: '10px', fontWeight: '600'}}
value={ instance && instance.name }
@ -119,14 +122,13 @@ const AlertForm = props => {
{!isThreshold && (
<div className="flex items-center my-3">
<label className="w-2/6 flex-shrink-0 font-normal">{'Trigger when'}</label>
<Dropdown
<Select
className="w-4/6"
placeholder="change"
selection
options={ changeOptions }
name="change"
value={ instance.change }
onChange={ writeOption }
defaultValue={ instance.change }
onChange={ ({ value }) => writeOption(null , { name: 'change', value }) }
id="change-dropdown"
/>
</div>
@ -134,29 +136,28 @@ const AlertForm = props => {
<div className="flex items-center my-3">
<label className="w-2/6 flex-shrink-0 font-normal">{isThreshold ? 'Trigger when' : 'of'}</label>
<Dropdown
<Select
className="w-4/6"
placeholder="Select Metric"
selection
search
isSearchable={true}
options={ triggerOptions }
name="left"
value={ instance.query.left }
onChange={ writeQueryOption }
value={ triggerOptions.find(i => i.value === instance.query.left) }
// onChange={ writeQueryOption }
onChange={ ({ value }) => writeQueryOption(null, { name: 'left', value }) }
/>
</div>
<div className="flex items-center my-3">
<label className="w-2/6 flex-shrink-0 font-normal">{'is'}</label>
<div className="w-4/6 flex items-center">
<Dropdown
className="px-4"
<Select
placeholder="Select Condition"
selection
options={ conditions }
name="operator"
value={ instance.query.operator }
onChange={ writeQueryOption }
defaultValue={ instance.query.operator }
// onChange={ writeQueryOption }
onChange={ ({ value }) => writeQueryOption(null, { name: 'operator', value }) }
/>
{ unit && (
<Input
@ -172,39 +173,40 @@ const AlertForm = props => {
)}
{ !unit && (
<Input
className="pl-4"
name="right"
value={ instance.query.right }
onChange={ writeQuery }
placeholder="Specify Value"
/>
wrapperClassName="ml-2"
// className="pl-4"
name="right"
value={ instance.query.right }
onChange={ writeQuery }
placeholder="Specify Value"
/>
)}
</div>
</div>
<div className="flex items-center my-3">
<label className="w-2/6 flex-shrink-0 font-normal">{'over the past'}</label>
<Dropdown
<Select
className="w-2/6"
placeholder="Select timeframe"
selection
options={ thresholdOptions }
name="currentPeriod"
value={ instance.currentPeriod }
onChange={ writeOption }
defaultValue={ instance.currentPeriod }
// onChange={ writeOption }
onChange={ ({ value }) => writeOption(null, { name: 'currentPeriod', value }) }
/>
</div>
{!isThreshold && (
<div className="flex items-center my-3">
<label className="w-2/6 flex-shrink-0 font-normal">{'compared to previous'}</label>
<Dropdown
<Select
className="w-2/6"
placeholder="Select timeframe"
selection
options={ thresholdOptions }
name="previousPeriod"
value={ instance.previousPeriod }
onChange={ writeOption }
defaultValue={ instance.previousPeriod }
// onChange={ writeOption }
onChange={ ({ value }) => writeOption(null, { name: 'previousPeriod', value }) }
/>
</div>
)}
@ -223,18 +225,17 @@ const AlertForm = props => {
<div className="flex items-center my-4">
<Checkbox
name="slack"
className="font-medium"
className="mr-8"
type="checkbox"
checked={ instance.slack }
onClick={ onChangeOption }
className="mr-8"
onClick={ onChangeCheck }
label="Slack"
/>
<Checkbox
name="email"
type="checkbox"
checked={ instance.email }
onClick={ onChangeOption }
onClick={ onChangeCheck }
className="mr-8"
label="Email"
/>
@ -242,7 +243,7 @@ const AlertForm = props => {
name="webhook"
type="checkbox"
checked={ instance.webhook }
onClick={ onChangeOption }
onClick={ onChangeCheck }
label="Webhook"
/>
</div>
@ -300,7 +301,7 @@ const AlertForm = props => {
<div className="flex items-center">
<Button
loading={loading}
primary
variant="primary"
type="submit"
disabled={loading || !instance.validate()}
id="submit-button"
@ -314,9 +315,9 @@ const AlertForm = props => {
{instance.exists() && (
<Button
hover
variant="text"
loading={deleting}
type="button"
outline plain
onClick={() => onDelete(instance)}
id="trash-button"
>

View file

@ -6,7 +6,7 @@ import AlertForm from '../AlertForm';
import { connect } from 'react-redux';
import { setShowAlerts } from 'Duck/dashboard';
import { EMAIL, SLACK, WEBHOOK } from 'App/constants/schedule';
import { confirm } from 'UI/Confirmation';
import { confirm } from 'UI';
interface Props {
showModal?: boolean;

View file

@ -1,6 +1,6 @@
import React from 'react'
import cn from 'classnames';
import stl from './alertItem.css';
import stl from './alertItem.module.css';
import AlertTypeLabel from './AlertTypeLabel';
const AlertItem = props => {

View file

@ -1,6 +1,6 @@
import React from 'react'
import cn from 'classnames'
import stl from './alertTypeLabel.css'
import stl from './alertTypeLabel.module.css'
function AlertTypeLabel({ filterKey, type = '' }) {
return (

View file

@ -8,7 +8,7 @@ import AlertForm from './AlertForm';
import { connect } from 'react-redux';
import { setShowAlerts } from 'Duck/dashboard';
import { EMAIL, SLACK, WEBHOOK } from 'App/constants/schedule';
import { confirm } from 'UI/Confirmation';
import { confirm } from 'UI';
const Alerts = props => {
const { webhooks, setShowAlerts } = props;
@ -18,8 +18,8 @@ const Alerts = props => {
props.fetchWebhooks();
}, [])
const slackChannels = webhooks.filter(hook => hook.type === SLACK).map(({ webhookId, name }) => ({ value: webhookId, text: name })).toJS();
const hooks = webhooks.filter(hook => hook.type === WEBHOOK).map(({ webhookId, name }) => ({ value: webhookId, text: name })).toJS();
const slackChannels = webhooks.filter(hook => hook.type === SLACK).map(({ webhookId, name }) => ({ value: webhookId, label: name })).toJS();
const hooks = webhooks.filter(hook => hook.type === WEBHOOK).map(({ webhookId, name }) => ({ value: webhookId, label: name })).toJS();
const saveAlert = instance => {
const wasUpdating = instance.exists();

View file

@ -1,5 +1,6 @@
import React from 'react'
import { Dropdown, TagBadge } from 'UI';
import { Input, TagBadge } from 'UI';
import Select from 'Shared/Select';
const DropdownChips = ({
textFiled = false,
@ -15,7 +16,7 @@ const DropdownChips = ({
onChange(selected.filter(i => i !== id))
}
const onSelect = (e, { name, value }) => {
const onSelect = ({ value }) => {
const newSlected = selected.concat(value);
onChange(newSlected)
};
@ -23,20 +24,20 @@ const DropdownChips = ({
const onKeyPress = e => {
const val = e.target.value;
if (e.key !== 'Enter' || selected.includes(val)) return;
e.preventDefault();
e.stopPropagation();
if (validate && !validate(val)) return;
const newSlected = selected.concat(val);
e.target.value = '';
onChange(newSlected);
e.preventDefault();
e.stopPropagation();
}
const _options = options.filter(item => !selected.includes(item.value))
const renderBadge = item => {
const val = typeof item === 'string' ? item : item.value;
const text = typeof item === 'string' ? item : item.text;
const text = typeof item === 'string' ? item : item.label;
return (
<TagBadge
className={badgeClassName}
@ -52,15 +53,14 @@ const DropdownChips = ({
return (
<div className="w-full">
{textFiled ? (
<input type="text" onKeyPress={onKeyPress} placeholder={placeholder} />
<Input type="text" onKeyPress={onKeyPress} placeholder={placeholder} />
) : (
<Dropdown
<Select
placeholder={placeholder}
search
selection
isSearchable={true}
options={ _options }
name="webhookInput"
value={ '' }
value={null}
onChange={ onSelect }
{...props}
/>

View file

@ -1,6 +1,6 @@
import React from 'react';
import { Button } from 'UI';
import stl from './listItem.css';
import stl from './listItem.module.css';
import cn from 'classnames';
import AlertTypeLabel from '../../AlertTypeLabel';
@ -10,7 +10,7 @@ const ListItem = ({ alert, onClear, loading, onNavigate }) => {
<div className="flex justify-between items-center">
<div className="text-sm">{alert.createdAt && alert.createdAt.toFormat('LLL dd, yyyy, hh:mm a')}</div>
<div className={ cn("invisible", { 'group-hover:visible' : !alert.viewed})} >
<Button plain simple loading={loading} noPadding>
<Button variant="text" loading={loading}>
<span className={ cn("text-sm color-gray-medium", { 'invisible' : loading })} onClick={onClear}>{'IGNORE'}</span>
</Button>
</div>

View file

@ -1,147 +0,0 @@
import React from 'react';
import stl from './notifications.css';
import ListItem from './ListItem';
import { connect } from 'react-redux';
import { Button, SlideModal, Icon, Popup, NoContent, SegmentSelection } from 'UI';
import { fetchList, setViewed, setLastRead, clearAll } from 'Duck/notifications';
import withToggle from 'Components/hocs/withToggle';
import { withRouter } from 'react-router-dom';
import { fetchList as fetchAlerts, init as initAlert } from 'Duck/alerts';
import cn from 'classnames';
const AUTOREFRESH_INTERVAL = 5 * 60 * 1000;
@withToggle('visible', 'toggleVisisble')
@withRouter
class Notifications extends React.Component {
state = { alertType: '' };
constructor(props) {
super(props);
// setTimeout(() => {
// props.fetchList();
// }, 1000);
setInterval(() => {
props.fetchList();
}, AUTOREFRESH_INTERVAL);
}
writeOption = (e, { name, value }) => this.setState({ [ name ]: value });
navigateToUrl = notification => { // TODO should be able to open the alert edit form
if (notification.options.source === 'ALERT') {
const { initAlert } = this.props;
this.props.fetchAlerts().then(function() {
const { alerts } = this.props;
const alert = alerts.find(i => i.alertId === notification.options.sourceId)
initAlert(alert.toJS());
}.bind(this));
}
}
onClearAll = () => {
const { notifications } = this.props;
const firstItem = notifications.first();
this.props.clearAll({ endTimestamp: firstItem.createdAt.ts });
}
onClear = notification => {
this.props.setViewed(notification.notificationId)
}
toggleModal = () => {
this.props.toggleVisisble(!this.props.visible);
}
render() {
const { notifications, visible, loading, clearing, clearingAll } = this.props;
const { alertType } = this.state;
const unReadNotificationsCount = notifications.filter(({viewed}) => !viewed).size
const filteredList = alertType === '' ?
notifications :
notifications.filter(i => i.filterKey === alertType);
return (
<div>
<Popup
trigger={
<div className={ stl.button } onClick={ this.toggleModal } data-active={ visible }>
<div className={ stl.counter } data-hidden={ unReadNotificationsCount === 0 }>
{ unReadNotificationsCount }
</div>
<Icon name="bell" size="18" />
</div>
}
content={ `Alerts` }
size="tiny"
inverted
position="top center"
/>
<SlideModal
title={
<div className="flex items-center justify-between">
<div>Alerts</div>
{ unReadNotificationsCount > 0 && (
<div className="">
<Button
loading={clearingAll}
plain
simple
onClick={this.props.setLastRead}
disabled={unReadNotificationsCount === 0}
noPadding
>
<span
className={ cn("text-sm color-gray-medium", { 'invisible' : clearingAll })}
onClick={this.onClearAll}>
IGNORE ALL
</span>
</Button>
</div>
)}
</div>
}
right
isDisplayed={ visible }
onClose={ visible && this.toggleModal }
bgColor="white"
size="small"
content={
<div className="">
<NoContent
title=""
subtext="There are no alerts to show."
animatedIcon="no-results"
show={ !loading && notifications.size === 0 }
size="small"
>
{
filteredList.map(item => (
<div className="border-b" key={item.key}>
<ListItem
key={item.key}
alert={item}
onClear={() => this.onClear(item)}
loading={clearing}
/>
</div>
))
}
</NoContent>
</div>
}
/>
</div>
);
}
}
export default connect(state => ({
notifications: state.getIn(['notifications', 'list']),
loading: state.getIn(['notifications', 'fetchRequest', 'loading']),
clearing: state.getIn(['notifications', 'setViewed', 'loading']),
clearingAll: state.getIn(['notifications', 'clearAll', 'loading']),
alerts: state.getIn(['alerts', 'list']),
}), { fetchList, setLastRead, setViewed, clearAll, fetchAlerts, initAlert })(Notifications);

View file

@ -0,0 +1,184 @@
import React, { useEffect } from 'react';
import stl from './notifications.module.css';
import ListItem from './ListItem';
import { connect } from 'react-redux';
import { Button, SlideModal, Icon, Popup, NoContent } from 'UI';
import { fetchList, setViewed, clearAll } from 'Duck/notifications';
import { setLastRead } from 'Duck/announcements';
import cn from 'classnames';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import { useModal } from 'App/components/Modal';
import AlertTriggersModal from 'Shared/AlertTriggersModal';
const AUTOREFRESH_INTERVAL = 5 * 60 * 1000;
// @withToggle('visible', 'toggleVisisble')
// @withRouter
// class Notifications extends React.Component {
// state = { alertType: '' };
// constructor(props) {
// super(props);
// // setTimeout(() => {
// // props.fetchList();
// // }, 1000);
// setInterval(() => {
// props.fetchList();
// }, AUTOREFRESH_INTERVAL);
// }
// writeOption = (e, { name, value }) => this.setState({ [ name ]: value });
// navigateToUrl = notification => { // TODO should be able to open the alert edit form
// if (notification.options.source === 'ALERT') {
// const { initAlert } = this.props;
// this.props.fetchAlerts().then(function() {
// const { alerts } = this.props;
// const alert = alerts.find(i => i.alertId === notification.options.sourceId)
// initAlert(alert.toJS());
// }.bind(this));
// }
// }
// onClearAll = () => {
// const { notifications } = this.props;
// const firstItem = notifications.first();
// this.props.clearAll({ endTimestamp: firstItem.createdAt.ts });
// }
// onClear = notification => {
// this.props.setViewed(notification.notificationId)
// }
// toggleModal = () => {
// this.props.toggleVisisble(!this.props.visible);
// }
// render() {
// const { notifications, visible, loading, clearing, clearingAll } = this.props;
// const { alertType } = this.state;
// const unReadNotificationsCount = notifications.filter(({viewed}) => !viewed).size
// const filteredList = alertType === '' ?
// notifications :
// notifications.filter(i => i.filterKey === alertType);
// return (
// <div>
// <Popup
// content={ `Alerts` }
// >
// <div className={ stl.button } onClick={ this.toggleModal } data-active={ visible }>
// <div className={ stl.counter } data-hidden={ unReadNotificationsCount === 0 }>
// { unReadNotificationsCount }
// </div>
// <Icon name="bell" size="18" />
// </div>
// </Popup>
// <SlideModal
// title={
// <div className="flex items-center justify-between">
// <div>Alerts</div>
// { unReadNotificationsCount > 0 && (
// <div className="">
// <Button
// loading={clearingAll}
// variant="text"
// onClick={this.props.setLastRead}
// disabled={unReadNotificationsCount === 0}
// >
// <span
// className={ cn("text-sm color-gray-medium", { 'invisible' : clearingAll })}
// onClick={this.onClearAll}>
// IGNORE ALL
// </span>
// </Button>
// </div>
// )}
// </div>
// }
// right
// isDisplayed={ visible }
// onClose={ visible && this.toggleModal }
// bgColor="white"
// size="small"
// content={
// <div className="">
// <NoContent
// title={
// <div className="flex items-center justify-between">
// <AnimatedSVG name={ICONS.EMPTY_STATE} size="100" />
// </div>
// }
// subtext="There are no alerts to show."
// show={ !loading && notifications.size === 0 }
// size="small"
// >
// {
// filteredList.map(item => (
// <div className="border-b" key={item.key}>
// <ListItem
// key={item.key}
// alert={item}
// onClear={() => this.onClear(item)}
// loading={clearing}
// />
// </div>
// ))
// }
// </NoContent>
// </div>
// }
// />
// </div>
// );
// }
// }
// export default connect(state => ({
// notifications: state.getIn(['notifications', 'list']),
// loading: state.getIn(['notifications', 'fetchRequest', 'loading']),
// clearing: state.getIn(['notifications', 'setViewed', 'loading']),
// clearingAll: state.getIn(['notifications', 'clearAll', 'loading']),
// alerts: state.getIn(['alerts', 'list']),
// }), { fetchList, setLastRead, setViewed, clearAll, fetchAlerts, initAlert })(Notifications);
interface Props {
notifications: any;
fetchList: any;
}
function Notifications(props: Props) {
const { notifications } = props;
const { showModal } = useModal();
const unReadNotificationsCount = notifications.filter(({viewed}: any) => !viewed).size
useEffect(() => {
if (notifications.size === 0) {
props.fetchList();
}
const interval = setInterval(() => {
props.fetchList();
}, AUTOREFRESH_INTERVAL);
return () => clearInterval(interval);
}, []);
return (
<Popup
content={ `Alerts` }
>
<div className={ stl.button } onClick={ () => showModal(<AlertTriggersModal unReadNotificationsCount={unReadNotificationsCount} />, { right: true }) }>
<div className={ stl.counter } data-hidden={ unReadNotificationsCount === 0 }>
{ unReadNotificationsCount }
</div>
<Icon name="bell" size="18" />
</div>
</Popup>
);
}
export default connect((state: any) => ({
notifications: state.getIn(['notifications', 'list']),
}), { fetchList, setLastRead, setViewed, clearAll })(Notifications);

View file

@ -1,11 +1,12 @@
import React from 'react';
import stl from './announcements.css';
import stl from './announcements.module.css';
import ListItem from './ListItem';
import { connect } from 'react-redux';
import { SlideModal, Icon, NoContent, Popup } from 'UI';
import { fetchList, setLastRead } from 'Duck/announcements';
import withToggle from 'Components/hocs/withToggle';
import { withRouter } from 'react-router-dom';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
@withToggle('visible', 'toggleVisisble')
@withRouter
@ -13,7 +14,7 @@ class Announcements extends React.Component {
navigateToUrl = url => {
if (url) {
if (url.startsWith(window.ENV.ORIGIN)) {
if (url.startsWith(window.env.ORIGIN)) {
const { history } = this.props;
var path = new URL(url).pathname
if (path.includes('/metrics')) {
@ -44,20 +45,14 @@ class Announcements extends React.Component {
return (
<div>
<Popup
trigger={
<div className={ stl.button } onClick={ this.toggleModal } data-active={ visible }>
<Popup content={ `Announcements` } >
<div className={ stl.button } onClick={ this.toggleModal } data-active={ visible }>
<div className={ stl.counter } data-hidden={ unReadNotificationsCount === 0 }>
{ unReadNotificationsCount }
</div>
<Icon name="bullhorn" size="18" />
</div>
}
content={ `Announcements` }
size="tiny"
inverted
position="top center"
/>
</Popup>
<SlideModal
title="Announcements"
@ -69,9 +64,13 @@ class Announcements extends React.Component {
content={
<div className="mx-4">
<NoContent
title=""
title={
<div className="flex items-center justify-between">
<AnimatedSVG name={ICONS.EMPTY_STATE} size="100" />
</div>
}
subtext="There are no announcements to show."
animatedIcon="no-results"
// animatedIcon="no-results"
show={ !loading && announcements.size === 0 }
size="small"
>

View file

@ -1,6 +1,6 @@
import React from 'react';
import { Button, Label } from 'UI';
import stl from './listItem.css';
import stl from './listItem.module.css';
const ListItem = ({ announcement, onButtonClick }) => {
return (
@ -17,7 +17,7 @@ const ListItem = ({ announcement, onButtonClick }) => {
<div className="mb-2 text-sm text-justify">{announcement.description}</div>
{announcement.buttonUrl &&
<Button
primary outline size="small"
variant="outline"
onClick={() => onButtonClick(announcement.buttonUrl) }
>
<span className="capitalize">{announcement.buttonText}</span>

View file

@ -1,5 +1,5 @@
import React, { useState } from 'react'
import stl from './ChatControls.css'
import stl from './ChatControls.module.css'
import cn from 'classnames'
import { Button, Icon } from 'UI'
import type { LocalStream } from 'Player/MessageDistributor/managers/LocalStream';
@ -29,14 +29,14 @@ function ChatControls({ stream, endCall, videoEnabled, setVideoEnabled } : Props
<div className={cn(stl.controls, "flex items-center w-full justify-start bottom-0 px-2")}>
<div className="flex items-center">
<div className={cn(stl.btnWrapper, { [stl.disabled]: audioEnabled})}>
<Button plain size="small" onClick={toggleAudio} noPadding className="flex items-center" hover>
<Button varient="text" onClick={toggleAudio} hover>
<Icon name={audioEnabled ? 'mic' : 'mic-mute'} size="16" />
<span className={cn("ml-1 color-gray-medium text-sm", { 'color-red' : audioEnabled })}>{audioEnabled ? 'Mute' : 'Unmute'}</span>
</Button>
</div>
<div className={cn(stl.btnWrapper, { [stl.disabled]: videoEnabled})}>
<Button plain size="small" onClick={toggleVideo} noPadding className="flex items-center" hover>
<Button varient="text" onClick={toggleVideo} hover>
<Icon name={ videoEnabled ? 'camera-video' : 'camera-video-off' } size="16" />
<span className={cn("ml-1 color-gray-medium text-sm", { 'color-red' : videoEnabled })}>{videoEnabled ? 'Stop Video' : 'Start Video'}</span>
</Button>

View file

@ -4,7 +4,7 @@ import VideoContainer from '../components/VideoContainer'
import { Icon, Popup, Button } from 'UI'
import cn from 'classnames'
import Counter from 'App/components/shared/SessionItem/Counter'
import stl from './chatWindow.css'
import stl from './chatWindow.module.css'
import ChatControls from '../ChatControls/ChatControls'
import Draggable from 'react-draggable';
import type { LocalStream } from 'Player/MessageDistributor/managers/LocalStream';

View file

@ -11,8 +11,8 @@ import RequestLocalStream from 'Player/MessageDistributor/managers/LocalStream';
import type { LocalStream } from 'Player/MessageDistributor/managers/LocalStream';
import { toast } from 'react-toastify';
import { confirm } from 'UI/Confirmation';
import stl from './AassistActions.css'
import { confirm } from 'UI';
import stl from './AassistActions.module.css'
function onClose(stream) {
stream.getTracks().forEach(t=>t.stop());
@ -114,25 +114,21 @@ function AssistActions({ toggleChatWindow, userId, calling, annotating, peerConn
<div className={ stl.divider } />
<Popup
trigger={
<div
className={
cn(
'cursor-pointer p-2 flex items-center',
{[stl.disabled]: cannotCall}
)
}
onClick={ onCall ? callObject?.end : confirmCall}
role="button"
>
<IconButton size="small" primary={!onCall} red={onCall} label={onCall ? 'End' : 'Call'} icon="headset" />
</div>
}
content={ cannotCall ? "You dont have the permissions to perform this action." : `Call ${userId ? userId : 'User'}` }
size="tiny"
inverted
position="top right"
/>
>
<div
className={
cn(
'cursor-pointer p-2 flex items-center',
{[stl.disabled]: cannotCall}
)
}
onClick={ onCall ? callObject?.end : confirmCall}
role="button"
>
<IconButton size="small" primary={!onCall} red={onCall} label={onCall ? 'End' : 'Call'} icon="headset" />
</div>
</Popup>
<div className="fixed ml-3 left-0 top-0" style={{ zIndex: 999 }}>
{ onCall && callObject && <ChatWindow endCall={callObject.end} userId={userId} incomeStream={incomeStream} localStream={localStream} /> }

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import { SlideModal, Avatar, TextEllipsis, Icon } from 'UI';
import SessionList from '../SessionList';
import stl from './assistTabs.css'
import stl from './assistTabs.module.css'
interface Props {
userId: any,

View file

@ -1,73 +0,0 @@
import React from 'react';
import { Input, Dropdown, Button } from 'UI';
import styles from './alertForm.css';
const periodOptions = [
{
text: '1 Week',
value: 'week',
},
{
text: '1 Month',
value: 'month',
},
];
const AlertForm = ({
alert, write, onSave, loading, onCancel = null,
}) => (
<div>
<div className={ styles.formGroup }>
<h3 className={ styles.label }>{'Title'}</h3>
<Input
name="name"
value={ alert.name }
onChange={ write }
fluid
placeholder="Name your alert"
/>
</div>
<div className={ styles.formGroup }>
<h3 className={ styles.label }>{'Threshold'}</h3>
<Input
type="number"
name="countThreshold"
value={ alert.countThreshold }
fluid
onChange={ write }
placeholder="E.g. 20"
/>
</div>
<div className={ styles.formGroup }>
<h3 className={ styles.label }>{'For Next'}</h3>
<Dropdown
name="period"
options={ periodOptions }
value={ alert.period }
fluid
onChange={ write }
/>
</div>
<Button
loading={ loading }
onClick={ onSave }
content={ alert.id ? 'Update' : 'Set Alert' }
outline
disabled={ !alert.validate() }
marginRight
/>
{ onCancel &&
<Button
onClick={ onCancel }
content="Cancel"
outline
/>
}
</div>
);
export default AlertForm;

View file

@ -1,57 +0,0 @@
import { connect } from 'react-redux';
import { Icon, SlideModal } from 'UI';
import withToggle from 'HOCs/withToggle';
import { save, edit } from 'Duck/alerts';
import styles from './alertManager.css';
import AlertForm from './AlertForm';
@connect(state => ({
alert: state.getIn([ 'alerts', 'instance' ]),
loading: state.getIn([ 'alerts', 'saveRequest', 'loading' ]),
filter: state.getIn([ 'filters', 'appliedFilter' ]),
}), {
save,
edit,
})
@withToggle('isModalDisplayed', 'toggleModal')
export default class AlertManager extends React.PureComponent {
write = (e, { name, value }) => {
this.props.edit({ [ name ]: value });
}
save = () => {
const { toggleModal, alert, filter } = this.props;
this.props.save(alert.set('filter', filter))
.then(toggleModal);
}
render() {
const {
isModalDisplayed, alert, toggleModal, loading,
} = this.props;
return (
<React.Fragment>
<div className={ styles.button } onClick={ toggleModal }>
<Icon name="search_notification" color="teal" size="16" />
</div>
<SlideModal
title="Alert"
isDisplayed={ isModalDisplayed }
onClose={ toggleModal }
size="small"
content={
<div className={ styles.wrapper }>
<AlertForm
alert={ alert }
onSave={ this.save }
write={ this.write }
loading={ loading }
/>
</div>
}
/>
</React.Fragment>
);
}
}

View file

@ -1,9 +0,0 @@
.formGroup {
margin-bottom: 15px;
& .label {
font-size: 14px;
margin: 0;
margin-bottom: 5px;
}
}

View file

@ -1,20 +0,0 @@
.wrapper {
padding: 20px;
}
.button {
padding: 5px 10px;
cursor: pointer;
display: flex;
align-items: center;
}
.formGroup {
margin-bottom: 15px;
& .label {
font-size: 14px;
margin: 0;
margin-bottom: 5px;
}
}

View file

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

View file

@ -1,10 +0,0 @@
import React from 'react';
import stl from './activeLabel.css';
const ActiveLabel = ({ item, onRemove }) => {
return (
<div className={ stl.wrapper } onClick={ () => onRemove(item) }>{ item.text }</div>
);
};
export default ActiveLabel;

View file

@ -1,93 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import { operatorOptions } from 'Types/filter';
import { Icon } from 'UI';
import { editAttribute, removeAttribute, applyFilter, fetchFilterOptions } from 'Duck/filters';
import { debounce } from 'App/utils';
import { KEYS } from 'Types/filter/customFilter';
import stl from './attributeItem.css'
import AttributeValueField from './AttributeValueField';
import OperatorDropdown from './OperatorDropdown';
import CustomFilters from '../CustomFilters';
import FilterSelectionButton from '../FilterSelectionButton';
const DEFAULT = null;
@connect(state => ({
loadingFilterOptions: state.getIn([ 'filters', 'fetchFilterOptions', 'loading' ]),
filterOptions: state.getIn([ 'filters', 'filterOptions' ]),
}), {
editAttribute,
removeAttribute,
applyFilter,
fetchFilterOptions
})
class AttributeItem extends React.PureComponent {
applyFilter = debounce(this.props.applyFilter, 1000)
fetchFilterOptionsDebounce = debounce(this.props.fetchFilterOptions, 500)
onFilterChange = (name, value, valueIndex) => {
const { index } = this.props;
this.props.editAttribute(index, name, value, valueIndex);
this.applyFilter();
}
removeFilter = () => {
const { index } = this.props;
this.props.removeAttribute(index)
this.applyFilter();
}
handleSearchChange = (e, { searchQuery }) => {
const { filter } = this.props;
this.fetchFilterOptionsDebounce(filter, searchQuery);
}
render() {
const { filter, options, index, loadingFilterOptions, filterOptions } = this.props;
const _operatorOptions = operatorOptions(filter);
let filterLabel = filter.label;
if (filter.type === KEYS.METADATA)
filterLabel = filter.key;
return (
<div className={ stl.wrapper }>
<CustomFilters
index={ index }
filter={ filter }
buttonComponent={ <FilterSelectionButton label={ filterLabel } />}
showFilters={ true }
filterType="filter"
/>
{ filter.type !== KEYS.DURATION &&
<OperatorDropdown
options={ _operatorOptions }
onChange={ this.onFilterChange }
value={ filter.operator || DEFAULT }
/>
}
{
// !filter.hasNoValue &&
<AttributeValueField
filter={ filter }
options={ options }
onChange={ this.onFilterChange }
handleSearchChange={this.handleSearchChange}
loading={loadingFilterOptions}
index={index}
/>
}
<div className={ stl.actions }>
<button className={ stl.button } onClick={ this.removeFilter }>
<Icon name="close" size="14" />
</button>
</div>
</div>
);
}
}
export default AttributeItem;

View file

@ -1,194 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import cn from 'classnames';
import stl from './attributeItem.css'
import { Dropdown } from 'semantic-ui-react';
import { LinkStyledInput, CircularLoader } from 'UI';
import { KEYS } from 'Types/filter/customFilter';
import Event, { TYPES } from 'Types/filter/event';
import CustomFilter from 'Types/filter/customFilter';
import { setActiveKey, addCustomFilter, removeCustomFilter, applyFilter, updateValue } from 'Duck/filters';
import DurationFilter from '../DurationFilter/DurationFilter';
import AutoComplete from '../AutoComplete';
const DEFAULT = null;
const getHeader = (type) => {
if (type === 'LOCATION') return 'Path';
return type;
}
@connect(null, {
setActiveKey,
addCustomFilter,
removeCustomFilter,
applyFilter,
updateValue,
})
class AttributeValueField extends React.PureComponent {
state = {
minDuration: this.props.filter.minDuration,
maxDuration: this.props.filter.maxDuration,
}
onValueChange = (e, { name: key, value }) => {
this.props.addCustomFilter(key, value);
};
onDurationChange = (durationValues) => {
this.setState(durationValues);
}
isAutoComplete = (type) => {
switch (type) {
case TYPES.METADATA:
case TYPES.CLICK:
case TYPES.CONSOLE:
case TYPES.GRAPHQL:
case TYPES.FETCH:
case TYPES.STATEACTION:
case TYPES.USERID:
case TYPES.USERANONYMOUSID:
case TYPES.REVID:
case TYPES.GRAPHQL:
case TYPES.CUSTOM:
case TYPES.LOCATION:
case TYPES.VIEW:
case TYPES.INPUT:
case 'metadata':
return true;
}
return false;
}
handleClose = (e) => {
const { filter, onChange } = this.props;
if (filter.key === KEYS.DURATION) {
const { maxDuration, minDuration, key } = filter;
if (maxDuration || minDuration) return;
if (maxDuration !== this.state.maxDuration ||
minDuration !== this.state.minDuration) {
onChange(e, { name: 'value', value: [this.state.minDuration, this.state.maxDuration] });
}
}
}
renderField() {
const { filter, onChange } = this.props;
if (filter.key === KEYS.DURATION) {
const { maxDuration, minDuration } = this.state;
return (
<DurationFilter
onChange={ this.onDurationChange }
onEnterPress={ this.handleClose }
onBlur={this.handleClose}
minDuration={ minDuration }
maxDuration={ maxDuration }
/>
);
}
const { options = [], handleSearchChange, loading } = this.props;
return (
<Dropdown
className={ cn(stl.filterDropdown) }
placeholder="Select"
name="value"
search
selection
value={ filter.value || DEFAULT }
options={ options }
multiple={options.length > 0 || options.size > 0}
onChange={ onChange }
onSearchChange={handleSearchChange}
icon={ null }
noResultsMessage={loading ? <div>
<CircularLoader loading={ loading } style={ { marginRight: '8px' } } />
</div>: 'No results found.'}
/>
)
}
optionMapping = (values) => {
const { filter } = this.props;
if ([KEYS.USER_DEVICE, KEYS.USER_OS, KEYS.USER_BROWSER, KEYS.REFERRER, KEYS.PLATFORM].indexOf(filter.type) !== -1) {
return values.map(item => ({ type: TYPES.METADATA, value: item })).map(CustomFilter);
} else {
return values.map(Event);
}
}
getParams = filter => {
const params = {};
if (filter.type === TYPES.METADATA) {
params.key = filter.key
}
params.type = filter.type
if (filter.type === TYPES.ERROR && filter.source) {
params.source = filter.source
}
return params;
}
onAddValue = () => {
const { index, filter } = this.props;
this.props.updateValue('filters', index, filter.value.concat(""));
}
onRemoveValue = (valueIndex) => {
const { index, filter } = this.props;
this.props.updateValue('filters', index, filter.value.filter((_, i) => i !== valueIndex));
}
onChange = (name, value, valueIndex) => {
const { index, filter } = this.props;
this.props.updateValue('filters', index, filter.value.map((item, i) => i === valueIndex ? value : item));
}
render() {
// const { filter, onChange } = this.props;
const { filter } = this.props;
const _showAutoComplete = this.isAutoComplete(filter.type);
const _params = _showAutoComplete ? this.getParams(filter) : {};
let _optionsEndpoint= '/events/search';
return (
<React.Fragment>
{ _showAutoComplete ? filter.value.map((v, i) => (
<AutoComplete
name={ 'value' }
endpoint={ _optionsEndpoint }
value={ v }
index={ i }
params={ _params }
optionMapping={this.optionMapping}
onSelect={ (e, { name, value }) => onChange(name, value, i) }
headerText={ <h5 className={ stl.header }>{ getHeader(filter.type) }</h5> }
fullWidth={ (filter.type === TYPES.CONSOLE || filter.type === TYPES.LOCATION || filter.type === TYPES.CUSTOM) && filter.value }
onRemoveValue={() => this.onRemoveValue(i)}
onAddValue={this.onAddValue}
showCloseButton={i !== filter.value.length - 1}
/>
))
: this.renderField()
}
{ filter.type === 'INPUT' &&
<LinkStyledInput
displayLabel="Specify value"
placeholder="Specify value"
name="custom"
onChange={ onChange }
value={filter.custom}
/>
}
</React.Fragment>
);
}
}
export default AttributeValueField;

View file

@ -1,71 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import { countries } from 'App/constants';
import { KEYS } from 'Types/filter/customFilter';
import { addAttribute } from 'Duck/filters';
import AttributeItem from './AttributeItem';
import ListHeader from '../ListHeader';
import logger from 'App/logger';
const DEFAULT = null;
const DEFAULT_OPTION = { text: 'Any', value: DEFAULT };
const toOptions = (values, mapper) => (values ? values
.map(({value}) => ({
text: mapper ? mapper[ value ] : value,
value,
}))
.toJS() : [ DEFAULT_OPTION ]);
const countryOptions = Object.keys(countries).map(i => ({ text: countries[i], value: i }));
@connect(state => ({
filters: state.getIn([ 'filters', 'appliedFilter', 'filters' ]),
filterValues: state.get('filterValues'),
filterOptions: state.getIn([ 'filters', 'filterOptions' ]),
}), {
addAttribute,
})
class Attributes extends React.PureComponent {
getOptions = filter => {
const { filterValues, filterOptions } = this.props;
if (filter.key === KEYS.USER_COUNTRY) {
logger.log('Filters: country')
return countryOptions;
}
if (filter.key === KEYS.METADATA) {
logger.log('Filters: metadata ' + filter.key)
const options = filterValues.get(filter.key);
return options && options.size ? toOptions(options) : [];
}
logger.log('Filters: general filters ' + filter.key)
const options = filterOptions.get(filter.key)
return options && options.size ? toOptions(options.filter(i => !!i)) : []
}
render() {
const { filters } = this.props;
return (
<>
{ filters.size > 0 &&
<div>
<div className="py-1"><ListHeader title="Filters" /></div>
{
filters.map((filter, index) => (
<AttributeItem
key={index}
index={ index }
filter={ filter }
options={ this.getOptions(filter) }
/>
))
}
</div>
}
</>
);
}
}
export default Attributes;

View file

@ -1,19 +0,0 @@
import React from 'react';
import cn from 'classnames';
import { Dropdown, Icon } from 'UI';
import stl from './attributeItem.css'
const OperatorDropdown = ({ options, value, onChange }) => {
return (
<Dropdown
className={ cn(stl.operatorDropdown) }
options={ options }
name="operator"
value={ value }
onChange={ onChange }
icon={ <Icon className="ml-5" name="chevron-down" size="12" /> }
/>
);
};
export default OperatorDropdown;

View file

@ -1,9 +0,0 @@
.wrapper {
padding: 3px 8px;
background-color: $gray-lightest;
border-radius: 10px;
margin-right: 5px;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05) inset;
font-size: 13px;
color: $gray-medium;
}

View file

@ -1,130 +0,0 @@
@import 'icons.css';
.wrapper {
display: flex;
align-items: center;
padding: 8px 15px;
background-color: white;
border-bottom: solid thin $gray-lightest;
&:last-child {
border-bottom: solid thin transparent;
}
&:hover {
background-color: $active-blue;
& .actions {
opacity: 1;
transition: all 0.2s;
}
}
& > div:not(:last-child) {
margin-right: 10px;
}
& .label {
font-weight: 600;
min-width: 80px;
}
& .filterDropdown {
/* height: 28px !important; */
padding: 0 5px !important;
min-height: 28px !important;
display: flex !important;
align-items: center !important;
font-weight: 400;
min-width: 280px !important;
max-width: 75% !important;
flex-wrap: wrap;
padding: 1.9px !important;
background-color: rgba(255, 255, 255, 0.8) !important;
padding-left: 5px !important;
& a {
background-color: $gray-lightest !important;
box-shadow: none !important;
border-radius: 10px !important;
white-space: nowrap !important;
margin: 0 !important;
margin-right: 5px !important;
margin-bottom: 2px !important;
font-size: 13px !important;
font-weight: 400;
display: flex !important;
align-items: center !important;
padding: 3px 5px !important;
& i::before {
display: none;
}
& i::after {
content: '' !important;
@mixin icon close, $gray-dark, 12px;
}
}
& input {
padding: 6px !important;
margin: 0 !important;
color: $gray-medium !important;
}
& .delete.icon {
padding: 0 !important;
display: none;
}
}
}
.operatorDropdown {
font-weight: 400;
height: 28px;
min-width: 60px;
display: flex !important;
align-items: center;
justify-content: space-between;
padding: 0 8px !important;
font-size: 13px;
background-color: rgba(255, 255, 255, 0.8) !important;
border: solid thin rgba(34, 36, 38, 0.15) !important;
border-radius: 4px !important;
color: $gray-darkest !important;
font-size: 14px !important;
&.ui.basic.button {
box-shadow: 0 0 0 1px rgba(62, 170, 175,36,38,.35) inset, 0 0 0 0 rgba(62, 170, 175,.15) inset !important;
}
}
.button {
width: 25px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-left: 10px;
}
.actions {
margin-left: auto;
opacity: 0;
transition: all 0.4s;
}
.inputValue {
height: 28px !important;
width: 180px;
color: $gray-medium !important;
}
.header {
margin-bottom: 10px;
font-size: 13px;
color: #596764;
white-space: nowrap;
text-transform: uppercase;
font-weight: normal;
letter-spacing: 0.1em;
text-align: left;
}

View file

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

View file

@ -5,7 +5,7 @@ import { Input, Icon } from 'UI';
import { debounce } from 'App/utils';
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
import EventSearchInput from 'Shared/EventSearchInput';
import stl from './autoComplete.css';
import stl from './autoComplete.module.css';
import FilterItem from '../CustomFilters/FilterItem';
const TYPE_TO_SEARCH_MSG = "Start typing to search...";

View file

@ -1,5 +1,5 @@
import React from 'react';
import stl from './dropdownItem.css';
import stl from './dropdownItem.module.css';
const DropdownItem = ({ value, onSelect }) => {
return (

View file

@ -1,3 +1,4 @@
import React from 'react';
import cn from 'classnames';
import { connect } from 'react-redux';
import withPageTitle from 'HOCs/withPageTitle';
@ -8,11 +9,10 @@ import { applyFilter, clearEvents, addAttribute } from 'Duck/filters';
import { fetchList as fetchFunnelsList } from 'Duck/funnels';
import { KEYS } from 'Types/filter/customFilter';
import SessionList from './SessionList';
import stl from './bugFinder.css';
import stl from './bugFinder.module.css';
import withLocationHandlers from "HOCs/withLocationHandlers";
import { fetch as fetchFilterVariables } from 'Duck/sources';
import { fetchSources } from 'Duck/customField';
import { RehydrateSlidePanel } from './WatchDogs/components';
import { setFunnelPage } from 'Duck/sessions';
import { setActiveTab } from 'Duck/search';
import SessionsMenu from './SessionsMenu/SessionsMenu';
@ -137,10 +137,6 @@ export default class BugFinder extends React.PureComponent {
<SessionList onMenuItemClick={this.setActiveTab} />
</div>
</div>
<RehydrateSlidePanel
isModalDisplayed={ showRehydratePanel }
onClose={ () => this.setState({ showRehydratePanel: false })}
/>
</div>
);
}

View file

@ -1,28 +0,0 @@
import React, { useCallback, useState } from 'react';
import { connect } from 'react-redux';
import { addEvent, applyFilter, setActiveKey, addAttribute } from 'Duck/filters';
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
import FilterModal from './FilterModal';
export default React.memo(function CustomFilters({
index,
buttonComponent,
filterType,
}) {
const [ displayed, setDisplayed ] = useState(false);
const close = useCallback(() => setDisplayed(false), []);
const toggle = useCallback(() => setDisplayed(d => !d), []);
return (
<OutsideClickDetectingDiv className="relative" onClickOutside={ close }>
<div role="button" onClick={ toggle }>{ buttonComponent || 'Add Step' }</div>
<FilterModal
index={ index }
close={ close }
displayed={ displayed }
filterType={ filterType }
/>
</OutsideClickDetectingDiv>
);
})

View file

@ -1,15 +0,0 @@
import React from 'react';
import { Icon } from 'UI';
import stl from './filterItem.css';
import cn from 'classnames';
const FilterItem = ({ className = '', icon, label, onClick }) => {
return (
<div className={ cn(stl.filterItem, className) } id="filter-item" onClick={ onClick }>
{ icon && <Icon name={ icon } size="16" marginRight="8" /> }
<span className={ stl.label }>{ label }</span>
</div>
);
};
export default FilterItem;

View file

@ -1,220 +0,0 @@
import React from 'react';
import cn from 'classnames';
import { List } from 'immutable';
import { connect } from 'react-redux';
import { getRE } from 'App/utils';
import { defaultFilters, preloadedFilters } from 'Types/filter';
import { TYPES } from 'Types/filter/event';
import CustomFilter, { KEYS } from 'Types/filter/customFilter';
import { applyFilter, setActiveKey, addEvent, removeEvent, setFilterOption, changeEvent, addAttribute, removeAttribute } from 'Duck/filters';
import { NoContent, CircularLoader } from 'UI';
import { debounce } from 'App/utils';
import FilterItem from './FilterItem';
import logger from 'App/logger';
import stl from './filterModal.css';
const customFilterAutoCompleteKeys = ['METADATA', KEYS.CLICK, KEYS.USER_BROWSER, KEYS.USER_OS, KEYS.USER_DEVICE, KEYS.REFERRER]
@connect(state => ({
filter: state.getIn([ 'filters', 'appliedFilter' ]),
customFilters: state.getIn([ 'filters', 'customFilters' ]),
variables: state.getIn([ 'customFields', 'list' ]),
sources: state.getIn([ 'customFields', 'sources' ]),
activeTab: state.getIn([ 'sessions', 'activeTab', 'type' ]),
}), {
applyFilter,
setActiveKey,
addEvent,
removeEvent,
addAttribute,
removeAttribute,
setFilterOption
})
export default class FilterModal extends React.PureComponent {
state = { query: '' }
applyFilter = debounce(this.props.applyFilter, 300);
onFilterClick = (filter, apply) => {
const key = filter.key || filter.type;
if (customFilterAutoCompleteKeys.includes(key)) {
this.props.setFilterOption(key, filter.value ? [{value: filter.value[0], type: key}] : [])
}
this.addFilter(filter);
if (apply || filter.hasNoValue) {
this.applyFilter();
}
}
renderFilterItem(type, filter) {
return (
<FilterItem
className="capitalize"
label={ filter.label || filter.key }
icon={ filter.icon }
onClick={ () => this.onFilterClick(filter) }
/>
);
}
addFilter = (filter) => {
const { index, filterType, filter: { filters } } = this.props;
this.props.close();
if (filter.isFilter || filter.type === 'METADATA') {
logger.log('Adding Filter', filter)
const _index = filterType === 'filter' ? index : undefined; // should add new one if coming from events
const _in = filters.findIndex(e => e.type === 'USERID');
this.props.addAttribute(filter, _in >= 0 ? _in : _index);
} else {
logger.log('Adding Event', filter)
const _index = filterType === 'event' ? index : undefined; // should add new one if coming from filters
this.props.addEvent(filter, false, _index);
}
if (filterType === 'event' && filter.isFilter) { // selected a filter from events
this.props.removeEvent(index);
}
if (filterType === 'filter' && !filter.isFilter) { // selected an event from filters
this.props.removeAttribute(index);
}
};
renderList(type, list) {
const { activeTab } = this.props;
const blocks = [];
for (let j = 0; j < list.length; j++) {
blocks.push(
<div key={`${ j }-block`} className={cn("mr-5", { [stl.disabled]: activeTab === 'live' && list[j].key !== 'USERID' })} >
{ list[ j ] && this.renderFilterItem(type, list[ j ]) }
</div>
);
}
return blocks;
}
test = (value = '') => getRE(this.props.searchQuery, 'i').test(value);
renderEventDropdownItem = filter => (
<FilterItem
key={ filter.actualValue || filter.value }
label={ filter.actualValue || filter.value }
icon={ filter.icon }
onClick={ () => this.onFilterClick(filter, true) }
/>
)
renderEventDropdownPartFromList = (list, headerText) => (list.size > 0 &&
<div className={ cn(stl.filterGroupApi, 'mb-2') }>
<h5 className={ stl.header }>{ headerText }</h5>
{ list.map(this.renderEventDropdownItem) }
</div>
)
renderEventDropdownPart = (type, headerText) => {
const searched = this.props.searchedEvents
.filter(e => e.type === type)
.filter(({ value, target }) => !this.props.loading || this.test(value) || this.test(target && target.label));
return this.renderEventDropdownPartFromList(searched, headerText)
};
renderStaticFiltersDropdownPart = (type, headerText, appliedFilterKeys) => {
if (appliedFilterKeys && appliedFilterKeys.includes(type)) return;
const staticFilters = List(preloadedFilters)
.filter(e => e.type === type)
.filter(({ value, actualValue }) => this.test(actualValue || value))
.map(CustomFilter);
return this.renderEventDropdownPartFromList(staticFilters, headerText)
};
render() {
const {
displayed,
customFilters,
filter,
loading = false,
searchedEvents,
searchQuery = '',
activeTab,
} = this.props;
const { query } = this.state;
const reg = getRE(query, 'i');
const _appliedFilterKeys = filter.filters.map(({type}) => type).toJS();
const filteredList = defaultFilters.map(cat => {
let _keys = [];
if (query.length === 0 && cat.type === 'custom') { // default show limited custom fields
_keys = cat.keys.slice(0, 9).filter(({key}) => reg.test(key))
} else {
_keys = cat.keys.filter(({key}) => reg.test(key));
}
return {
...cat,
keys: _keys
.filter(({key, filterKey}) => !_appliedFilterKeys.includes(filterKey) && !customFilters.has(filterKey || key) && !filter.get(filterKey || key))
}
}).filter(cat => cat.keys.length > 0);
const staticFilters = preloadedFilters
.filter(({ value, actualValue }) => !this.props.loading && this.test(actualValue || value))
return (!displayed ? null :
<div className={ stl.modal }>
{ loading &&
<div style={ {marginBottom: '20px'}}><CircularLoader loading={ loading } /></div>
}
<NoContent
title="No results found."
size="small"
show={ searchQuery !== '' && !loading && staticFilters.length === 0 && searchedEvents.size === 0 }
>
<div className={ stl.filterListDynamic }>
{ searchQuery &&
<React.Fragment>
{this.renderEventDropdownPart(TYPES.USERID, 'User Id')}
{activeTab !== 'live' && (
<>
{this.renderEventDropdownPart(TYPES.METADATA, 'Metadata')}
{this.renderEventDropdownPart(TYPES.CONSOLE, 'Errors')}
{this.renderEventDropdownPart(TYPES.CUSTOM, 'Custom Events')}
{this.renderEventDropdownPart(KEYS.USER_COUNTRY, 'Country', _appliedFilterKeys)}
{this.renderEventDropdownPart(KEYS.USER_BROWSER, 'Browser', _appliedFilterKeys)}
{this.renderEventDropdownPart(KEYS.USER_DEVICE, 'Device', _appliedFilterKeys)}
{this.renderEventDropdownPart(TYPES.LOCATION, 'Page')}
{this.renderEventDropdownPart(TYPES.CLICK, 'Click')}
{this.renderEventDropdownPart(TYPES.FETCH, 'Fetch')}
{this.renderEventDropdownPart(TYPES.INPUT, 'Input')}
{this.renderEventDropdownPart(KEYS.USER_OS, 'Operating System', _appliedFilterKeys)}
{this.renderEventDropdownPart(KEYS.REFERRER, 'Referrer', _appliedFilterKeys)}
{this.renderEventDropdownPart(TYPES.GRAPHQL, 'GraphQL')}
{this.renderEventDropdownPart(TYPES.STATEACTION, 'Store Action')}
{this.renderEventDropdownPart(TYPES.REVID, 'Rev ID')}
</>
)}
</React.Fragment>
}
</div>
{ searchQuery === '' &&
<div className={ stl.filterListStatic }>
{
filteredList.map(category => (
<div className={ cn(stl.filterGroup, 'mr-6 mb-6') } key={category.category}>
<h5 className={ stl.header }>{ category.category }</h5>
<div className={ stl.list }>
{ this.renderList(category.type, category.keys) }
</div>
</div>
))
}
</div>
}
</NoContent>
</div>
);
}
}

View file

@ -1,20 +0,0 @@
.filterItem {
display: flex;
align-items: center;
padding: 8px;
cursor: pointer;
border-radius: 3px;
transition: all 0.4s;
margin-bottom: 5px;
max-width: 100%;
& .label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&:hover {
background-color: $gray-lightest;
transition: all 0.2s;
}
}

View file

@ -1,96 +0,0 @@
.modal {
position: absolute;
left: 0;
background-color: white;
width: -webkit-fill-available;
min-width: 705px;
max-width: calc(100vw - 500px);
border-radius: 3px;
border: solid thin $gray-light;
box-shadow: 0 2px 10px 0 $gray-light;
z-index: 99;
padding: 20px;
}
.hint {
color: $gray-light;
font-size: 12px;
padding-bottom: 5px;
}
h5.title {
margin: 10px 0 3px;
}
.filterListDynamic {
max-height: 350px;
overflow-y: auto;
&::-webkit-scrollbar {
width: 2px;
}
&::-webkit-scrollbar-thumb {
background: transparent;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&:hover {
&::-webkit-scrollbar-track {
background: #f3f3f3;
}
&::-webkit-scrollbar-thumb {
background: $gray-medium;
}
}
& .header {
margin-bottom: 10px;
font-size: 13px;
color: #596764;
white-space: nowrap;
text-transform: uppercase;
font-weight: 600;
letter-spacing: 0.1em;
text-align: left;
}
& .list {
margin-left: -8px;
}
}
.filterListStatic {
display: flex;
flex-wrap: wrap;
flex-direction: column;
max-height: 33rem;
min-height: 20px;
color: $gray-medium;
& .header {
margin-bottom: 10px;
font-size: 13px;
color: #596764;
white-space: nowrap;
text-transform: uppercase;
font-weight: 600;
letter-spacing: 0.1em;
text-align: left;
}
& .list {
margin-left: -8px;
}
& .filterGroup {
width: 205px;
}
}
.disabled {
opacity: 0.5;
pointer-events: none;
}

View file

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

View file

@ -1,3 +1,4 @@
import React from 'react';
import { connect } from 'react-redux';
import { applyFilter } from 'Duck/search';
import { fetchList as fetchFunnelsList } from 'Duck/funnels';

View file

@ -1,66 +0,0 @@
import { Input, Label } from 'semantic-ui-react';
import styles from './durationFilter.css';
const fromMs = value => value ? `${ value / 1000 / 60 }` : ''
const toMs = value => value !== '' ? value * 1000 * 60 : null
export default class DurationFilter extends React.PureComponent {
state = { focused: false }
onChange = (e, { name, value }) => {
const { onChange } = this.props;
if (typeof onChange === 'function') {
onChange({
[ name ]: toMs(value),
});
}
}
onKeyPress = e => {
const { onEnterPress } = this.props;
if (e.key === 'Enter' && typeof onEnterPress === 'function') {
onEnterPress(e);
}
}
render() {
const {
minDuration,
maxDuration,
} = this.props;
return (
<div className={ styles.wrapper }>
<Input
labelPosition="left"
type="number"
placeholder="0 min"
name="minDuration"
value={ fromMs(minDuration) }
onChange={ this.onChange }
className="customInput"
onKeyPress={ this.onKeyPress }
onFocus={() => this.setState({ focused: true })}
onBlur={this.props.onBlur}
>
<Label basic className={ styles.label }>{ 'Min' }</Label>
<input min="1" />
</Input>
<Input
labelPosition="left"
type="number"
placeholder="∞ min"
name="maxDuration"
value={ fromMs(maxDuration) }
onChange={ this.onChange }
className="customInput"
onKeyPress={ this.onKeyPress }
onFocus={() => this.setState({ focused: true })}
onBlur={this.props.onBlur}
>
<Label basic className={ styles.label }>{ 'Max' }</Label>
<input min="1" />
</Input>
</div>
);
}
}

View file

@ -1,24 +0,0 @@
.wrapper {
display: flex;
justify-content: space-between;
& input {
max-width: 85px !important;
font-size: 13px !important;
font-weight: 400 !important;
color: $gray-medium !important;
}
& > div {
&:first-child {
margin-right: 10px;
}
}
}
.label {
font-size: 13px !important;
font-weight: 400 !important;
color: $gray-medium !important;
}

View file

@ -1,32 +0,0 @@
import { TYPES } from 'Types/filter/event';
import cn from 'classnames';
import { Icon } from 'UI';
import cls from './eventDropdownItem.css';
const getText = (event) => {
if (event.type === TYPES.METADATA) {
return `${ event.key }: ${ event.value }`;
}
if (event.target) {
return event.target.label || event.value;
}
return event.value; // both should be?
};
export default function EventDropdownItem({ event }) {
return (
<div className={ cn("flex items-center", cls.eventDropdownItem) }>
<Icon name={ event.icon } size="14" marginRight="10" />
<div
className={ cn(cls.values,{
[ cls.inputType ]: event.type === TYPES.INPUT,
[ cls.clickType ]: event.type === TYPES.CLICK,
[ cls.consoleType ]: event.type === TYPES.CONSOLE,
})}
>
{ getText(event) }
</div>
</div>
);
}

View file

@ -1,109 +0,0 @@
import { connect } from 'react-redux';
// import { DNDSource, DNDTarget } from 'Components/hocs/dnd';
import Event, { TYPES } from 'Types/filter/event';
import { operatorOptions } from 'Types/filter';
import { editEvent, removeEvent, clearEvents, applyFilter } from 'Duck/filters';
import { Icon } from 'UI';
import stl from './eventEditor.css';
import { debounce } from 'App/utils';
import AttributeValueField from '../Attributes/AttributeValueField';
import OperatorDropdown from '../Attributes/OperatorDropdown';
import CustomFilters from '../CustomFilters';
import FilterSelectionButton from '../FilterSelectionButton';
const getPlaceholder = ({ type }) => {
if (type === TYPES.INPUT) return "E.g. First Name";
if (type === TYPES.LOCATION) return "Specify URL / Path";
if (type === TYPES.VIEW) return "Specify View Name";
if (type === TYPES.CONSOLE) return "Specify Error Message";
if (type === TYPES.CUSTOM) return "Specify Custom Event Name";
return '';
};
const getLabel = ({ type }) => {
if (type === TYPES.INPUT) return "Specify Value";
return getPlaceholder({ type });
};
// @DNDTarget('event')
// @DNDSource('event')
@connect(state => ({
isLastEvent: state.getIn([ 'filters', 'appliedFilter', 'events' ]).size === 1,
}), { editEvent, removeEvent, clearEvents, applyFilter })
export default class EventEditor extends React.PureComponent {
applyFilter = debounce(this.props.applyFilter, 1500)
onChange = (e, { name, value, searchType }) => {
const { index } = this.props;
const updFields = { [name]: value };
if (searchType != null) {
updFields.searchType = searchType;
}
this.props.editEvent(index, updFields);
this.applyFilter();
}
onTargetChange = (e, {target}) => {
const { index, event } = this.props;
this.props.editEvent(index, {target});
this.applyFilter();
}
onCheckboxChange = ({ target: { name, checked }}) => {
this.props.editEvent(this.props.index, name, checked);
}
remove = () => {
this.props.removeEvent(this.props.index);
this.applyFilter()
};
render() {
const {
event,
index,
isDragging,
connectDragSource,
connectDropTarget,
} = this.props;
const _operatorOptions = operatorOptions(event);
const dndBtn = connectDragSource(
<button className={ stl.button }><Icon name="drag" size="16" /></button>
);
return connectDropTarget(
<div className={ stl.wrapper } style={ isDragging ? { opacity: 0.5 } : null } >
<div className={ stl.leftSection }>
<div className={ stl.index }>{ index + 1 }</div>
<CustomFilters
index={ index }
filter={ event }
buttonComponent={ <FilterSelectionButton label={ (event.source && event.source !== 'js_exception') ? event.source : event.label } />}
filterType="event"
/>
<OperatorDropdown
options={ _operatorOptions }
onChange={ this.onChange }
value={ event.operator || DEFAULT }
/>
<AttributeValueField
filter={ event }
onChange={ this.onChange }
onTargetChange={ this.onTargetChange }
/>
</div>
<div className={ stl.actions }>
{ dndBtn }
<button className={ stl.button } onClick={ this.remove }>
<Icon name="close" size="14" />
</button>
</div>
</div>
);
}
}

View file

@ -1,200 +0,0 @@
import { connect } from 'react-redux';
import { Input } from 'semantic-ui-react';
// import { DNDContext } from 'Components/hocs/dnd';
import {
addEvent, applyFilter, moveEvent, clearEvents, edit,
addCustomFilter, addAttribute, setSearchQuery, setActiveFlow, setFilterOption
} from 'Duck/filters';
import { fetchList as fetchEventList } from 'Duck/events';
import { debounce } from 'App/utils';
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
import EventEditor from './EventEditor';
import ListHeader from '../ListHeader';
import FilterModal from '../CustomFilters/FilterModal';
import { IconButton, SegmentSelection } from 'UI';
import stl from './eventFilter.css';
import Attributes from '../Attributes/Attributes';
import RandomPlaceholder from './RandomPlaceholder';
import CustomFilters from '../CustomFilters';
import ManageFilters from '../ManageFilters';
import { blink as setBlink } from 'Duck/funnels';
import cn from 'classnames';
import SaveFilterButton from 'Shared/SaveFilterButton';
@connect(state => ({
events: state.getIn([ 'filters', 'appliedFilter', 'events' ]),
appliedFilter: state.getIn([ 'filters', 'appliedFilter' ]),
searchQuery: state.getIn([ 'filters', 'searchQuery' ]),
appliedFilterKeys: state.getIn([ 'filters', 'appliedFilter', 'filters' ])
.map(({type}) => type).toJS(),
searchedEvents: state.getIn([ 'events', 'list' ]),
loading: state.getIn([ 'events', 'loading' ]),
strict: state.getIn([ 'filters', 'appliedFilter', 'strict' ]),
blink: state.getIn([ 'funnels', 'blink' ]),
}), {
applyFilter,
addEvent,
moveEvent,
fetchEventList,
clearEvents,
addCustomFilter,
addAttribute,
setSearchQuery,
setActiveFlow,
setFilterOption,
setBlink,
edit,
})
// @DNDContext
export default class EventFilter extends React.PureComponent {
state = { search: '', showFilterModal: false, showPlacehoder: true }
fetchEventList = debounce(this.props.fetchEventList, 500)
inputRef = React.createRef()
componentDidUpdate(){
const { blink, setBlink } = this.props;
if (blink) {
setTimeout(function() {
setBlink(false)
}, 3000)
}
}
onBlur = () => {
const { searchQuery } = this.props;
this.setState({ showPlacehoder: searchQuery === '' });
}
onFocus = () => {
this.setState({ showPlacehoder: false, showFilterModal: true });
}
onChangeStrict = () => {
this.props.applyFilter({ strict: !this.props.strict });
}
onSearchChange = (e, { value }) => {
this.props.setSearchQuery(value)
if (value !== '') this.fetchEventList({ q: value });
}
onPlaceholderClick = () => {
this.inputRef.current && this.inputRef.current.focus();
}
closeModal = () => {
this.setState({ showPlacehoder: true, showFilterModal: false })
}
onPlaceholderItemClick = (e, filter) => {
e.stopPropagation();
e.preventDefault();
if (Array.isArray(filter)) {
for (var i = 0; i < filter.length; i++) {
this.onPlaceholderItemClick(e, filter[i]);
}
} else if (filter.isFilter) {
this.props.setFilterOption(filter.key, [{ value: filter.value[0], type: filter.key }])
this.props.addAttribute(filter);
}
else
this.props.addEvent(filter);
if (filter.value || filter.hasNoValue) {
this.props.applyFilter();
}
}
clearEvents = () => {
this.props.clearEvents();
this.props.setActiveFlow(null)
}
changeConditionTab = (e, { name, value }) => {
this.props.edit({ [ 'condition' ]: value })
};
render() {
const {
events,
loading,
searchedEvents,
appliedFilterKeys,
appliedFilter,
searchQuery,
blink
} = this.props;
const { showFilterModal, showPlacehoder } = this.state;
const hasFilters = appliedFilter.events.size > 0 || appliedFilter.filters.size > 0;
return (
<OutsideClickDetectingDiv className={ stl.wrapper } onClickOutside={ this.closeModal } >
<FilterModal
close={ this.closeModal }
displayed={ showFilterModal }
loading={ loading }
searchedEvents={ searchedEvents }
searchQuery={ searchQuery }
/>
{ hasFilters &&
<div className={cn("bg-white rounded border-gray-light mt-2 relative", { 'blink-border' : blink })}>
<div className="absolute right-0 top-0 m-3 z-10 flex items-center">
<div className="mr-2">Operator</div>
<SegmentSelection
primary
name="condition"
extraSmall={true}
// className="my-3"
onSelect={ this.changeConditionTab }
value={{ value: appliedFilter.condition }}
list={ [
{ name: 'AND', value: 'and' },
{ name: 'OR', value: 'or' },
{ name: 'THEN', value: 'then' },
]}
/>
</div>
{ events.size > 0 &&
<>
<div className="py-1"><ListHeader title="Events" /></div>
{ events.map((event, i) => (
<EventEditor
index={ i }
key={ event._key }
event={ event }
onDNDMove={ this.props.moveEvent }
/>
)) }
</>
}
<Attributes />
<hr className="divider-light m-0 h-0"/>
<div className="bg-white flex items-center py-2" style={{ borderBottomLeftRadius: '3px', borderBottomRightRadius: '3px'}}>
<div className="mr-auto ml-2">
<CustomFilters
buttonComponent={
<div>
<IconButton icon="plus" label="ADD STEP" primaryText />
</div>
}
showFilters={ true }
/>
</div>
<SaveFilterButton />
<div className="flex items-center">
<div>
<IconButton plain label="CLEAR STEPS" onClick={ this.clearEvents } />
</div>
<ManageFilters />
</div>
</div>
</div>
}
</OutsideClickDetectingDiv>
);
}
}

View file

@ -1,88 +0,0 @@
import React from 'react';
import { RandomElement } from 'UI';
import stl from './randomPlaceholder.css';
import Event, { TYPES } from 'Types/filter/event';
import CustomFilter, { KEYS } from 'Types/filter/customFilter';
const getLabel = (type) => {
if (type === KEYS.MISSING_RESOURCE) return 'Missing Resource';
if (type === KEYS.SLOW_SESSION) return 'Slow Sessions';
if (type === KEYS.USER_COUNTRY) return 'Country';
if (type === KEYS.USER_BROWSER) return 'Browser';
if (type === KEYS.USERID) return 'User Id';
}
const getObject = (type, key) => {
switch(type) {
case TYPES.CLICK:
case TYPES.INPUT:
case TYPES.ERROR:
case TYPES.LOCATION:
return Event({ type, key: type });
case KEYS.JOURNEY:
return [
Event({ type: TYPES.LOCATION, key: TYPES.LOCATION }),
Event({ type: TYPES.LOCATION, key: TYPES.LOCATION }),
Event({ type: TYPES.CLICK, key: TYPES.CLICK })
]
case KEYS.USER_BROWSER:
return CustomFilter({type, key: type, isFilter: true, label: getLabel(type), value: ['Chrome'] });
case TYPES.METADATA:
return CustomFilter({type, key, isFilter: true, label: key });
case TYPES.USERID:
return CustomFilter({type, key, isFilter: true, label: key });
case KEYS.USER_COUNTRY:
return CustomFilter({type, key: type, isFilter: true, value: ['FR'], label: getLabel(type) });
case KEYS.SLOW_SESSION:
case KEYS.MISSING_RESOURCE:
return CustomFilter({type, key: type, hasNoValue: true, isFilter: true, label: getLabel(type) });
}
}
const getList = (onClick, appliedFilterKeys) => {
let list = [
{
key: KEYS.CLICK,
element: <div className={ stl.placeholder }>Find sessions with <span onClick={(e) => onClick(e, getObject(TYPES.CLICK))}>Click</span></div>
},
{
key: KEYS.INPUT,
element: <div className={ stl.placeholder }>Find sessions with <span onClick={(e) => onClick(e, getObject(TYPES.INPUT))}>Input</span></div>
},
{
key: KEYS.ERROR,
element: <div className={ stl.placeholder }>Find sessions with <span onClick={(e) => onClick(e, getObject(TYPES.ERROR))}>Errors</span></div>
},
{
key: KEYS.LOCATION,
element: <div className={ stl.placeholder }>Find sessions with <span onClick={(e) => onClick(e, getObject(TYPES.LOCATION))}>URL</span></div>
},
{
key: TYPES.USERID,
element: <div className={ stl.placeholder }>Find sessions with <span onClick={(e) => onClick(e, getObject(TYPES.USERID))}>User ID</span></div>
},
{
key: KEYS.JOURNEY,
element: <div className={ stl.placeholder }>Find sessions in a <span onClick={(e) => onClick(e, getObject(KEYS.JOURNEY))}>Journey</span></div>
},
{
key: KEYS.USER_COUNTRY,
element: <div className={ stl.placeholder }>Find sessions from <span onClick={(e) => onClick(e, getObject(KEYS.USER_COUNTRY))}>France</span></div>
},
{
key: KEYS.USER_BROWSER,
element: <div className={ stl.placeholder }>Find sessions on <span onClick={(e) => onClick(e, getObject(KEYS.USER_BROWSER))}>Chrome</span></div>
},
]
return list.filter(({key}) => !appliedFilterKeys.includes(key))
}
const RandomPlaceholder = ({ onClick, appliedFilterKeys }) => {
return (
<RandomElement list={ getList(onClick, appliedFilterKeys) } />
);
};
export default RandomPlaceholder;

View file

@ -1,45 +0,0 @@
import cn from 'classnames';
import { TYPES } from 'Types/filter/event';
import { LEVEL } from 'Types/session/log';
import { Icon } from 'UI';
import styles from './typeBadge.css';
function getText(type, source) {
if (type === TYPES.CLICK) return 'Click';
if (type === TYPES.LOCATION) return 'URL';
if (type === TYPES.VIEW) return 'View';
if (type === TYPES.INPUT) return 'Input';
if (type === TYPES.CONSOLE) return 'Console';
if (type === TYPES.GRAPHQL) return 'GraphQL';
if (type === TYPES.ERROR) return 'Error';
if (type === TYPES.STATEACTION) return 'Store Action';
if (type === TYPES.FETCH) return 'Fetch';
if (type === TYPES.REVID) return 'Rev ID';
if (type === TYPES.METADATA) return 'Metadata';
if (type === TYPES.CUSTOM) {
if (!source) return 'Custom';
return (
<React.Fragment >
<Icon name={ `integrations/${ source }` } size="12" inline className={ cn(styles.icon, "mr-5") } />
{ 'Custom' }
</React.Fragment>
);
}
return '?';
}
const TypeBadge = ({ event: { type, level, source } }) => (
<div
className={ cn(styles.badge, {
[ styles.red ]: level === LEVEL.ERROR || level === LEVEL.EXCEPTION,
[ styles.yellow ]: level === LEVEL.WARN,
}) }
>
{ getText(type, source) }
</div>
);
TypeBadge.displayName = 'TypeBadge';
export default TypeBadge;

View file

@ -1,26 +0,0 @@
.eventDropdownItem {
padding: 8px 0;
padding-left: 18px;
border-bottom: solid thin $gray-light;
&:last-child {
border-bottom: solid thin transparent;
}
& .values {
max-width: 400px;
overflow: hidden;
text-overflow: ellipsis;
&.inputType,
&.clickType {
color: $gray-darkest !important;
font-size: 14px;
}
&.consoleType {
font-family: 'menlo', 'monaco', 'consolas', monospace;
font-size: 12px;
}
}
}

View file

@ -1,71 +0,0 @@
@import 'mixins.css';
@import 'icons.css';
.wrapper {
width: 100%;
display: flex;
padding: 8px 15px;
background-color: white;
border-bottom: solid thin $gray-lightest;
transition: all 0.4s;
&:last-child {
border-bottom: solid thin transparent;
}
&:hover {
background-color: $active-blue;
transition: all 0.2s;
& .actions {
opacity: 1;
transition: all 0.2s;
}
}
& .leftSection,
& .actions {
display: flex;
align-items: center;
}
& .leftSection {
flex: 1;
& > div {
margin-right: 10px;
flex-shrink: 0;
}
}
}
.index {
background: $white;
width: 24px;
height: 24px;
border-radius: 12px;
margin-right: 10px;
color: $gray-medium;
font-weight: 300;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1) inset;
}
.button {
width: 25px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-left: 10px;
}
.actions {
opacity: 0;
transition: all 0.4s;
}

View file

@ -1,77 +0,0 @@
.searchField {
box-shadow: none !important;
& input {
box-shadow: none !important;
border-radius: 3 !important;
border: solid thin $gray-light !important;
height: 46px !important;
font-size: 16px;
}
}
.wrapper {
box-shadow: none !important;
position: relative;
& .clearStepsButton {
position: absolute;
bottom: 10px;
right: 10x;
}
}
.randomElement {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: 8;
padding: 15px;
padding-left: 40px;
}
.dropdownMenu {
max-width: 100%;
border-top-left-radius: 0 !important;
border-top-right-radius: 0 !important;
&[data-hidden=true] {
display: none !important;
}
}
.header {
padding: 5px 10px;
letter-spacing: 1.5px;
background-color: $gray-lightest;
color: $gray-medium;
font-size: 12px;
text-transform: uppercase;
}
.dateRange {
color: red;
z-index: 8;
position: absolute;
right: 9px;
top: 9px;
}
.placeholder {
color: $gray-medium;
font-weight: 300;
font-size: 16px;
user-select: none;
& span {
font-weight: 400;
color: $teal;
cursor: pointer;
border-bottom: dashed thin $teal;
&:hover {
color: $teal-dark;
}
}
}

View file

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

View file

@ -1,17 +0,0 @@
.placeholder {
color: $gray-medium;
font-weight: 300;
font-size: 16px;
user-select: none;
& span {
font-weight: 400;
color: $teal;
cursor: pointer;
border-bottom: dashed thin $teal;
&:hover {
color: $teal-dark;
}
}
}

View file

@ -1,23 +0,0 @@
.badge {
font-size: 11px;
border-radius: 3px;
background-color: white;
border: solid thin $gray-light;
padding: 2px 0;
text-align: center;
width: 66px;
margin-right: 10px;
user-select: none;
&.red {
background-color: rgba(204, 0, 0, 0.05);
}
&.yellow {
background-color: rgba(245, 166, 35, 0.05);
}
}
.icon {
vertical-align: text-top;
}

View file

@ -1,6 +1,6 @@
import React from 'react';
import { Icon } from 'UI';
import stl from './filterSelectionButton.css';
import stl from './filterSelectionButton.module.css';
const FilterSelectionButton = ({ label }) => {
return (

View file

@ -1,14 +1,15 @@
import React from 'react';
import { connect } from 'react-redux';
import { Dropdown } from 'semantic-ui-react';
import Select from 'Shared/Select';
import { Icon } from 'UI';
import { sort } from 'Duck/sessions';
import { applyFilter } from 'Duck/search';
import stl from './sortDropdown.css';
import stl from './sortDropdown.module.css';
@connect(null, { sort, applyFilter })
export default class SortDropdown extends React.PureComponent {
state = { value: null }
sort = (e, { value }) => {
sort = ({ value }) => {
this.setState({ value: value })
const [ sort, order ] = value.split('-');
const sign = order === 'desc' ? -1 : 1;
@ -21,14 +22,14 @@ export default class SortDropdown extends React.PureComponent {
render() {
const { options } = this.props;
return (
<Dropdown
<Select
name="sortSessions"
className={ stl.dropdown }
direction="left"
plain
// className={ stl.dropdown }
right
options={ options }
onChange={ this.sort }
defaultValue={ options[ 0 ].value }
icon={null}
icon={ <Icon name="chevron-down" color="gray-dark" size="14" className={stl.dropdownIcon} /> }
/>
);

View file

@ -1,33 +0,0 @@
import { connect } from 'react-redux';
import { Button } from 'UI';
import { applyFilter } from 'Duck/filters';
import styles from './findBlock.css';
@connect(state => ({
eventsCount: state.getIn([ 'filters', 'appliedFilter', 'events' ]).size,
lodaing: state.getIn([ 'sessions', 'loading' ]),
}), {
applyFilter,
})
export default class FindBlock extends React.PureComponent {
onClick = () => this.props.applyFilter()
render() {
const { lodaing, eventsCount } = this.props;
return (
<div className={ styles.findBlock }>
<div>
<Button
className={ styles.findButton }
onClick={ this.onClick }
primary
loading={ lodaing }
disabled={ eventsCount === 0 }
>
{'Find Sessions'}
</Button>
</div>
</div>
);
}
}

View file

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import styles from './insights.css';
import styles from './insights.module.css';
const Insights = ({ insights }) => (
<div className={ styles.notes }>

View file

@ -1,5 +1,5 @@
import React from 'react';
import stl from './listHeader.css';
import stl from './listHeader.module.css';
const ListHeader = ({ title }) => {
return (

View file

@ -1,64 +0,0 @@
import { Button } from 'UI';
import styles from './activeFilterDetails.css';
import cn from 'classnames';
import { BrowserIcon, OsIcon } from 'UI';
import TypeBadge from '../EventFilter/TypeBadge';
export default ({
activeFilter, applyFiltersHandler, removeFilter, loading,
}) => (
<div className={ styles.filterDetails }>
<div className={ styles.title }>
{ activeFilter.name }
</div>
<div>
<div className={ styles.userEvents }>
<div className={ styles.filterLabel }>{ 'User Events' }</div>
<div className={ styles.list }>
<div>
{ activeFilter.events.map((item, i) => (
<div className={ styles.filterType }>
<div className={ styles.indexCount }>{ i+1 }</div>
<TypeBadge event={ item } />
<div className={ styles.value }>{ item.value }</div>
</div>
))}
</div>
</div>
</div>
<div className={ styles.filterType } data-hidden={ !activeFilter.userCountry }>
<div className={ styles.filterLabel }>{ 'Location:' }</div>
<div>
<span className={ styles.badge }>{ activeFilter.userCountry }</span>
</div>
</div>
<div className={ styles.filterType } data-hidden={ !activeFilter.userBrowser }>
<div className={ styles.filterLabel }>{ 'Browser:' }</div>
<div className={ cn('flex items-center', styles.badge) }>
<BrowserIcon browser={ activeFilter.userBrowser || '' } size="16" className="mr-5" />
<span >{ activeFilter.userBrowser }</span>
</div>
</div>
<div className={ styles.filterType } data-hidden={ !activeFilter.userOs }>
<div className={ styles.filterLabel }>{ 'OS:' }</div>
<div className={ cn('flex items-center', styles.badge) }>
<OsIcon os={ activeFilter.userOs || '' } size="16" className="mr-5" />
<span >{ activeFilter.userOs }</span>
</div>
</div>
</div>
<div className={ styles.footer }>
<Button primary marginRight onClick={ () => applyFiltersHandler(activeFilter) }>{ 'Apply' }</Button>
<Button
onClick={ () => removeFilter(activeFilter.id) }
basic
loading={ loading }
>
{ 'Delete' }
</Button>
</div>
</div>
);

View file

@ -1,77 +0,0 @@
import { connect } from 'react-redux';
import { IconButton } from 'UI';
import Funnel from 'Types/funnel';
import {
remove as removeFilter,
setActive as setActiveFilter,
applyFilter,
toggleFilterModal
} from 'Duck/filters';
import {
fetchList as fetchFilterList,
save as saveFunnel
} from 'Duck/funnels';
import withToggle from 'Components/hocs/withToggle';
import SaveModal from './SaveModal';
@withToggle('slideModalDisplayed', 'toggleSlideModal')
@connect(
state =>
({
savedFilters: state.getIn([ 'filters', 'list' ]),
activeFilter: state.getIn([ 'filters', 'activeFilter' ]),
fetching: state.getIn([ 'filters', 'fetchListRequest', 'loading' ]),
loading: state.getIn([ 'filters', 'loading' ]),
saveModalOpen: state.getIn([ 'filters', 'saveModalOpen' ]),
appliedFilter: state.getIn([ 'filters', 'appliedFilter' ]),
customFilters: state.getIn([ 'filters', 'customFilters']),
})
,
{
fetchFilterList,
saveFunnel,
removeFilter,
setActiveFilter,
applyFilter,
toggleFilterModal,
},
)
export default class ManageFilters extends React.PureComponent {
updateFilter = (name, isPublic = false) => {
const { appliedFilter } = this.props;
const savedFilter = Funnel({name, filter: appliedFilter, isPublic });
this.props.saveFunnel(savedFilter).then(function() {
this.props.fetchFilterList();
this.props.toggleFilterModal(false);
}.bind(this));
}
applyFiltersHandler = (filter) => {
this.props.applyFilter(filter);
this.props.toggleSlideModal(false);
}
render() {
const {
saveModalOpen,
appliedFilter,
} = this.props;
return (
<div>
<IconButton
primaryText
className="mr-2"
label="SAVE FUNNEL"
onClick={ () => this.props.toggleFilterModal(true) }
/>
<SaveModal
saveModalOpen={ saveModalOpen }
appliedFilter={ appliedFilter }
toggleFilterModal={ this.props.toggleFilterModal }
updateFilter={ this.updateFilter }
/>
</div>
);
}
}

View file

@ -1,100 +0,0 @@
import { connect } from 'react-redux';
import { Button, Modal, Form, Icon, Checkbox } from 'UI';
import styles from './saveModal.css';
@connect(state => ({
loading: state.getIn([ 'funnels', 'saveRequest', 'loading' ]) || state.getIn([ 'funnels', 'updateRequest', 'loading' ]),
}))
export default class SaveModal extends React.PureComponent {
state = { name: 'Untitled', isPublic: false };
static getDerivedStateFromProps(props) {
if (!props.saveModalOpen) {
return {
name: props.appliedFilter.name || 'Untitled',
};
}
return null;
}
onNameChange = ({ target: { value } }) => {
this.setState({ name: value });
};
onChangeOption = (e, { checked, name }) => this.setState({ [ name ]: !this.state.isPublic })
onSave = () => {
const { toggleFilterModal } = this.props;
const { name, isPublic } = this.state;
if (name.trim() === '') return;
this.props.updateFilter(name.trim(), isPublic);
}
render() {
const {
saveModalOpen,
appliedFilter,
toggleFilterModal,
loading,
} = this.props;
const { name, isPublic } = this.state;
return (
<Modal size="tiny" open={ saveModalOpen }>
<Modal.Header className={ styles.modalHeader }>
<div>{ 'Save Funnel' }</div>
<Icon
role="button"
tabIndex="-1"
color="gray-dark"
size="14"
name="close"
onClick={ () => toggleFilterModal(false) }
/>
</Modal.Header>
<Modal.Content>
<Form onSubmit={this.onSave}>
<Form.Field>
<label>{'Title:'}</label>
<input
autoFocus={ true }
className={ styles.name }
name="name"
value={ name }
onChange={ this.onNameChange }
placeholder="Title"
/>
</Form.Field>
<Form.Field>
<div className="flex items-center">
<Checkbox
name="isPublic"
className="font-medium"
type="checkbox"
checked={ isPublic }
onClick={ () => this.setState({ 'isPublic' : !isPublic }) }
className="mr-3"
/>
<div className="flex items-center cursor-pointer" onClick={ () => this.setState({ 'isPublic' : !isPublic }) }>
<Icon name="user-friends" size="16" />
<span className="ml-2"> Team Visible</span>
</div>
</div>
</Form.Field>
</Form>
</Modal.Content>
<Modal.Actions className="">
<Button
primary
onClick={ this.onSave }
loading={ loading }
>
{ appliedFilter.filterId ? 'Modify' : 'Save' }
</Button>
<Button className={ styles.cancelButton } marginRight onClick={ () => toggleFilterModal(false) }>{ 'Cancel' }</Button>
</Modal.Actions>
</Modal>
);
}
}

View file

@ -1,17 +0,0 @@
import styles from './savedFilterList.css';
export default ({ savedFilters, activeFilter, onFilterClick }) => (
<div className={ styles.filtersContainer }>
{ savedFilters && savedFilters.size > 0 &&
savedFilters.map((filter, index) => filter &&
<div
className={ styles.filter }
data-active={ activeFilter && filter.id === activeFilter.id }
onClick={ () => onFilterClick(filter) }
key={ index }
>
{ filter.name }
</div>)
}
</div>
);

View file

@ -1,85 +0,0 @@
.userEvents {
& .list {
margin-top: 10px;
margin-bottom: 10px;
border: solid thin $gray-light;
border-radius: 3px;
background-color: white;
& .filterType {
border-bottom: solid thin $gray-light;
padding: 8px 10px;
align-items: center;
& .value {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&:last-child {
border-bottom: none;
}
& .indexCount {
width: 20px;
height: 20px;
background-color: white;
border-radius: 50%;
margin-right: 10px;
box-shadow: 0 1px 5px 0 $gray-light;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
}
}
}
}
.filterDetails {
width: 400px;
padding: 20px;
& .title {
font-size: 20px;
margin-bottom: 25px;
}
}
.filterType {
display: flex;
align-items: start;
padding: 10px 0;
font-size: 12px;
}
.filterLabel {
font-weight: bold;
width: 100px;
flex-grow: 0;
flex-shrink: 0;
}
.eventsBadge {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.footer {
margin-top: 30px;
}
.badge {
padding: 5px 10px;
background-color: $gray-light;
margin-right: 10px;
border-radius: 3px;
font-size: 12px;
/* margin-bottom: 10px; */
}
[data-hidden=true] {
display: none;
}

View file

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

View file

@ -1,17 +0,0 @@
.filter {
padding: 15px;
cursor: pointer;
border-top: solid thin $gray-light;
border-bottom: solid thin transparent;
transition: all 0.3s;
&:last-child {
border-bottom: solid thin $gray-light;
}
&[data-active=true],
&:hover {
background-color: $active-blue;
transition: all 0.2s;
}
}

View file

@ -1,80 +0,0 @@
import React, { useState } from 'react'
import { Input, Slider, Button, Popup, CircularLoader } from 'UI';
import { saveCaptureRate, editCaptureRate } from 'Duck/watchdogs';
import { connect } from 'react-redux';
import stl from './sessionCaptureRate.css';
function isPercent(val) {
if (isNaN(+val)) return false;
if (+val > 100 || +val < 0) return false;
return true;
}
const SessionCaptureRate = props => {
const { captureRate, saveCaptureRate, editCaptureRate, loading, onClose } = props;
const _sampleRate = captureRate.get('rate');
if (_sampleRate == null) return null;
const [sampleRate, setSampleRate] = useState(_sampleRate)
const captureAll = captureRate.get('captureAll');
const onSampleRateChange = (e) => {
saveCaptureRate({ rate: sampleRate, captureAll: captureAll }).then(onClose);
}
const onCaptureAllChange = () => saveCaptureRate({ rate: sampleRate, captureAll: !captureAll });
return (
<div>
<Popup
trigger={
<Slider
name="sessionsLive"
onChange={ onCaptureAllChange }
checked={ captureAll }
className={stl.customSlider}
label="Capture All"
/>
}
content={ `Capture All` }
size="tiny"
inverted
position="top center"
/>
{ !captureAll && (
<div className="flex items-center justify-between mt-4 border-t pt-4">
<Input
icon="percent"
name="sampleRate"
disabled={ captureAll }
value={ captureAll ? '100' : sampleRate }
onChange={ ({ target: { value }}) => isPercent(value) && setSampleRate(+value) }
size="small"
className={stl.inputField}
/>
<div>
<Button
primary
onClick={onSampleRateChange}
disabled={loading}
>
<CircularLoader loading={ loading } style={ { marginRight: '8px' } } />
Apply
</Button>
<Button outline onClick={onClose}>
Cancel
</Button>
</div>
</div>
)}
</div>
)
}
export default connect(state => ({
currentProjectId: state.getIn([ 'site', 'siteId' ]),
captureRate: state.getIn(['watchdogs', 'captureRate']),
loading: state.getIn(['watchdogs', 'savingCaptureRate', 'loading']),
}), {
saveCaptureRate, editCaptureRate
})(SessionCaptureRate);

View file

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

View file

@ -1,13 +0,0 @@
.inputField {
max-width: 140px !important;
& label {
font-weight: 300 !important;
}
& input {
max-width: 70px !important;
}
}
.customSlider {
line-height: 20px !important;
}

View file

@ -1,33 +0,0 @@
import React from 'react'
import { connect } from 'react-redux'
import { Loader, NoContent } from 'UI';
import SessionStack from 'Shared/SessionStack/SessionStack'
import FunnelListHeader from 'Components/Funnels/FunnelListHeader';
function SessionFlowList({ activeTab, savedFilters, loading }) {
return (
<div>
<FunnelListHeader activeTab={activeTab} count={0} />
<NoContent
title="No Flows Found!"
subtext="Please try changing your search parameters."
animatedIcon="no-results"
show={ !loading && savedFilters.size === 0 }
>
<Loader loading={ loading }>
{savedFilters.map(item => (
<div className="mb-4" key={item.key}>
<SessionStack flow={item} />
</div>
))}
</Loader>
</NoContent>
</div>
)
}
export default connect(state => ({
loading: state.getIn([ 'filters', 'fetchListRequest', 'loading' ]),
activeTab: state.getIn([ 'sessions', 'activeTab' ]),
savedFilters: state.getIn([ 'filters', 'list' ]),
}), {})(SessionFlowList)

View file

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

View file

@ -1,10 +1,12 @@
import React from 'react';
import { connect } from 'react-redux';
import { Loader, NoContent, Button, Pagination } from 'UI';
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;
@ -94,24 +96,16 @@ export default class SessionList extends React.PureComponent {
return (
<NoContent
title={this.getNoContentMessage(activeTab)}
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"
// animatedIcon="no-results"
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>
)} */}
</div>
}
>
@ -142,18 +136,6 @@ export default class SessionList extends React.PureComponent {
render() {
const { activeTab, allList, total } = this.props;
// var filteredList;
// if (activeTab.type !== ALL && activeTab.type !== 'bookmark' && activeTab.type !== 'live') { // Watchdog sessions
// filteredList = allList.filter(session => activeTab.fits(session))
// } else {
// filteredList = allList
// }
// if (activeTab.type === 'bookmark') {
// filteredList = filteredList.filter(item => item.favorite)
// }
// const _total = activeTab.type === 'all' ? total : allList.size
return (
<div className="">

View file

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { Button } from 'UI';
import styles from './sessionListFooter.css';
import styles from './sessionListFooter.module.css';
const SessionListFooter = ({
displayedCount, totalCount, loading, onLoadMoreClick,

View file

@ -1,14 +1,11 @@
import React, { useEffect } from 'react';
import React from 'react';
import { connect } from 'react-redux';
import { applyFilter } from 'Duck/filters';
import SortDropdown from '../Filters/SortDropdown';
import DateRange from '../DateRange';
import { TimezoneDropdown } from 'UI';
import { numberWithCommas } from 'App/utils';
import DropdownPlain from 'Shared/DropdownPlain';
import SelectDateRange from 'Shared/SelectDateRange';
import { applyFilter } from 'Duck/search';
import Period from 'Types/app/period';
const DEFAULT_SORT = 'startTs';
const DEFAULT_ORDER = 'desc';
const sortOptionsMap = {
'startTs-desc': 'Newest',
'startTs-asc': 'Oldest',
@ -16,16 +13,22 @@ const sortOptionsMap = {
'eventsCount-desc': 'Events Descending',
};
const sortOptions = Object.entries(sortOptionsMap)
.map(([ value, text ]) => ({ value, text }));
.map(([ value, label ]) => ({ value, label }));
function SessionListHeader({
activeTab,
count,
applyFilter,
...props
filter,
}) {
// useEffect(() => { applyFilter({ sort: DEFAULT_SORT, order: DEFAULT_ORDER }) }, [])
const { startDate, endDate, rangeValue } = filter;
const period = new Period({ start: startDate, end: endDate, rangeName: rangeValue });
const onDateChange = (e) => {
const dateValues = e.toJSON();
applyFilter(dateValues);
};
return (
<div className="flex mb-6 justify-between items-end">
<div className="flex items-baseline">
@ -36,22 +39,14 @@ function SessionListHeader({
{ activeTab.type !== 'bookmark' && (
<div className="ml-3 flex items-center">
<span className="mr-2 color-gray-medium">Sessions Captured in</span>
<DateRange />
<SelectDateRange
period={period}
onChange={onDateChange}
/>
</div>
)}
</div>
<div className="flex items-center">
{/* <div className="flex items-center">
<span className="mr-2 color-gray-medium">Session View</span>
<DropdownPlain
options={[
{ text: 'List', value: 'list' },
{ text: 'Grouped', value: 'grouped' }
]}
onChange={() => {}}
value='list'
/>
</div> */}
<div className="flex items-center ml-6">
<span className="mr-2 color-gray-medium">Sort By</span>
<SortDropdown options={ sortOptions }/>
@ -63,4 +58,6 @@ function SessionListHeader({
export default connect(state => ({
activeTab: state.getIn([ 'search', 'activeTab' ]),
period: state.getIn([ 'search', 'period' ]),
filter: state.getIn([ 'search', 'instance' ]),
}), { applyFilter })(SessionListHeader);

View file

@ -1,3 +1,4 @@
import React from 'react';
import { Popup } from 'UI';
export default class Tooltip extends React.PureComponent {
@ -26,15 +27,14 @@ export default class Tooltip extends React.PureComponent {
open={ open }
content={ tooltip }
inverted
trigger={
<span
onMouseEnter={ this.onMouseEnter }
onMouseLeave={ this.onMouseLeave }
>
{ trigger }
</span>
}
/>
>
<span
onMouseEnter={ this.onMouseEnter }
onMouseLeave={ this.onMouseLeave }
>
{ trigger }
</span>
</Popup>
);
}
}

View file

@ -1,9 +1,8 @@
import React from 'react'
import { connect } from 'react-redux';
import { Tooltip } from 'react-tippy'
import cn from 'classnames';
import { SideMenuitem, SavedSearchList, Progress, Popup } from 'UI'
import stl from './sessionMenu.css';
import { SideMenuitem, SavedSearchList, Popup } 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';
@ -25,32 +24,13 @@ function SessionsMenu(props) {
<span>Sessions</span>
</div>
<span className={ cn(stl.manageButton, 'mr-2') } onClick={() => showModal(<SessionSettings />, { right: true })}>
<Tooltip
<Popup
hideOnClick={true}
position="bottom"
size="small"
html={<span>Configure the percentage of sessions <br /> to be captured, timezone and more.</span>}
content={<span>Configure the percentage of sessions <br /> to be captured, timezone and more.</span>}
>
Settings
</Tooltip>
</Popup>
</span>
{/* { !capturingAll && (
<Popup
trigger={
<div
style={{ width: '120px' }}
className="ml-6 cursor-pointer"
onClick={ toggleRehydratePanel }
>
<Progress success percent={ props.captureRate.get('rate') } indicating size="tiny" />
</div>
}
content={ `Capturing ${props.captureRate.get('rate')}% of all sessions. Click to manage capture rate. ` }
size="tiny"
inverted
position="top right"
/>
)} */}
</div>
<div>

Some files were not shown because too many files have changed in this diff Show more