change(ui): remove old reducers, rewrite eventsblock component
This commit is contained in:
parent
0fe5d3c8d3
commit
63857a2f01
27 changed files with 265 additions and 897 deletions
|
|
@ -1,34 +1,27 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import stl from './notifications.module.css';
|
||||
import { connect } from 'react-redux';
|
||||
import { Icon, Tooltip } from 'UI';
|
||||
import { fetchList, setViewed, clearAll } from 'Duck/notifications';
|
||||
import { setLastRead } from 'Duck/announcements';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import AlertTriggersModal from 'Shared/AlertTriggersModal';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
const AUTOREFRESH_INTERVAL = 5 * 60 * 1000;
|
||||
|
||||
interface Props {
|
||||
notifications: any;
|
||||
fetchList: any;
|
||||
}
|
||||
function Notifications(props: Props) {
|
||||
function Notifications() {
|
||||
const { showModal } = useModal();
|
||||
const { notificationStore } = useStore();
|
||||
const count = useObserver(() => notificationStore.notificationsCount);
|
||||
const count = notificationStore.notificationsCount;
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
notificationStore.fetchNotificationsCount();
|
||||
void notificationStore.fetchNotificationsCount();
|
||||
}, AUTOREFRESH_INTERVAL);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<Tooltip title={`Alerts`}>
|
||||
<div
|
||||
className={stl.button}
|
||||
|
|
@ -40,12 +33,7 @@ function Notifications(props: Props) {
|
|||
<Icon name="bell" size="18" color="gray-dark" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
notifications: state.getIn(['notifications', 'list']),
|
||||
}),
|
||||
{ fetchList, setLastRead, setViewed, clearAll }
|
||||
)(Notifications);
|
||||
export default observer(Notifications)
|
||||
|
|
@ -9,7 +9,6 @@ import {
|
|||
init,
|
||||
edit,
|
||||
remove,
|
||||
setAlertMetricId,
|
||||
setActiveWidget,
|
||||
updateActiveState,
|
||||
} from 'Duck/customMetrics';
|
||||
|
|
@ -181,7 +180,6 @@ export default connect(
|
|||
{
|
||||
remove,
|
||||
setShowAlerts,
|
||||
setAlertMetricId,
|
||||
edit,
|
||||
setActiveWidget,
|
||||
updateActiveState,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,5 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import FilterList from 'Shared/Filters/FilterList';
|
||||
import {
|
||||
edit,
|
||||
updateSeries,
|
||||
addSeriesFilterFilter,
|
||||
removeSeriesFilterFilter,
|
||||
editSeriesFilterFilter,
|
||||
editSeriesFilter,
|
||||
} from 'Duck/customMetrics';
|
||||
import { connect } from 'react-redux';
|
||||
import { Button, Icon } from 'UI';
|
||||
import FilterSelection from 'Shared/Filters/FilterSelection';
|
||||
import SeriesName from './SeriesName';
|
||||
|
|
@ -18,21 +9,21 @@ import { observer } from 'mobx-react-lite';
|
|||
interface Props {
|
||||
seriesIndex: number;
|
||||
series: any;
|
||||
edit: typeof edit;
|
||||
updateSeries: typeof updateSeries;
|
||||
onRemoveSeries: (seriesIndex: any) => void;
|
||||
canDelete?: boolean;
|
||||
addSeriesFilterFilter: typeof addSeriesFilterFilter;
|
||||
editSeriesFilterFilter: typeof editSeriesFilterFilter;
|
||||
editSeriesFilter: typeof editSeriesFilter;
|
||||
removeSeriesFilterFilter: typeof removeSeriesFilterFilter;
|
||||
canDelete?: boolean;
|
||||
hideHeader?: boolean;
|
||||
emptyMessage?: any;
|
||||
observeChanges?: () => void;
|
||||
}
|
||||
|
||||
function FilterSeries(props: Props) {
|
||||
const { observeChanges = () => {}, canDelete, hideHeader = false, emptyMessage = 'Add user event or filter to define the series by clicking Add Step.' } = props;
|
||||
const {
|
||||
observeChanges = () => {
|
||||
},
|
||||
canDelete,
|
||||
hideHeader = false,
|
||||
emptyMessage = 'Add user event or filter to define the series by clicking Add Step.'
|
||||
} = props;
|
||||
const [expanded, setExpanded] = useState(true)
|
||||
const { series, seriesIndex } = props;
|
||||
|
||||
|
|
@ -46,7 +37,7 @@ function FilterSeries(props: Props) {
|
|||
observeChanges()
|
||||
}
|
||||
|
||||
const onChangeEventsOrder = (e, { name, value }: any) => {
|
||||
const onChangeEventsOrder = (e: any, { name, value }: any) => {
|
||||
series.filter.updateKey(name, value)
|
||||
observeChanges()
|
||||
}
|
||||
|
|
@ -60,11 +51,11 @@ function FilterSeries(props: Props) {
|
|||
<div className="border rounded bg-white">
|
||||
<div className={cn("border-b px-5 h-12 flex items-center relative", { 'hidden': hideHeader })}>
|
||||
<div className="mr-auto">
|
||||
<SeriesName seriesIndex={seriesIndex} name={series.name} onUpdate={(name) => series.update('name', name) } />
|
||||
<SeriesName seriesIndex={seriesIndex} name={series.name} onUpdate={(name) => series.update('name', name)} />
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex items-center cursor-pointer">
|
||||
<div onClick={props.onRemoveSeries} className={cn("ml-3", {'disabled': !canDelete})}>
|
||||
<div onClick={props.onRemoveSeries} className={cn("ml-3", { 'disabled': !canDelete })}>
|
||||
<Icon name="trash" size="16" />
|
||||
</div>
|
||||
|
||||
|
|
@ -73,17 +64,17 @@ function FilterSeries(props: Props) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{ expanded && (
|
||||
{expanded && (
|
||||
<>
|
||||
<div className="p-5">
|
||||
{ series.filter.filters.length > 0 ? (
|
||||
{series.filter.filters.length > 0 ? (
|
||||
<FilterList
|
||||
filter={series.filter}
|
||||
onUpdateFilter={onUpdateFilter}
|
||||
onRemoveFilter={onRemoveFilter}
|
||||
onChangeEventsOrder={onChangeEventsOrder}
|
||||
/>
|
||||
): (
|
||||
) : (
|
||||
<div className="color-gray-medium">{emptyMessage}</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -103,11 +94,4 @@ function FilterSeries(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default connect(null, {
|
||||
edit,
|
||||
updateSeries,
|
||||
addSeriesFilterFilter,
|
||||
editSeriesFilterFilter,
|
||||
editSeriesFilter,
|
||||
removeSeriesFilterFilter,
|
||||
})(observer(FilterSeries));
|
||||
export default observer(FilterSeries);
|
||||
|
|
@ -1,33 +1,30 @@
|
|||
import React from 'react'
|
||||
import EventsBlock from '../Session_/EventsBlock';
|
||||
import PageInsightsPanel from '../Session_/PageInsightsPanel/PageInsightsPanel'
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import cn from 'classnames';
|
||||
import stl from './rightblock.module.css';
|
||||
|
||||
function RightBlock(props: any) {
|
||||
const { activeTab } = props;
|
||||
const { player, store } = React.useContext(PlayerContext)
|
||||
|
||||
const { eventListNow, playing } = store.get()
|
||||
const currentTimeEventIndex = eventListNow.length > 0 ? eventListNow.length - 1 : 0
|
||||
|
||||
const EventsBlockConnected = () => <EventsBlock playing={playing} player={player} setActiveTab={props.setActiveTab} currentTimeEventIndex={currentTimeEventIndex} />
|
||||
const renderActiveTab = (tab: string) => {
|
||||
switch(tab) {
|
||||
case props.tabs.EVENTS:
|
||||
return <EventsBlockConnected />
|
||||
case props.tabs.HEATMAPS:
|
||||
return <PageInsightsPanel setActiveTab={props.setActiveTab} />
|
||||
}
|
||||
if (activeTab === props.tabs.EVENTS) {
|
||||
return (
|
||||
<div className={cn("flex flex-col bg-white border-l", stl.panel)}>
|
||||
<EventsBlock
|
||||
setActiveTab={props.setActiveTab}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className={cn("flex flex-col bg-white border-l", stl.panel)}>
|
||||
{renderActiveTab(activeTab)}
|
||||
</div>
|
||||
)
|
||||
if (activeTab === props.tabs.HEATMAPS) {
|
||||
return (
|
||||
<div className={cn("flex flex-col bg-white border-l", stl.panel)}>
|
||||
<PageInsightsPanel setActiveTab={props.setActiveTab} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export default observer(RightBlock)
|
||||
export default RightBlock
|
||||
|
|
|
|||
|
|
@ -147,14 +147,6 @@ export default class Event extends React.PureComponent {
|
|||
</button>
|
||||
}
|
||||
<div className={ cls.topBlock }>
|
||||
{/* <div className={ cls.checkbox }>
|
||||
<Checkbox
|
||||
className="customCheckbox"
|
||||
name={ event.key }
|
||||
checked={ selected }
|
||||
onClick={ onCheckboxClick }
|
||||
/>
|
||||
</div> */}
|
||||
<div className={ cls.firstLine }>
|
||||
{ this.renderBody() }
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,16 +5,10 @@ import { PlayerContext } from 'App/components/Session/playerContext';
|
|||
function EventSearch(props) {
|
||||
const { player } = React.useContext(PlayerContext)
|
||||
|
||||
const { onChange, clearSearch, value, header, setActiveTab } = props;
|
||||
const { onChange, value, header, setActiveTab } = props;
|
||||
|
||||
const toggleEvents = () => player.toggleEvents()
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearSearch()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex items-center w-full relative">
|
||||
<div className="flex flex-1 flex-col">
|
||||
|
|
|
|||
|
|
@ -1,249 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import cn from 'classnames';
|
||||
import { Icon } from 'UI';
|
||||
import { List, AutoSizer, CellMeasurer, CellMeasurerCache } from "react-virtualized";
|
||||
import { TYPES } from 'Types/session/event';
|
||||
import { setSelected } from 'Duck/events';
|
||||
import { setEventFilter, filterOutNote } from 'Duck/sessions';
|
||||
import { show as showTargetDefiner } from 'Duck/components/targetDefiner';
|
||||
import EventGroupWrapper from './EventGroupWrapper';
|
||||
import styles from './eventsBlock.module.css';
|
||||
import EventSearch from './EventSearch/EventSearch';
|
||||
|
||||
@connect(state => ({
|
||||
session: state.getIn([ 'sessions', 'current' ]),
|
||||
filteredEvents: state.getIn([ 'sessions', 'filteredEvents' ]),
|
||||
eventsIndex: state.getIn([ 'sessions', 'eventsIndex' ]),
|
||||
selectedEvents: state.getIn([ 'events', 'selected' ]),
|
||||
targetDefinerDisplayed: state.getIn([ 'components', 'targetDefiner', 'isDisplayed' ]),
|
||||
testsAvaliable: false,
|
||||
}), {
|
||||
showTargetDefiner,
|
||||
setSelected,
|
||||
setEventFilter,
|
||||
filterOutNote
|
||||
})
|
||||
export default class EventsBlock extends React.Component {
|
||||
state = {
|
||||
editingEvent: null,
|
||||
mouseOver: false,
|
||||
query: ''
|
||||
}
|
||||
|
||||
scroller = React.createRef();
|
||||
cache = new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
defaultHeight: 300
|
||||
});
|
||||
|
||||
write = ({ target: { value, name } }) => {
|
||||
const { filter } = this.state;
|
||||
this.setState({ query: value })
|
||||
this.props.setEventFilter({ query: value, filter })
|
||||
|
||||
setTimeout(() => {
|
||||
if (!this.scroller.current) return;
|
||||
|
||||
this.scroller.current.scrollToRow(0);
|
||||
}, 100)
|
||||
}
|
||||
|
||||
clearSearch = () => {
|
||||
const { filter } = this.state;
|
||||
this.setState({ query: '' })
|
||||
this.props.setEventFilter({ query: '', filter })
|
||||
if (this.scroller.current) {
|
||||
this.scroller.current.forceUpdateGrid();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (!this.scroller.current) return;
|
||||
|
||||
this.scroller.current.scrollToRow(0);
|
||||
}, 100)
|
||||
}
|
||||
|
||||
onSetEventFilter = (e, { name, value }) => {
|
||||
const { query } = this.state;
|
||||
this.setState({ filter: value })
|
||||
this.props.setEventFilter({ filter: value, query });
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.targetDefinerDisplayed && !this.props.targetDefinerDisplayed) {
|
||||
this.setState({ editingEvent: null });
|
||||
}
|
||||
if (prevProps.session !== this.props.session) { // Doesn't happen
|
||||
this.cache = new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
defaultHeight: 300
|
||||
});
|
||||
}
|
||||
if (prevProps.currentTimeEventIndex !== this.props.currentTimeEventIndex &&
|
||||
this.scroller.current !== null) {
|
||||
this.scroller.current.forceUpdateGrid();
|
||||
if (!this.state.mouseOver) {
|
||||
this.scroller.current.scrollToRow(this.props.currentTimeEventIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onCheckboxClick(e, event) {
|
||||
e.stopPropagation();
|
||||
const {
|
||||
session: { events },
|
||||
selectedEvents,
|
||||
} = this.props;
|
||||
|
||||
this.props.player.pause();
|
||||
|
||||
let newSelectedSet;
|
||||
const wasSelected = selectedEvents.contains(event);
|
||||
if (wasSelected) {
|
||||
newSelectedSet = selectedEvents.remove(event);
|
||||
} else {
|
||||
newSelectedSet = selectedEvents.add(event);
|
||||
}
|
||||
|
||||
let selectNextLoad = false;
|
||||
events.reverse().forEach((sessEvent) => {
|
||||
if (sessEvent.type === TYPES.LOCATION) {
|
||||
if (selectNextLoad) {
|
||||
newSelectedSet = newSelectedSet.add(sessEvent);
|
||||
}
|
||||
selectNextLoad = false;
|
||||
} else if (newSelectedSet.contains(sessEvent)) {
|
||||
selectNextLoad = true;
|
||||
}
|
||||
});
|
||||
this.props.setSelected(newSelectedSet);
|
||||
}
|
||||
|
||||
onEventClick = (e, event) => this.props.player.jump(event.time)
|
||||
|
||||
onMouseOver = () => this.setState({ mouseOver: true })
|
||||
onMouseLeave = () => this.setState({ mouseOver: false })
|
||||
|
||||
get eventsList() {
|
||||
const { session: { notesWithEvents }, filteredEvents } = this.props
|
||||
const usedEvents = filteredEvents || notesWithEvents
|
||||
|
||||
return usedEvents
|
||||
}
|
||||
|
||||
renderGroup = ({ index, key, style, parent }) => {
|
||||
const {
|
||||
selectedEvents,
|
||||
currentTimeEventIndex,
|
||||
testsAvaliable,
|
||||
playing,
|
||||
eventsIndex,
|
||||
filterOutNote,
|
||||
} = this.props;
|
||||
|
||||
const { query } = this.state;
|
||||
const _events = this.eventsList
|
||||
const isLastEvent = index === _events.size - 1;
|
||||
const isLastInGroup = isLastEvent || _events.get(index + 1).type === TYPES.LOCATION;
|
||||
const event = _events.get(index);
|
||||
const isNote = !!event.noteId
|
||||
const isSelected = selectedEvents.includes(event);
|
||||
const isCurrent = index === currentTimeEventIndex;
|
||||
const isEditing = this.state.editingEvent === event;
|
||||
|
||||
const heightBug = index === 0 && event.type === TYPES.LOCATION && event.referrer ? { top: 2 } : {}
|
||||
return (
|
||||
<CellMeasurer
|
||||
key={key}
|
||||
cache={this.cache}
|
||||
parent={parent}
|
||||
rowIndex={index}
|
||||
>
|
||||
{({measure, registerChild}) => (
|
||||
<div style={{ ...style, ...heightBug }} ref={registerChild}>
|
||||
<EventGroupWrapper
|
||||
query={query}
|
||||
presentInSearch={eventsIndex.includes(index)}
|
||||
isFirst={index==0}
|
||||
mesureHeight={measure}
|
||||
onEventClick={ this.onEventClick }
|
||||
onCheckboxClick={ this.onCheckboxClick }
|
||||
event={ event }
|
||||
isLastEvent={ isLastEvent }
|
||||
isLastInGroup={ isLastInGroup }
|
||||
isSelected={ isSelected }
|
||||
isCurrent={ isCurrent }
|
||||
isEditing={ isEditing }
|
||||
showSelection={ testsAvaliable && !playing }
|
||||
isNote={isNote}
|
||||
filterOutNote={filterOutNote}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</CellMeasurer>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { query } = this.state;
|
||||
const {
|
||||
session: {
|
||||
events,
|
||||
},
|
||||
setActiveTab,
|
||||
} = this.props;
|
||||
|
||||
const _events = this.eventsList
|
||||
|
||||
const isEmptySearch = query && (_events.size === 0 || !_events)
|
||||
return (
|
||||
<>
|
||||
<div className={ cn(styles.header, 'p-4') }>
|
||||
<div className={ cn(styles.hAndProgress, 'mt-3') }>
|
||||
<EventSearch
|
||||
onChange={this.write}
|
||||
clearSearch={this.clearSearch}
|
||||
setActiveTab={setActiveTab}
|
||||
value={query}
|
||||
header={
|
||||
<div className="text-xl">User Steps <span className="color-gray-medium">{ events.size }</span></div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={ cn("flex-1 px-4 pb-4", styles.eventsList) }
|
||||
id="eventList"
|
||||
data-openreplay-masked
|
||||
onMouseOver={ this.onMouseOver }
|
||||
onMouseLeave={ this.onMouseLeave }
|
||||
>
|
||||
{isEmptySearch && (
|
||||
<div className='flex items-center'>
|
||||
<Icon name="binoculars" size={18} />
|
||||
<span className='ml-2'>No Matching Results</span>
|
||||
</div>
|
||||
)}
|
||||
<AutoSizer disableWidth>
|
||||
{({ height }) => (
|
||||
<List
|
||||
ref={this.scroller}
|
||||
className={ styles.eventsList }
|
||||
height={height + 10}
|
||||
width={248}
|
||||
overscanRowCount={6}
|
||||
itemSize={230}
|
||||
rowCount={_events.size}
|
||||
deferredMeasurementCache={this.cache}
|
||||
rowHeight={this.cache.rowHeight}
|
||||
rowRenderer={this.renderGroup}
|
||||
scrollToAlignment="start"
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
187
frontend/app/components/Session_/EventsBlock/EventsBlock.tsx
Normal file
187
frontend/app/components/Session_/EventsBlock/EventsBlock.tsx
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import cn from 'classnames';
|
||||
import { Icon } from 'UI';
|
||||
import { List, AutoSizer, CellMeasurer } from "react-virtualized";
|
||||
import { TYPES } from 'Types/session/event';
|
||||
import { setEventFilter, filterOutNote } from 'Duck/sessions';
|
||||
import { show as showTargetDefiner } from 'Duck/components/targetDefiner';
|
||||
import EventGroupWrapper from './EventGroupWrapper';
|
||||
import styles from './eventsBlock.module.css';
|
||||
import EventSearch from './EventSearch/EventSearch';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { RootStore } from 'App/duck'
|
||||
import { List as ImmList } from 'immutable'
|
||||
import useCellMeasurerCache from 'Components/shared/DevTools/useCellMeasurerCache'
|
||||
|
||||
interface IProps {
|
||||
setEventFilter: (filter: { query: string }) => void
|
||||
filteredEvents: ImmList<Record<string, any>>
|
||||
setActiveTab: (tab?: string) => void
|
||||
query: string
|
||||
session: Record<string, any>
|
||||
filterOutNote: (id: string) => void
|
||||
eventsIndex: number[]
|
||||
}
|
||||
|
||||
function EventsBlock(props: IProps) {
|
||||
const [mouseOver, setMouseOver] = React.useState(true)
|
||||
const scroller = React.useRef<List>(null)
|
||||
const cache = useCellMeasurerCache(undefined, {
|
||||
fixedWidth: true,
|
||||
defaultHeight: 300
|
||||
});
|
||||
|
||||
const { store, player } = React.useContext(PlayerContext)
|
||||
|
||||
const { eventListNow, playing } = store.get()
|
||||
|
||||
const { session: { events, notesWithEvents }, filteredEvents,
|
||||
eventsIndex,
|
||||
filterOutNote,
|
||||
query,
|
||||
setActiveTab,
|
||||
} = props
|
||||
const currentTimeEventIndex = eventListNow.length > 0 ? eventListNow.length - 1 : 0
|
||||
const usedEvents = filteredEvents || notesWithEvents
|
||||
|
||||
const write = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
|
||||
props.setEventFilter({ query: value })
|
||||
|
||||
setTimeout(() => {
|
||||
if (!scroller.current) return;
|
||||
|
||||
scroller.current.scrollToRow(0);
|
||||
}, 100)
|
||||
}
|
||||
|
||||
const clearSearch = () => {
|
||||
props.setEventFilter({ query: '' })
|
||||
if (scroller.current) {
|
||||
scroller.current.forceUpdateGrid();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (!scroller.current) return;
|
||||
|
||||
scroller.current.scrollToRow(0);
|
||||
}, 100)
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
clearSearch()
|
||||
}
|
||||
}, [])
|
||||
React.useEffect(() => {
|
||||
if (scroller.current) {
|
||||
scroller.current.forceUpdateGrid();
|
||||
if (!mouseOver) {
|
||||
scroller.current.scrollToRow(currentTimeEventIndex);
|
||||
}
|
||||
}
|
||||
}, [currentTimeEventIndex])
|
||||
|
||||
const onEventClick = (_: React.MouseEvent, event: any) => player.jump(event.time)
|
||||
const onMouseOver = () => setMouseOver(true)
|
||||
const onMouseLeave = () => setMouseOver(false)
|
||||
|
||||
const renderGroup = ({ index, key, style, parent }: { index: number; key: string; style: any; parent: any }) => {
|
||||
const isLastEvent = index === usedEvents.size - 1;
|
||||
const isLastInGroup = isLastEvent || usedEvents.get(index + 1)?.type === TYPES.LOCATION;
|
||||
const event = usedEvents.get(index);
|
||||
const isNote = !!event?.noteId
|
||||
const isCurrent = index === currentTimeEventIndex;
|
||||
|
||||
const heightBug = index === 0 && event?.type === TYPES.LOCATION && event.referrer ? { top: 2 } : {}
|
||||
return (
|
||||
<CellMeasurer
|
||||
key={key}
|
||||
cache={cache}
|
||||
parent={parent}
|
||||
rowIndex={index}
|
||||
>
|
||||
{({measure, registerChild}) => (
|
||||
<div style={{ ...style, ...heightBug }} ref={registerChild}>
|
||||
<EventGroupWrapper
|
||||
query={query}
|
||||
presentInSearch={eventsIndex.includes(index)}
|
||||
isFirst={index==0}
|
||||
mesureHeight={measure}
|
||||
onEventClick={ onEventClick }
|
||||
event={ event }
|
||||
isLastEvent={ isLastEvent }
|
||||
isLastInGroup={ isLastInGroup }
|
||||
isCurrent={ isCurrent }
|
||||
showSelection={ !playing }
|
||||
isNote={isNote}
|
||||
filterOutNote={filterOutNote}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</CellMeasurer>
|
||||
);
|
||||
}
|
||||
|
||||
const isEmptySearch = query && (usedEvents.size === 0 || !usedEvents)
|
||||
return (
|
||||
<>
|
||||
<div className={ cn(styles.header, 'p-4') }>
|
||||
<div className={ cn(styles.hAndProgress, 'mt-3') }>
|
||||
<EventSearch
|
||||
onChange={write}
|
||||
setActiveTab={setActiveTab}
|
||||
value={query}
|
||||
header={
|
||||
<div className="text-xl">User Steps <span className="color-gray-medium">{ events.size }</span></div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={ cn("flex-1 px-4 pb-4", styles.eventsList) }
|
||||
id="eventList"
|
||||
data-openreplay-masked
|
||||
onMouseOver={ onMouseOver }
|
||||
onMouseLeave={ onMouseLeave }
|
||||
>
|
||||
{isEmptySearch && (
|
||||
<div className='flex items-center'>
|
||||
<Icon name="binoculars" size={18} />
|
||||
<span className='ml-2'>No Matching Results</span>
|
||||
</div>
|
||||
)}
|
||||
<AutoSizer disableWidth>
|
||||
{({ height }) => (
|
||||
<List
|
||||
ref={scroller}
|
||||
className={ styles.eventsList }
|
||||
height={height + 10}
|
||||
width={248}
|
||||
overscanRowCount={6}
|
||||
itemSize={230}
|
||||
rowCount={usedEvents.size}
|
||||
deferredMeasurementCache={cache}
|
||||
rowHeight={cache.rowHeight}
|
||||
rowRenderer={renderGroup}
|
||||
scrollToAlignment="start"
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect((state: RootStore) => ({
|
||||
session: state.getIn([ 'sessions', 'current' ]),
|
||||
filteredEvents: state.getIn([ 'sessions', 'filteredEvents' ]),
|
||||
query: state.getIn(['sessions', 'eventsQuery']),
|
||||
eventsIndex: state.getIn([ 'sessions', 'eventsIndex' ]),
|
||||
targetDefinerDisplayed: state.getIn([ 'components', 'targetDefiner', 'isDisplayed' ]),
|
||||
}), {
|
||||
showTargetDefiner,
|
||||
setEventFilter,
|
||||
filterOutNote
|
||||
})(observer(EventsBlock))
|
||||
|
|
@ -13,7 +13,6 @@ const JUMP_OFFSET = 1000;
|
|||
interface Props {
|
||||
filters: any;
|
||||
fetchInsights: (filters: Record<string, any>) => void;
|
||||
urls: [];
|
||||
insights: any;
|
||||
events: Array<any>;
|
||||
urlOptions: Array<any>;
|
||||
|
|
|
|||
|
|
@ -1,112 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import FilterList from 'Shared/Filters/FilterList';
|
||||
import {
|
||||
edit,
|
||||
updateSeries,
|
||||
addSeriesFilterFilter,
|
||||
removeSeriesFilterFilter,
|
||||
editSeriesFilterFilter,
|
||||
editSeriesFilter,
|
||||
} from 'Duck/customMetrics';
|
||||
import { connect } from 'react-redux';
|
||||
import { IconButton, Icon } from 'UI';
|
||||
import FilterSelection from '../../Filters/FilterSelection';
|
||||
import SeriesName from './SeriesName';
|
||||
import cn from 'classnames';
|
||||
|
||||
interface Props {
|
||||
seriesIndex: number;
|
||||
series: any;
|
||||
edit: typeof edit;
|
||||
updateSeries: typeof updateSeries;
|
||||
onRemoveSeries: (seriesIndex) => void;
|
||||
canDelete?: boolean;
|
||||
addSeriesFilterFilter: typeof addSeriesFilterFilter;
|
||||
editSeriesFilterFilter: typeof editSeriesFilterFilter;
|
||||
editSeriesFilter: typeof editSeriesFilter;
|
||||
removeSeriesFilterFilter: typeof removeSeriesFilterFilter;
|
||||
hideHeader?: boolean;
|
||||
emptyMessage?: any;
|
||||
}
|
||||
|
||||
function FilterSeries(props: Props) {
|
||||
const { canDelete, hideHeader = false, emptyMessage = 'Add user event or filter to define the series by clicking Add Step.' } = props;
|
||||
const [expanded, setExpanded] = useState(true)
|
||||
const { series, seriesIndex } = props;
|
||||
|
||||
const onAddFilter = (filter) => {
|
||||
filter.value = [""]
|
||||
if (filter.hasOwnProperty('filters') && Array.isArray(filter.filters)) {
|
||||
filter.filters = filter.filters.map(i => ({ ...i, value: [""] }))
|
||||
}
|
||||
props.addSeriesFilterFilter(seriesIndex, filter);
|
||||
}
|
||||
|
||||
const onUpdateFilter = (filterIndex, filter) => {
|
||||
props.editSeriesFilterFilter(seriesIndex, filterIndex, filter);
|
||||
}
|
||||
|
||||
const onChangeEventsOrder = (e, { name, value }) => {
|
||||
props.editSeriesFilter(seriesIndex, { eventsOrder: value });
|
||||
}
|
||||
|
||||
const onRemoveFilter = (filterIndex) => {
|
||||
props.removeSeriesFilterFilter(seriesIndex, filterIndex);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border rounded bg-white">
|
||||
<div className={cn("border-b px-5 h-12 flex items-center relative", { 'hidden': hideHeader })}>
|
||||
<div className="mr-auto">
|
||||
<SeriesName seriesIndex={seriesIndex} name={series.name} onUpdate={(name) => props.updateSeries(seriesIndex, { name }) } />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center cursor-pointer" >
|
||||
<div onClick={props.onRemoveSeries} className={cn("ml-3", {'disabled': !canDelete})}>
|
||||
<Icon name="trash" size="16" />
|
||||
</div>
|
||||
|
||||
<div onClick={() => setExpanded(!expanded)} className="ml-3">
|
||||
<Icon name="chevron-down" size="16" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{ expanded && (
|
||||
<>
|
||||
<div className="p-5">
|
||||
{ series.filter.filters.size > 0 ? (
|
||||
<FilterList
|
||||
filter={series.filter}
|
||||
onUpdateFilter={onUpdateFilter}
|
||||
onRemoveFilter={onRemoveFilter}
|
||||
onChangeEventsOrder={onChangeEventsOrder}
|
||||
/>
|
||||
): (
|
||||
<div className="color-gray-medium">{emptyMessage}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="border-t h-12 flex items-center">
|
||||
<div className="-mx-4 px-6">
|
||||
<FilterSelection
|
||||
filter={undefined}
|
||||
onFilterClick={onAddFilter}
|
||||
>
|
||||
<IconButton primaryText label="ADD STEP" icon="plus" />
|
||||
</FilterSelection>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(null, {
|
||||
edit,
|
||||
updateSeries,
|
||||
addSeriesFilterFilter,
|
||||
editSeriesFilterFilter,
|
||||
editSeriesFilter,
|
||||
removeSeriesFilterFilter,
|
||||
})(FilterSeries);
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Icon } from 'UI';
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
onUpdate: (name) => void;
|
||||
seriesIndex?: number;
|
||||
}
|
||||
function SeriesName(props: Props) {
|
||||
const { seriesIndex = 1 } = props;
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [name, setName] = useState(props.name)
|
||||
const ref = useRef<any>(null)
|
||||
|
||||
const write = ({ target: { value, name } }) => {
|
||||
setName(value)
|
||||
}
|
||||
|
||||
const onBlur = () => {
|
||||
setEditing(false)
|
||||
props.onUpdate(name)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (editing) {
|
||||
ref.current.focus()
|
||||
}
|
||||
}, [editing])
|
||||
|
||||
useEffect(() => {
|
||||
setName(props.name)
|
||||
}, [props.name])
|
||||
|
||||
// const { name } = props;
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{ editing ? (
|
||||
<input
|
||||
ref={ ref }
|
||||
name="name"
|
||||
className="fluid border-0 -mx-2 px-2 h-8"
|
||||
value={name}
|
||||
// readOnly={!editing}
|
||||
onChange={write}
|
||||
onBlur={onBlur}
|
||||
onFocus={() => setEditing(true)}
|
||||
/>
|
||||
) : (
|
||||
<div className="text-base h-8 flex items-center border-transparent">{name && name.trim() === '' ? 'Series ' + (seriesIndex + 1) : name }</div>
|
||||
)}
|
||||
|
||||
<div className="ml-3 cursor-pointer" onClick={() => setEditing(true)}><Icon name="pencil" size="14" /></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SeriesName;
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './SeriesName';
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './FilterSeries'
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import { useMemo } from 'react'
|
||||
import { CellMeasurerCache } from 'react-virtualized';
|
||||
import { CellMeasurerCache, CellMeasurerCacheParams } from 'react-virtualized';
|
||||
import useLatestRef from 'App/hooks/useLatestRef'
|
||||
|
||||
|
||||
export default function useCellMeasurerCache(itemList: any[]) {
|
||||
const filteredListRef = useLatestRef(itemList)
|
||||
export default function useCellMeasurerCache(itemList?: any[], options?: CellMeasurerCacheParams) {
|
||||
const filteredListRef = itemList ? useLatestRef(itemList) : undefined
|
||||
return useMemo(() => new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
keyMapper: (index) => filteredListRef.current[index],
|
||||
keyMapper: filteredListRef ? (index) => filteredListRef.current[index] : undefined,
|
||||
...options
|
||||
}), [])
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
|
||||
redux -> other storage ::<< Entities + Lists + relations <|> methods:: crud. request declaration -> request realisation with middleware -< (uses) MODEL
|
||||
|
||||
|
||||
|
||||
!request declaration
|
||||
|
||||
|
||||
|
||||
action/request formatter => ReducerModule Fabrique =>
|
||||
|
||||
|
||||
class ReducerModule {
|
||||
_ns = "common"
|
||||
_switch = {}
|
||||
_n = 0
|
||||
|
||||
constructor(namespace) {
|
||||
this._ns = namespace
|
||||
}
|
||||
|
||||
/**
|
||||
Action: state => newState | { reduce: state, action => newState, creator: () => {objects to action} }
|
||||
*/
|
||||
actions(actns): this {
|
||||
Object.keys(actns).map(key => {
|
||||
const type = `${this._namespace}/${key.toUpperCase()}`;
|
||||
this._switch[ type ] = actns[ key ];
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
requests(reqsts): this {
|
||||
Object.keys(reqsts).map(key => {
|
||||
const type = `${this._namespace}/${key.toUpperCase()}`;
|
||||
this._switch[ type ] = actns[ key ];
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
get actionTypes() {
|
||||
|
||||
}
|
||||
|
||||
get actionCreators() {
|
||||
|
||||
}
|
||||
|
||||
get reducer() {
|
||||
return (state, action = {}) => {
|
||||
const reduce = this._switch[ action.type ];
|
||||
return reduce ? reduce(state, action) : state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { List, Map } from 'immutable';
|
||||
import CustomMetric, { FilterSeries } from 'Types/customMetric'
|
||||
import { createFetch, fetchListType, fetchType, saveType, removeType, editType, createRemove, createEdit } from './funcTools/crud';
|
||||
import { fetchListType, fetchType, saveType, removeType, editType, createRemove, createEdit } from './funcTools/crud';
|
||||
import { createRequestReducer, ROOT_KEY } from './funcTools/request';
|
||||
import { array, request, success, failure, createListUpdater, mergeReducers } from './funcTools/tools';
|
||||
import { array, success, createListUpdater, mergeReducers } from './funcTools/tools';
|
||||
import Filter from 'Types/filter';
|
||||
import Session from 'Types/session';
|
||||
|
||||
|
|
@ -28,11 +28,6 @@ const INIT = `${name}/INIT`;
|
|||
const SET_ACTIVE_WIDGET = `${name}/SET_ACTIVE_WIDGET`;
|
||||
const REMOVE = removeType(name);
|
||||
const UPDATE_SERIES = `${name}/UPDATE_SERIES`;
|
||||
const SET_ALERT_METRIC_ID = `${name}/SET_ALERT_METRIC_ID`;
|
||||
|
||||
function chartWrapper(chart = []) {
|
||||
return chart.map(point => ({ ...point, count: Math.max(point.count, 0) }));
|
||||
}
|
||||
|
||||
const updateItemInList = createListUpdater(idKey);
|
||||
const updateInstance = (state, instance) => state.getIn([ "instance", idKey ]) === instance[ idKey ]
|
||||
|
|
@ -117,12 +112,6 @@ export default mergeReducers(
|
|||
export const edit = createEdit(name);
|
||||
export const remove = createRemove(name);
|
||||
|
||||
export const updateSeries = (index, series) => ({
|
||||
type: UPDATE_SERIES,
|
||||
index,
|
||||
series,
|
||||
});
|
||||
|
||||
export function fetch(id) {
|
||||
return {
|
||||
id,
|
||||
|
|
@ -147,34 +136,6 @@ export function fetchList() {
|
|||
};
|
||||
}
|
||||
|
||||
export function setAlertMetricId(id) {
|
||||
return {
|
||||
type: SET_ALERT_METRIC_ID,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export const addSeries = (series = null) => (dispatch, getState) => {
|
||||
const instance = getState().getIn([ 'customMetrics', 'instance' ]);
|
||||
const seriesIndex = instance.series.size;
|
||||
const newSeries = series || {
|
||||
name: `Series ${seriesIndex + 1}`,
|
||||
filter: new Filter({ filters: [], eventsOrder: 'then' }),
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: ADD_SERIES,
|
||||
series: newSeries,
|
||||
});
|
||||
}
|
||||
|
||||
export const removeSeries = (index) => (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: REMOVE_SERIES,
|
||||
index,
|
||||
});
|
||||
}
|
||||
|
||||
export const init = (instance = null, forceNull = false) => (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: INIT,
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
import Environment from 'Types/environment';
|
||||
import crudDuckGenerator from './tools/crudDuck';
|
||||
|
||||
const crudDuck = crudDuckGenerator('environment', Environment);
|
||||
export const { fetchList, fetch, init, edit, save, remove } = crudDuck.actions;
|
||||
|
||||
export default crudDuck.reducer;
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
import { List, Map, Set } from 'immutable';
|
||||
import withRequestState, { RequestTypes } from 'Duck/requestStateCreator';
|
||||
import Event from 'Types/filter/event';
|
||||
import CustomFilter from 'Types/filter/customFilter';
|
||||
import { KEYS } from 'Types/filter/customFilter';
|
||||
import logger from 'App/logger';
|
||||
import { countries } from 'App/constants';
|
||||
import { getRE } from 'App/utils';
|
||||
|
||||
const FETCH_LIST = new RequestTypes('events/FETCH_LIST');
|
||||
const TOGGLE_SELECT = 'events/TOGGLE_SELECT';
|
||||
const SET_SELECTED = 'events/SET_SELECTED';
|
||||
|
||||
const countryOptions = Object.keys(countries).map(c => ({filterKey: KEYS.USER_COUNTRY, label: KEYS.USER_COUNTRY, type: KEYS.USER_COUNTRY, value: c, actualValue: countries[c], isFilter: true }));
|
||||
|
||||
const initialState = Map({
|
||||
list: List(),
|
||||
store: Set(),
|
||||
|
||||
// replace?
|
||||
selected: Set(),
|
||||
});
|
||||
|
||||
const filterKeys = ['METADATA', KEYS.USERID, KEYS.USER_COUNTRY, KEYS.USER_BROWSER, KEYS.USER_OS, KEYS.USER_DEVICE, KEYS.REFERRER]
|
||||
|
||||
const reducer = (state = initialState, action = {}) => {
|
||||
switch (action.type) {
|
||||
case FETCH_LIST.SUCCESS: {
|
||||
const regCountry = getRE(action.params.q, 'i');
|
||||
const countryOptionsFiltered = List(countryOptions).filter(({ actualValue }) => regCountry.test(actualValue)).take(5);
|
||||
|
||||
const eventList = List(action.data).concat(countryOptionsFiltered).map(item => (
|
||||
filterKeys.includes(item.type) ?
|
||||
CustomFilter({...item, isFilter: true }) :
|
||||
Event({...item, key: item.type, filterKey: item.type, label: item.type}) )
|
||||
);
|
||||
|
||||
return state
|
||||
.set('list', eventList)
|
||||
.update('store', store => store.concat(eventList));
|
||||
}
|
||||
// TODO: use ids. or make a set-hoc?
|
||||
case TOGGLE_SELECT: {
|
||||
const { event, flag } = action;
|
||||
const shouldBeInSet = typeof flag === 'boolean'
|
||||
? flag
|
||||
: !state.get('selected').contains(event);
|
||||
return state.update('selected', set => (shouldBeInSet
|
||||
? set.add(event)
|
||||
: set.remove(event)));
|
||||
}
|
||||
case SET_SELECTED:
|
||||
return state.set('selected', Set(action.events));
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export default withRequestState(FETCH_LIST, reducer);
|
||||
|
||||
export function fetchList(params) {
|
||||
return {
|
||||
types: FETCH_LIST.toArray(),
|
||||
call: client => client.get('/events/search', params),
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
||||
export function toggleSelect(event, flag) {
|
||||
return {
|
||||
type: TOGGLE_SELECT,
|
||||
event,
|
||||
flag,
|
||||
};
|
||||
}
|
||||
|
||||
export function setSelected(events) {
|
||||
return {
|
||||
type: SET_SELECTED,
|
||||
events,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
// @ts-ignore
|
||||
import { combineReducers } from 'redux-immutable';
|
||||
|
||||
import jwt from './jwt';
|
||||
|
|
@ -8,12 +9,8 @@ import target from './target';
|
|||
import targetCustom from './targetCustom';
|
||||
import filters from './filters';
|
||||
import funnelFilters from './funnelFilters';
|
||||
import events from './events';
|
||||
import environments from './environments';
|
||||
import variables from './variables';
|
||||
import templates from './templates';
|
||||
import alerts from './alerts';
|
||||
import notifications from './notifications';
|
||||
import dashboard from './dashboard';
|
||||
import components from './components';
|
||||
import sources from './sources';
|
||||
|
|
@ -43,12 +40,8 @@ const rootReducer = combineReducers({
|
|||
filters,
|
||||
funnelFilters,
|
||||
|
||||
events,
|
||||
environments,
|
||||
variables,
|
||||
templates,
|
||||
alerts,
|
||||
notifications,
|
||||
dashboard,
|
||||
components,
|
||||
members,
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
import { List, Map } from 'immutable';
|
||||
import Notification from 'Types/notification';
|
||||
import { mergeReducers, success, array, request, createListUpdater } from './funcTools/tools';
|
||||
import { createRequestReducer } from './funcTools/request';
|
||||
import {
|
||||
createCRUDReducer,
|
||||
getCRUDRequestTypes,
|
||||
createFetchList,
|
||||
} from './funcTools/crud';
|
||||
|
||||
const name = 'notification';
|
||||
const idKey = 'notificationId';
|
||||
const SET_VIEWED = 'notifications/SET_VIEWED';
|
||||
const CLEAR_ALL = 'notifications/CLEAR_ALL';
|
||||
const SET_VIEWED_SUCCESS = success(SET_VIEWED);
|
||||
const CLEAR_ALL_SUCCESS = success(CLEAR_ALL);
|
||||
|
||||
const listUpdater = createListUpdater(idKey);
|
||||
|
||||
const initialState = Map({
|
||||
list: List(),
|
||||
});
|
||||
|
||||
const reducer = (state = initialState, action = {}) => {
|
||||
switch (action.type) {
|
||||
case SET_VIEWED_SUCCESS:
|
||||
if (!action.data) return state;
|
||||
const item = state.get('list').find(item => item[ idKey ] === action.id)
|
||||
return listUpdater(state, Notification({...item.toJS(), createdAt: item.createdAt.ts, viewed: true }));
|
||||
case CLEAR_ALL_SUCCESS:
|
||||
if (!action.data) return state;
|
||||
return state.update('list', list => list.map(l => Notification({...l.toJS(), createdAt: l.createdAt.ts, viewed: true })))
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export const fetchList = createFetchList(name);
|
||||
|
||||
export default mergeReducers(
|
||||
reducer,
|
||||
createCRUDReducer(name, Notification, idKey),
|
||||
createRequestReducer({
|
||||
setViewed: SET_VIEWED,
|
||||
clearAll: CLEAR_ALL,
|
||||
...getCRUDRequestTypes(name),
|
||||
}),
|
||||
);
|
||||
|
||||
export function setViewed(id) {
|
||||
return {
|
||||
types: array(SET_VIEWED),
|
||||
call: client => client.get(`/notifications/${ id }/view`),
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function clearAll(params) {
|
||||
return {
|
||||
types: array(CLEAR_ALL),
|
||||
call: client => client.post('/notifications/view', params),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -60,6 +60,7 @@ const initialState = Map({
|
|||
eventsIndex: [],
|
||||
sourcemapUploaded: true,
|
||||
filteredEvents: null,
|
||||
eventsQuery: '',
|
||||
showChatWindow: false,
|
||||
liveSessions: List(),
|
||||
visitedEvents: List(),
|
||||
|
|
@ -110,14 +111,17 @@ const reducer = (state = initialState, action = {}) => {
|
|||
case SET_EVENT_QUERY: {
|
||||
const events = state.get('current').events;
|
||||
const query = action.filter.query;
|
||||
// const filter = action.filter.filter;
|
||||
const searchRe = getRE(query, 'i');
|
||||
let filteredEvents = query ? events.filter((e) => searchRe.test(e.url) || searchRe.test(e.value) || searchRe.test(e.label)) : null;
|
||||
|
||||
// if (filter) {
|
||||
// filteredEvents = filteredEvents ? filteredEvents.filter(e => e.type === filter) : events.filter(e => e.type === filter);
|
||||
// }
|
||||
return state.set('filteredEvents', filteredEvents);
|
||||
const filteredEvents = query ? events.filter(
|
||||
(e) => searchRe.test(e.url)
|
||||
|| searchRe.test(e.value)
|
||||
|| searchRe.test(e.label)
|
||||
|| searchRe.test(e.type)
|
||||
|| (e.type === 'LOCATION' && searchRe.test('visited'))
|
||||
) : null;
|
||||
|
||||
return state.set('filteredEvents', filteredEvents).set('eventsQuery', query);
|
||||
}
|
||||
case FETCH.SUCCESS: {
|
||||
// TODO: more common.. or TEMP
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ const reducer = (state = initialState, action = {}) => {
|
|||
return state.set('passwordErrors', List(action.errors))
|
||||
case DELETE:
|
||||
deleteCookie('jwt', '/', '.openreplay.com')
|
||||
console.log('called')
|
||||
return initialState;
|
||||
case PUT_CLIENT.REQUEST:
|
||||
return state.mergeIn([ 'account' ], action.params);
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
import Variable from 'Types/variable';
|
||||
import crudDuckGenerator from './tools/crudDuck';
|
||||
|
||||
const crudDuck = crudDuckGenerator('variable', Variable);
|
||||
export const {
|
||||
fetchList, fetch, init, edit, save, remove,
|
||||
} = crudDuck.actions;
|
||||
|
||||
export default crudDuck.reducer;
|
||||
|
|
@ -44,7 +44,7 @@ export default class UserStore {
|
|||
}
|
||||
|
||||
initUser(user?: any ): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
if (user) {
|
||||
this.instance = new User().fromJson(user.toJson());
|
||||
} else {
|
||||
|
|
@ -54,7 +54,7 @@ export default class UserStore {
|
|||
})
|
||||
}
|
||||
|
||||
updateKey(key: string, value: any) {
|
||||
updateKey(key: keyof this, value: any) {
|
||||
this[key] = value
|
||||
|
||||
if (key === 'searchQuery') {
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
import Record from 'Types/Record';
|
||||
|
||||
export default Record({
|
||||
line1: '',
|
||||
postal_code: '',
|
||||
city: '',
|
||||
state: '',
|
||||
country: '',
|
||||
}, {
|
||||
methods: {
|
||||
validate() {
|
||||
return true;
|
||||
},
|
||||
toData() {
|
||||
const js = this.toJS();
|
||||
delete js.key;
|
||||
return js;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
import { Record, List, Set } from 'immutable';
|
||||
import { validateName } from 'App/validate';
|
||||
import { DateTime } from 'luxon';
|
||||
import Run from './run';
|
||||
import Step from './step';
|
||||
|
||||
class Test extends Record({
|
||||
testId: undefined,
|
||||
name: 'Unnamed Test',
|
||||
steps: List(),
|
||||
stepsCount: undefined,
|
||||
framework: 'selenium',
|
||||
sessionId: undefined,
|
||||
generated: false,
|
||||
tags: Set(),
|
||||
runHistory: List(),
|
||||
editedAt: undefined,
|
||||
seqId: undefined,
|
||||
seqChange: false,
|
||||
uptime: 0,
|
||||
}) {
|
||||
// ???TODO
|
||||
// idKey = "testId"
|
||||
|
||||
exists() {
|
||||
return this.testId !== undefined;
|
||||
}
|
||||
|
||||
validate() {
|
||||
if (this.steps.size === 0) return false;
|
||||
|
||||
return validateName(this.name, {
|
||||
empty: false,
|
||||
admissibleChars: ':-',
|
||||
});
|
||||
}
|
||||
|
||||
isComplete() {
|
||||
return this.stepsCount === this.steps.size;
|
||||
}
|
||||
|
||||
// not the best code
|
||||
toData() {
|
||||
const js = this
|
||||
.update('steps', steps => steps.map(step => step.toData()))
|
||||
.toJS();
|
||||
|
||||
if (js.seqChange) {
|
||||
const { testId, seqId } = js;
|
||||
return { testId, seqId };
|
||||
}
|
||||
|
||||
delete js.stepsCount;
|
||||
delete js.seqChange;
|
||||
|
||||
return js;
|
||||
}
|
||||
// not the best code
|
||||
}
|
||||
|
||||
const fromJS = (test = {}) => {
|
||||
if (test instanceof Test) return test;
|
||||
|
||||
const stepsLength = test.steps && test.steps.length; //
|
||||
const editedAt = test.editedAt ? DateTime.fromMillis(test.editedAt) : undefined;
|
||||
|
||||
const lastRun = Run(test.lastRun);
|
||||
const runHistory = List(test.runHistory) // TODO: GOOD ENDPOINTS
|
||||
.map(run => {
|
||||
if (typeof run === 'string') {
|
||||
return run === lastRun.runId
|
||||
? lastRun
|
||||
: Run({ runId: run })
|
||||
}
|
||||
return Run(run);
|
||||
});
|
||||
|
||||
return new Test({ ...test, editedAt, uptime: parseInt(test.passed / test.count * 100) || 0 })
|
||||
.set('stepsCount', typeof test.stepsCount === 'number'
|
||||
? test.stepsCount
|
||||
: stepsLength) //
|
||||
.set('runHistory', runHistory)
|
||||
.set('steps', List(test.steps).map(Step))
|
||||
.set('tags', test.tags && Set(test.tags.map(t => t.toLowerCase())));
|
||||
};
|
||||
|
||||
export default fromJS;
|
||||
|
|
@ -16,6 +16,20 @@ export interface IMember {
|
|||
invitationLink: string
|
||||
}
|
||||
|
||||
export interface IMemberApiRes {
|
||||
userId: string
|
||||
name: string
|
||||
email: string
|
||||
createdAt: string
|
||||
admin: boolean
|
||||
superAdmin: boolean
|
||||
joined: boolean
|
||||
expiredInvitation: boolean
|
||||
roleId: string
|
||||
roleName: string
|
||||
invitationLink: string
|
||||
}
|
||||
|
||||
export default Record({
|
||||
id: undefined,
|
||||
name: '',
|
||||
|
|
@ -42,9 +56,9 @@ export default Record({
|
|||
return js;
|
||||
},
|
||||
},
|
||||
fromJS: ({ createdAt, ...rest }) => ({
|
||||
fromJS: ({ createdAt, ...rest }: IMemberApiRes) => ({
|
||||
...rest,
|
||||
createdAt: createdAt && DateTime.fromISO(createdAt || 0),
|
||||
createdAt: createdAt && DateTime.fromISO(createdAt || '0'),
|
||||
id: rest.userId,
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue