+ ))}
{notes.map((note) => note.timestamp > 0 ? (
({
- issues: state.getIn(['sessions', 'current', 'issues']),
- startedAt: state.getIn(['sessions', 'current', 'startedAt']),
+ (state: any) => ({
+ issues: state.getIn(['sessions', 'current']).issues || [],
+ startedAt: state.getIn(['sessions', 'current']).startedAt || 0,
tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']),
}),
{ setTimelinePointer, setTimelineHoverTime }
diff --git a/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx b/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx
index 68f510de6..e2e50d2e4 100644
--- a/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx
+++ b/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx
@@ -298,7 +298,7 @@ export default connect(
} = state.getIn(['sessions', 'createNoteTooltip']);
const slackChannels = state.getIn(['slack', 'list']);
const teamsChannels = state.getIn(['teams', 'list']);
- const sessionId = state.getIn(['sessions', 'current', 'sessionId']);
+ const sessionId = state.getIn(['sessions', 'current']).sessionId;
return { isVisible, time, sessionId, isEdit, editNote, slackChannels, teamsChannels };
},
{ setCreateNoteTooltip, addNote, updateNote, fetchSlack, fetchTeams }
diff --git a/frontend/app/components/Session_/Player/Controls/timeline.module.css b/frontend/app/components/Session_/Player/Controls/timeline.module.css
index 48217119d..c935d4fd4 100644
--- a/frontend/app/components/Session_/Player/Controls/timeline.module.css
+++ b/frontend/app/components/Session_/Player/Controls/timeline.module.css
@@ -73,6 +73,22 @@
.event.location {
background: $blue;
} */
+.redEvent {
+ position: absolute;
+ width: 2px;
+ height: 10px;
+ background: $red;
+ z-index: 3;
+ pointer-events: none;
+ /* top: 0; */
+ /* bottom: 0; */
+ /* &:hover {
+ width: 10px;
+ height: 10px;
+ margin-left: -6px;
+ z-index: 1;
+ };*/
+}
.markup {
position: absolute;
diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js
index afda1e4d8..e0a467ca2 100644
--- a/frontend/app/components/Session_/Player/Player.js
+++ b/frontend/app/components/Session_/Player/Player.js
@@ -3,7 +3,6 @@ import { connect } from 'react-redux';
import { findDOMNode } from 'react-dom';
import cn from 'classnames';
import { EscapeButton } from 'UI';
-import { hide as hideTargetDefiner } from 'Duck/components/targetDefiner';
import {
NONE,
CONSOLE,
@@ -108,15 +107,14 @@ export default connect((state) => {
return {
fullscreen: state.getIn(['components', 'player', 'fullscreen']),
nextId: state.getIn(['sessions', 'nextId']),
- sessionId: state.getIn(['sessions', 'current', 'sessionId']),
+ sessionId: state.getIn(['sessions', 'current']).sessionId,
bottomBlock: state.getIn(['components', 'player', 'bottomBlock']),
closedLive:
!!state.getIn(['sessions', 'errors']) ||
- (isAssist && !state.getIn(['sessions', 'current', 'live'])),
+ (isAssist && !state.getIn(['sessions', 'current']).live),
};
},
{
- hideTargetDefiner,
fullscreenOff,
updateLastPlayedSession,
}
diff --git a/frontend/app/components/Session_/PlayerBlock.js b/frontend/app/components/Session_/PlayerBlock.js
index 5da29a587..9d9d1698a 100644
--- a/frontend/app/components/Session_/PlayerBlock.js
+++ b/frontend/app/components/Session_/PlayerBlock.js
@@ -8,9 +8,9 @@ import styles from './playerBlock.module.css';
@connect((state) => ({
fullscreen: state.getIn(['components', 'player', 'fullscreen']),
- sessionId: state.getIn(['sessions', 'current', 'sessionId']),
+ sessionId: state.getIn(['sessions', 'current']).sessionId,
disabled: state.getIn(['components', 'targetDefiner', 'inspectorMode']),
- jiraConfig: state.getIn(['issues', 'list']).first(),
+ jiraConfig: state.getIn(['issues', 'list'])[0],
}))
export default class PlayerBlock extends React.PureComponent {
render() {
diff --git a/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx b/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx
index 5031d2cca..fc5c66eea 100644
--- a/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx
+++ b/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx
@@ -120,5 +120,5 @@ function ScreenRecorder({
export default connect((state: any) => ({
siteId: state.getIn(['site', 'siteId']),
- sessionId: state.getIn(['sessions', 'current', 'sessionId']),
+ sessionId: state.getIn(['sessions', 'current']).sessionId,
}))(observer(ScreenRecorder))
diff --git a/frontend/app/components/Session_/TimeTable/TimeTable.tsx b/frontend/app/components/Session_/TimeTable/TimeTable.tsx
index c6e3ea716..d81a22dcf 100644
--- a/frontend/app/components/Session_/TimeTable/TimeTable.tsx
+++ b/frontend/app/components/Session_/TimeTable/TimeTable.tsx
@@ -179,9 +179,9 @@ export default class TimeTable extends React.PureComponent
{
onClick={typeof onRowClick === 'function' ? () => onRowClick(row, index) : undefined}
id="table-row"
>
- {columns.map(({ dataKey, render, width }) => (
-
- {render ? render(row) : row[dataKey || ''] ||
{'empty'}}
+ {columns.map((column, key) => (
+
+ {column.render ? column.render(row) : row[column.dataKey || ''] || {'empty'}}
))}
@@ -262,7 +262,7 @@ export default class TimeTable extends React.PureComponent
{
{columns.map(({ label, width }) => (
-
+
{label}
))}
@@ -282,14 +282,15 @@ export default class TimeTable extends React.PureComponent
{
{timeColumns.map((_, index) => (
))}
- {visibleRefLines.map(({ time, color, onClick }) => (
+ {visibleRefLines.map((line, key) => (
))}
diff --git a/frontend/app/components/shared/AnnouncementModal/AnnouncementModal.tsx b/frontend/app/components/shared/AnnouncementModal/AnnouncementModal.tsx
deleted file mode 100644
index 5f69de976..000000000
--- a/frontend/app/components/shared/AnnouncementModal/AnnouncementModal.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import React from 'react';
-import { Button, NoContent } from 'UI';
-import { connect } from 'react-redux';
-import { fetchList, setLastRead } from 'Duck/announcements';
-import cn from 'classnames';
-import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
-import ListItem from './ListItem'
-
-interface Props {
- unReadNotificationsCount: number;
- setLastRead: Function;
- list: any;
-}
-function AnnouncementModal(props: Props) {
- const { list, unReadNotificationsCount } = props;
-
- // const onClear = (notification: any) => {
- // console.log('onClear', notification);
- // props.setViewed(notification.notificationId)
- // }
-
- return (
-
-
-
-
- }
- subtext="There are no alerts to show."
- // show={ !loading && unReadNotificationsCount === 0 }
- size="small"
- >
- {list.map((item: any, i: any) => (
-
- {/* onClear(item)} loading={false} /> */}
-
- ))}
-
-
-
- );
-}
-
-export default connect((state: any) => ({
- list: state.getIn(['announcements', 'list']),
-}), {
- fetchList,
- setLastRead,
-})(AnnouncementModal);
\ No newline at end of file
diff --git a/frontend/app/components/shared/AnnouncementModal/ListItem/ListItem.tsx b/frontend/app/components/shared/AnnouncementModal/ListItem/ListItem.tsx
deleted file mode 100644
index dd777c719..000000000
--- a/frontend/app/components/shared/AnnouncementModal/ListItem/ListItem.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import { Button, Label } from 'UI';
-import stl from './listItem.module.css';
-
-const ListItem = ({ announcement, onButtonClick }) => {
- return (
-
-
-
{announcement.createdAt && announcement.createdAt.toFormat('LLL dd, yyyy')}
-
-
- {announcement.imageUrl &&
-

- }
-
-
{announcement.title}
-
{announcement.description}
- {announcement.buttonUrl &&
-
- }
-
-
- )
-}
-
-export default ListItem
diff --git a/frontend/app/components/shared/AnnouncementModal/ListItem/index.ts b/frontend/app/components/shared/AnnouncementModal/ListItem/index.ts
deleted file mode 100644
index 741aed270..000000000
--- a/frontend/app/components/shared/AnnouncementModal/ListItem/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ListItem';
diff --git a/frontend/app/components/shared/AnnouncementModal/ListItem/listItem.module.css b/frontend/app/components/shared/AnnouncementModal/ListItem/listItem.module.css
deleted file mode 100644
index 5bc3a44c8..000000000
--- a/frontend/app/components/shared/AnnouncementModal/ListItem/listItem.module.css
+++ /dev/null
@@ -1,5 +0,0 @@
-.wrapper {
- background-color: white;
- margin-bottom: 20px;
- padding: 15px;
-}
\ No newline at end of file
diff --git a/frontend/app/components/shared/AnnouncementModal/index.ts b/frontend/app/components/shared/AnnouncementModal/index.ts
deleted file mode 100644
index b9af0fc52..000000000
--- a/frontend/app/components/shared/AnnouncementModal/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './AnnouncementModal';
\ No newline at end of file
diff --git a/frontend/app/components/shared/Bookmark/Bookmark.tsx b/frontend/app/components/shared/Bookmark/Bookmark.tsx
index 04c88e589..d011af5f7 100644
--- a/frontend/app/components/shared/Bookmark/Bookmark.tsx
+++ b/frontend/app/components/shared/Bookmark/Bookmark.tsx
@@ -65,7 +65,7 @@ function Bookmark(props: Props) {
export default connect(
(state: any) => ({
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
- favorite: state.getIn(['sessions', 'current', 'favorite']),
+ favorite: state.getIn(['sessions', 'current']).favorite,
}),
{ toggleFavorite }
)(Bookmark);
diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx b/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx
deleted file mode 100644
index 8f8aca480..000000000
--- a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx
+++ /dev/null
@@ -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 (
-
-
-
- props.updateSeries(seriesIndex, { name }) } />
-
-
-
-
-
-
-
-
setExpanded(!expanded)} className="ml-3">
-
-
-
-
-
- { expanded && (
- <>
-
- { series.filter.filters.size > 0 ? (
-
- ): (
-
{emptyMessage}
- )}
-
-
- >
- )}
-
- );
-}
-
-export default connect(null, {
- edit,
- updateSeries,
- addSeriesFilterFilter,
- editSeriesFilterFilter,
- editSeriesFilter,
- removeSeriesFilterFilter,
-})(FilterSeries);
\ No newline at end of file
diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/SeriesName.tsx b/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/SeriesName.tsx
deleted file mode 100644
index d6a69c73d..000000000
--- a/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/SeriesName.tsx
+++ /dev/null
@@ -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
(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 (
-
- { editing ? (
-
setEditing(true)}
- />
- ) : (
-
{name && name.trim() === '' ? 'Series ' + (seriesIndex + 1) : name }
- )}
-
-
setEditing(true)}>
-
- );
-}
-
-export default SeriesName;
\ No newline at end of file
diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/index.ts b/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/index.ts
deleted file mode 100644
index 90e63cdb6..000000000
--- a/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './SeriesName';
\ No newline at end of file
diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/index.ts b/frontend/app/components/shared/CustomMetrics/FilterSeries/index.ts
deleted file mode 100644
index 5882e382a..000000000
--- a/frontend/app/components/shared/CustomMetrics/FilterSeries/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './FilterSeries'
\ No newline at end of file
diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx
index 4719914ba..1c7e9c425 100644
--- a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx
+++ b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx
@@ -12,7 +12,7 @@ import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorD
import { useModal } from 'App/components/Modal';
import useAutoscroll, { getLastItemTime } from '../useAutoscroll';
import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter'
-import useCellMeasurerCache from '../useCellMeasurerCache'
+import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache'
const ALL = 'ALL';
const INFO = 'INFO';
diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx
index f34b3e04e..1f2212152 100644
--- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx
+++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx
@@ -384,5 +384,5 @@ function NetworkPanel({ startedAt }: { startedAt: number }) {
}
export default connect((state: any) => ({
- startedAt: state.getIn(['sessions', 'current', 'startedAt']),
+ startedAt: state.getIn(['sessions', 'current']).startedAt,
}))(observer(NetworkPanel));
diff --git a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx
index 366750b51..b97e82e97 100644
--- a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx
+++ b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx
@@ -12,7 +12,7 @@ import StackEventRow from 'Shared/DevTools/StackEventRow';
import StackEventModal from '../StackEventModal';
import useAutoscroll, { getLastItemTime } from '../useAutoscroll';
import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter'
-import useCellMeasurerCache from '../useCellMeasurerCache'
+import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache'
const INDEX_KEY = 'stackEvent';
const ALL = 'ALL';
diff --git a/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx
index 2fa0343e3..bbb2b204e 100644
--- a/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx
+++ b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx
@@ -223,7 +223,7 @@ export default class TimeTable extends React.PureComponent {
{columns
.filter((i: any) => !i.hidden)
.map(({ dataKey, render, width, label }) => (
-
+
{render
? render(row)
: row[dataKey || ''] ||
{'empty'}}
@@ -327,7 +327,7 @@ export default class TimeTable extends React.PureComponent
{
{columns.map(({ label, width, dataKey, onClick = null }) => (
{
))}
{visibleRefLines.map(({ time, color, onClick }) => (
new CellMeasurerCache({
- fixedWidth: true,
- keyMapper: (index) => filteredListRef.current[index],
- }), [])
-}
\ No newline at end of file
diff --git a/frontend/app/components/shared/SessionItem/SessionItem.tsx b/frontend/app/components/shared/SessionItem/SessionItem.tsx
index fbfd2202e..a59f8e5aa 100644
--- a/frontend/app/components/shared/SessionItem/SessionItem.tsx
+++ b/frontend/app/components/shared/SessionItem/SessionItem.tsx
@@ -38,7 +38,6 @@ interface Props {
userNumericHash: number;
live: boolean;
metadata: Record;
- userSessionsCount: number;
issueTypes: [];
active: boolean;
isCallActive?: boolean;
diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx
index 5f279c394..57c41b411 100644
--- a/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx
+++ b/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx
@@ -169,7 +169,7 @@ function SessionList(props: Props) {
}
- show={!loading && list.size === 0}
+ show={!loading && list.length === 0}
>
{list.map((session: any) => (
@@ -188,7 +188,7 @@ function SessionList(props: Props) {
Showing {(currentPage - 1) * pageSize + 1} to{' '}
- {(currentPage - 1) * pageSize + list.size} of{' '}
+ {(currentPage - 1) * pageSize + list.length} of{' '}
{numberWithCommas(total)} sessions.
(
-
-
0 ? 'gray' : 'gray-medium'} />
- 0 ? 'color-gray' : 'color-gray-medium')}>{label}
-
-)
-
-function SessionStack({ flow = {}, applySavedFilter, setActiveTab, setActiveFlow }) {
- const onAllClick = (flow) => {
- setActiveFlow(flow)
- applySavedFilter(flow.filter)
- setActiveTab({ type: 'all', name: 'All'})
- }
- return (
-
-
onAllClick(flow)}>
- {flow.name}
-
-
-
{flow.count} Sessions
-
- {flow.watchdogs.map(({type, count}) => (
-
- ))}
-
-
-
- )
-}
-
-export default connect(null, { applySavedFilter, setActiveTab, setActiveFlow })(SessionStack)
diff --git a/frontend/app/components/shared/SessionStack/index.js b/frontend/app/components/shared/SessionStack/index.js
deleted file mode 100644
index db3464728..000000000
--- a/frontend/app/components/shared/SessionStack/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './SessionStack';
\ No newline at end of file
diff --git a/frontend/app/components/shared/SessionStack/sessionStack.module.css b/frontend/app/components/shared/SessionStack/sessionStack.module.css
deleted file mode 100644
index 30b2a6eef..000000000
--- a/frontend/app/components/shared/SessionStack/sessionStack.module.css
+++ /dev/null
@@ -1,18 +0,0 @@
-
-@import 'mixins.css';
-
-.wrapper {
- background: #fff;
- border: solid thin $gray-light;
- border-radius: 3px;
- @mixin defaultHover;
- box-shadow:
- /* The top layer shadow */
- /* 0 1px 1px rgba(0,0,0,0.15), */
- /* The second layer */
- 4px 4px 1px 1px white,
- /* The second layer shadow */
- 4px 4px 0px 1px rgba(0,0,0,0.4);
- /* Padding for demo purposes */
- padding: 16px;
-}
\ No newline at end of file
diff --git a/frontend/app/components/shared/SharePopup/SharePopup.js b/frontend/app/components/shared/SharePopup/SharePopup.js
index 47f2b68b7..1df16cf6d 100644
--- a/frontend/app/components/shared/SharePopup/SharePopup.js
+++ b/frontend/app/components/shared/SharePopup/SharePopup.js
@@ -12,7 +12,7 @@ import { fetchList as fetchTeams, sendMsTeamsMsg } from 'Duck/integrations/teams
@connect(
(state) => ({
- sessionId: state.getIn(['sessions', 'current', 'sessionId']),
+ sessionId: state.getIn(['sessions', 'current']).sessionId,
channels: state.getIn(['slack', 'list']),
msTeamsChannels: state.getIn(['teams', 'list']),
tenantId: state.getIn(['user', 'account', 'tenantId']),
diff --git a/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx b/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx
index 4acd355c2..2aca274ea 100644
--- a/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx
+++ b/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx
@@ -80,7 +80,7 @@ export default connect(
(state: any) => ({
// errorStack: state.getIn(['sessions', 'errorStack']),
errorStack: state.getIn(['errors', 'instanceTrace']),
- sessionId: state.getIn(['sessions', 'current', 'sessionId']),
+ sessionId: state.getIn(['sessions', 'current']).sessionId,
}),
{ fetchErrorStackList }
)(ErrorDetails);
diff --git a/frontend/app/duck/ReducerModule.js.dev b/frontend/app/duck/ReducerModule.js.dev
deleted file mode 100644
index c80c65a37..000000000
--- a/frontend/app/duck/ReducerModule.js.dev
+++ /dev/null
@@ -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;
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/app/duck/announcements.js b/frontend/app/duck/announcements.js
deleted file mode 100644
index 3a7612ee7..000000000
--- a/frontend/app/duck/announcements.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { List, Map } from 'immutable';
-import Announcement from 'Types/announcement';
-import { RequestTypes } from './requestStateCreator';
-
-import { mergeReducers } from './funcTools/tools';
-import { createRequestReducer } from './funcTools/request';
-import {
- createCRUDReducer,
- getCRUDRequestTypes,
- createFetchList
-} from './funcTools/crud';
-
-const name = 'announcement';
-const idKey = 'id';
-
-const SET_LAST_READ = new RequestTypes('announcement/SET_LAST_READ');
-
-const initialState = Map({
- list: List()
-});
-
-const reducer = (state = initialState, action = {}) => {
- switch (action.type) {
- case SET_LAST_READ.SUCCESS:
- return state.update('list', (list) => list.map(i => ({...i.toJS(), viewed: true })));
- }
- return state;
-};
-
-export function setLastRead() {
- return {
- types: SET_LAST_READ.toArray(),
- call: client => client.get(`/announcements/view`),
- };
-}
-
-export const fetchList = createFetchList(name);
-
-export default mergeReducers(
- reducer,
- createCRUDReducer(name, Announcement, idKey),
- createRequestReducer({
- ...getCRUDRequestTypes(name),
- }),
-);
\ No newline at end of file
diff --git a/frontend/app/duck/assignments.js b/frontend/app/duck/assignments.js
index 3abb99fb7..427937e05 100644
--- a/frontend/app/duck/assignments.js
+++ b/frontend/app/duck/assignments.js
@@ -2,10 +2,9 @@ import { List, Map, Set } from 'immutable';
import Assignment from 'Types/session/assignment';
import Activity from 'Types/session/activity';
import withRequestState, { RequestTypes } from './requestStateCreator';
-import { createListUpdater, createItemInListUpdater } from './funcTools/tools';
+import { createListUpdater } from './funcTools/tools';
import { editType, initType } from './funcTools/crud/types';
import { createInit, createEdit } from './funcTools/crud';
-import IssuesType from 'Types/issue/issuesType'
const idKey = 'id';
const name = 'assignment';
@@ -22,8 +21,8 @@ const INIT = initType(name);
const initialState = Map({
list: List(),
- instance: Assignment(),
- activeIssue: Assignment(),
+ instance: new Assignment(),
+ activeIssue: new Assignment(),
issueTypes: List(),
issueTypeIcons: Set(),
users: List(),
@@ -33,22 +32,23 @@ const initialState = Map({
const reducer = (state = initialState, action = {}) => {
const users = state.get('users');
- var issueTypes = []
+ let issueTypes = []
switch (action.type) {
case INIT:
action.instance.issueType = issueTypes.length > 0 ? issueTypes[0].id : '';
- return state.set('instance', Assignment(action.instance));
+ return state.set('instance', new Assignment(action.instance));
case EDIT:
- return state.mergeIn([ 'instance' ], action.instance);
+ const inst = state.get('instance')
+ return state.set('instance', new Assignment({ ...inst, ...action.instance }));
case FETCH_PROJECTS.SUCCESS:
return state.set('projects', List(action.data)).set('projectsFetched', true);
case FETCH_ASSIGNMENTS.SUCCESS:
- return state.set('list', List(action.data).map(Assignment));
+ return state.set('list', List(action.data).map(as => new Assignment(as)));
case FETCH_ASSIGNMENT.SUCCESS:
- return state.set('activeIssue', Assignment({ ...action.data, users}));
+ return state.set('activeIssue', new Assignment({ ...action.data, users}));
case FETCH_META.SUCCESS:
issueTypes = action.data.issueTypes
- var issueTypeIcons = {}
+ const issueTypeIcons = {}
issueTypes.forEach(iss => {
issueTypeIcons[iss.id] = iss.iconUrl
})
@@ -56,12 +56,12 @@ const reducer = (state = initialState, action = {}) => {
.set('users', List(action.data.users))
.set('issueTypeIcons', issueTypeIcons)
case ADD_ACTIVITY.SUCCESS:
- const instance = Assignment(action.data);
+ const instance = new Assignment(action.data);
return listUpdater(state, instance);
case ADD_MESSAGE.SUCCESS:
const user = users.filter(user => user.id === action.data.author).first();
- const activity = Activity({ type: 'message', user, ...action.data,});
- return state.updateIn([ 'activeIssue', 'activities' ], list => list.push(activity));
+ const activity = new Activity({ type: 'message', user, ...action.data,});
+ return state.update([ 'activeIssue' ], issue => issue.activities.push(activity));
default:
return state;
}
@@ -79,7 +79,7 @@ export default withRequestState({
export const init = createInit(name);
export const edit = createEdit(name);
-export function fetchProjects(sessionId) {
+export function fetchProjects() {
return {
types: FETCH_PROJECTS.toArray(),
call: client => client.get(`/integrations/issues/list_projects`)
@@ -100,13 +100,6 @@ export function fetchAssignments(sessionId) {
}
}
-export function fetchAssigment(sessionId, id) {
- return {
- types: FETCH_ASSIGNMENT.toArray(),
- call: client => client.get(`/sessions/${ sessionId }/assign/${ id }`)
- }
-}
-
export function addActivity(sessionId, params) {
const data = { ...params, assignee: params.assignee, issueType: params.issueType }
return {
diff --git a/frontend/app/duck/config.js b/frontend/app/duck/config.js
deleted file mode 100644
index a445e3f50..000000000
--- a/frontend/app/duck/config.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import { Map } from 'immutable';
-import { saveType, fetchType, editType } from './funcTools/crud/types';
-import { mergeReducers, success, array } from './funcTools/tools';
-import { createRequestReducer } from './funcTools/request';
-
-const name = 'config'
-
-const FETCH = fetchType(name);
-const SAVE = saveType(name);
-const EDIT = editType(name);
-
-const FETCH_SUCCESS = success(FETCH);
-const SAVE_SUCCESS = success(SAVE);
-
-const initialState = Map({
- options: {
- weeklyReport: false
- },
-});
-
-const reducer = (state = initialState, action = {}) => {
- switch(action.type) {
- case FETCH_SUCCESS:
- return state.set('options', action.data)
- case SAVE_SUCCESS:
- return state
- case EDIT:
- return state.set('options', action.config)
- default:
- return state;
- }
-}
-
-export const fetch = () => {
- return {
- types: array(FETCH),
- call: client => client.get(`/config/weekly_report`),
- }
-}
-
-export const save = (config) => {
- return {
- types: array(SAVE),
- call: client => client.post(`/config/weekly_report`, config),
- }
-}
-
-export const edit = (config) => {
- return {
- type: EDIT,
- config
- }
-}
-
-export default mergeReducers(
- reducer,
- createRequestReducer({
- fetchRequest: FETCH,
- saveRequest: SAVE,
- }),
-)
\ No newline at end of file
diff --git a/frontend/app/duck/customMetrics.js b/frontend/app/duck/customMetrics.js
index e6713acb4..c740f7a57 100644
--- a/frontend/app/duck/customMetrics.js
+++ b/frontend/app/duck/customMetrics.js
@@ -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 ]
@@ -97,7 +92,7 @@ function reducer(state = initialState, action = {}) {
const { data } = action;
return state.set("list", List(data.map(CustomMetric)));
case success(FETCH_SESSION_LIST):
- return state.set("sessionList", List(action.data.map(item => ({ ...item, sessions: item.sessions.map(Session) }))));
+ return state.set("sessionList", List(action.data.map(item => ({ ...item, sessions: item.sessions.map(s => new Session(s)) }))));
case SET_ACTIVE_WIDGET:
return state.set("activeWidget", action.widget).set('sessionList', List());
}
@@ -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,
diff --git a/frontend/app/duck/environments.js b/frontend/app/duck/environments.js
deleted file mode 100644
index 8356c4281..000000000
--- a/frontend/app/duck/environments.js
+++ /dev/null
@@ -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;
diff --git a/frontend/app/duck/errors.js b/frontend/app/duck/errors.js
index 32cbf4c40..8a23875cc 100644
--- a/frontend/app/duck/errors.js
+++ b/frontend/app/duck/errors.js
@@ -66,6 +66,8 @@ function reducer(state = initialState, action = {}) {
} else {
return state.set("instance", ErrorInfo(action.data));
}
+ case failure(FETCH):
+ return state.set("instance", ErrorInfo());
case success(FETCH_TRACE):
return state.set("instanceTrace", List(action.data.trace)).set('sourcemapUploaded', action.data.sourcemapUploaded);
case success(FETCH_LIST):
diff --git a/frontend/app/duck/events.js b/frontend/app/duck/events.js
deleted file mode 100644
index 000bd24b7..000000000
--- a/frontend/app/duck/events.js
+++ /dev/null
@@ -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,
- };
-}
diff --git a/frontend/app/duck/funcTools/tools.js b/frontend/app/duck/funcTools/tools.js
index e2be77371..139c824ac 100644
--- a/frontend/app/duck/funcTools/tools.js
+++ b/frontend/app/duck/funcTools/tools.js
@@ -27,11 +27,6 @@ export function createItemInListUpdater(idKey = 'id', shouldAdd = true) {
}
}
-export function createItemInListFilter(idKey = 'id') {
- return id =>
- list => list.filter(item => item[ idKey ] !== id)
-}
-
export const request = type => `${ type }_REQUEST`;
export const success = type => `${ type }_SUCCESS`;
export const failure = type => `${ type }_FAILURE`;
diff --git a/frontend/app/duck/funnels.js b/frontend/app/duck/funnels.js
index 3abfcc450..d175b64d5 100644
--- a/frontend/app/duck/funnels.js
+++ b/frontend/app/duck/funnels.js
@@ -99,12 +99,12 @@ const reducer = (state = initialState, action = {}) => {
.set('criticalIssuesCount', action.data.issues.criticalIssuesCount)
case FETCH_SESSIONS_SUCCESS:
return state
- .set('sessions', List(action.data.sessions).map(Session))
+ .set('sessions', List(action.data.sessions).map(s => new Session(s)))
.set('total', action.data.total)
case FETCH_ISSUE_SUCCESS:
return state
.set('issue', FunnelIssue(action.data.issue))
- .set('sessions', List(action.data.sessions.sessions).map(Session))
+ .set('sessions', List(action.data.sessions.sessions).map(s => new Session(s)))
.set('sessionsTotal', action.data.sessions.total)
case RESET_ISSUE:
return state.set('isses', FunnelIssue())
diff --git a/frontend/app/duck/index.ts b/frontend/app/duck/index.ts
index 0371396bf..4e8e24d8f 100644
--- a/frontend/app/duck/index.ts
+++ b/frontend/app/duck/index.ts
@@ -1,18 +1,13 @@
+// @ts-ignore
import { combineReducers } from 'redux-immutable';
import user from './user';
import sessions from './sessions';
import assignments from './assignments';
-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';
@@ -21,12 +16,9 @@ import site from './site';
import customFields from './customField';
import webhooks from './webhook';
import integrations from './integrations';
-import watchdogs from './watchdogs';
import rehydrate from './rehydrate';
-import announcements from './announcements';
import errors from './errors';
import funnels from './funnels';
-import config from './config';
import roles from './roles';
import customMetrics from './customMetrics';
import search from './search';
@@ -36,29 +28,20 @@ const rootReducer = combineReducers({
user,
sessions,
assignments,
- target,
- targetCustom,
filters,
funnelFilters,
- events,
- environments,
- variables,
templates,
alerts,
- notifications,
dashboard,
components,
members,
site,
customFields,
webhooks,
- watchdogs,
rehydrate,
- announcements,
errors,
funnels,
- config,
roles,
customMetrics,
search,
diff --git a/frontend/app/duck/issues.js b/frontend/app/duck/issues.js
index 6c2c70733..6cc97a96f 100644
--- a/frontend/app/duck/issues.js
+++ b/frontend/app/duck/issues.js
@@ -9,7 +9,6 @@ import { createInit, createEdit } from './funcTools/crud';
const idKey = 'id';
const name = 'assignment';
const listUpdater = createListUpdater(idKey);
-const itemInListUpdater = createItemInListUpdater(idKey);
const FETCH_ASSIGNMENTS = new RequestTypes('asignment/FETCH_ASSIGNMENTS');
const FETCH_ISSUE = new RequestTypes('asignment/FETCH_ISSUE');
@@ -23,8 +22,8 @@ const RESET_ACTIVE_ISSUE = 'assignment/RESET_ACTIVE_ISSUE';
const initialState = Map({
list: List(),
- instance: Assignment(),
- activeIssue: Assignment(),
+ instance: new Assignment(),
+ activeIssue: new Assignment(),
issueTypes: List(),
issueTypeIcons: Set(),
users: List(),
@@ -39,9 +38,9 @@ const reducer = (state = initialState, action = {}) => {
case FETCH_PROJECTS.SUCCESS:
return state.set('projects', List(action.data));
case FETCH_ASSIGNMENTS.SUCCESS:
- return state.set('list', List(action.data).map(Assignment));
+ return state.set('list', action.data.map(as => new Assignment(as)));
case ADD_ACTIVITY.SUCCESS:
- const instance = Assignment(action.data);
+ const instance = new Assignment(action.data);
return listUpdater(state, instance);
case FETCH_META.SUCCESS:
issueTypes = action.data.issueTypes;
@@ -53,16 +52,16 @@ const reducer = (state = initialState, action = {}) => {
.set('users', List(action.data.users))
.set('issueTypeIcons', issueTypeIcons)
case FETCH_ISSUE.SUCCESS:
- return state.set('activeIssue', Assignment({ ...action.data, users}));
+ return state.set('activeIssue', new Assignment({ ...action.data, users}));
case RESET_ACTIVE_ISSUE:
- return state.set('activeIssue', Assignment());
+ return state.set('activeIssue', new Assignment());
case ADD_MESSAGE.SUCCESS:
const user = users.filter(user => user.id === action.data.author).first();
- const activity = Activity({ type: 'message', user, ...action.data,});
+ const activity = new Activity({ type: 'message', user, ...action.data,});
return state.updateIn([ 'activeIssue', 'activities' ], list => list.push(activity));
case INIT:
action.instance.issueType = issueTypes.length > 0 ? issueTypes[0].id : '';
- return state.set('instance', Assignment(action.instance));
+ return state.set('instance', new Assignment(action.instance));
case EDIT:
return state.mergeIn([ 'instance' ], action.instance);
default:
@@ -101,13 +100,6 @@ export function fetchProjects() {
}
}
-export function fetchIssue(sessionId, id) {
- return {
- types: FETCH_ISSUE.toArray(),
- call: client => client.get(`/sessions/${ sessionId }/assign/jira/${ id }`)
- }
-}
-
export function fetchMeta(projectId) {
return {
types: FETCH_META.toArray(),
diff --git a/frontend/app/duck/liveSearch.js b/frontend/app/duck/liveSearch.js
index 591dd7084..8500158bd 100644
--- a/frontend/app/duck/liveSearch.js
+++ b/frontend/app/duck/liveSearch.js
@@ -36,7 +36,7 @@ function reducer(state = initialState, action = {}) {
return state.set('currentPage', action.page);
case success(FETCH_SESSION_LIST):
const { sessions, total } = action.data;
- const list = List(sessions).map(Session);
+ const list = List(sessions).map(s => new Session(s));
return state
.set('list', list)
.set('total', total);
diff --git a/frontend/app/duck/member.js b/frontend/app/duck/member.js
index 31cccb395..ce12c1659 100644
--- a/frontend/app/duck/member.js
+++ b/frontend/app/duck/member.js
@@ -1,7 +1,7 @@
import { Map } from 'immutable';
import Member from 'Types/member';
import crudDuckGenerator from './tools/crudDuck';
-import withRequestState, { RequestTypes } from 'Duck/requestStateCreator';
+import { RequestTypes } from 'Duck/requestStateCreator';
import { reduceDucks } from 'Duck/tools';
const GENERATE_LINK = new RequestTypes('member/GENERATE_LINK');
@@ -37,12 +37,4 @@ export function save(instance) {
};
}
-export function generateInviteLink(instance) {
- return {
- types: GENERATE_LINK.toArray(),
- call: client => client.get(`/client/members/${ instance.id }/reset`),
- id: instance.id
- };
-}
-
export default reduceDucks(crudDuck, { initialState, reducer }).reducer;
diff --git a/frontend/app/duck/notifications.js b/frontend/app/duck/notifications.js
deleted file mode 100644
index 129349792..000000000
--- a/frontend/app/duck/notifications.js
+++ /dev/null
@@ -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),
- };
-}
-
diff --git a/frontend/app/duck/sessions.js b/frontend/app/duck/sessions.ts
similarity index 77%
rename from frontend/app/duck/sessions.js
rename to frontend/app/duck/sessions.ts
index 8afb6d073..11191f9cd 100644
--- a/frontend/app/duck/sessions.js
+++ b/frontend/app/duck/sessions.ts
@@ -1,6 +1,7 @@
import { List, Map } from 'immutable';
import Session from 'Types/session';
import ErrorStack from 'Types/session/errorStack';
+import { Location, InjectedEvent } from 'Types/session/event'
import Watchdog from 'Types/watchdog';
import { clean as cleanParams } from 'App/api_client';
import withRequestState, { RequestTypes } from './requestStateCreator';
@@ -9,7 +10,6 @@ import { LAST_7_DAYS } from 'Types/app/period';
import { getDateRangeFromValue } from 'App/dateRange';
const name = 'sessions';
-const INIT = 'sessions/INIT';
const FETCH_LIST = new RequestTypes('sessions/FETCH_LIST');
const FETCH_AUTOPLAY_LIST = new RequestTypes('sessions/FETCH_AUTOPLAY_LIST');
const FETCH = new RequestTypes('sessions/FETCH');
@@ -46,10 +46,10 @@ const defaultDateFilters = {
endDate: range.end.unix() * 1000,
};
-const initialState = Map({
- list: List(),
+const initObj = {
+ list: [],
sessionIds: [],
- current: Session(),
+ current: new Session(),
total: 0,
keyMap: Map(),
wdTypeCount: Map(),
@@ -60,8 +60,9 @@ const initialState = Map({
eventsIndex: [],
sourcemapUploaded: true,
filteredEvents: null,
+ eventsQuery: '',
showChatWindow: false,
- liveSessions: List(),
+ liveSessions: [],
visitedEvents: List(),
insights: List(),
insightFilters: defaultDateFilters,
@@ -72,30 +73,31 @@ const initialState = Map({
lastPlayedSessionId: null,
timeLineTooltip: { time: 0, offset: 0, isVisible: false, timeStr: '' },
createNoteTooltip: { time: 0, isVisible: false, isEdit: false, note: null },
-});
+}
-const reducer = (state = initialState, action = {}) => {
+const initialState = Map(initObj);
+
+interface IAction extends Record{
+ type: string;
+ data: any;
+}
+
+const reducer = (state = initialState, action: IAction) => {
switch (action.type) {
- case INIT:
- return state.set('current', Session(action.session));
- // case FETCH_LIST.REQUEST:
- // return action.clear ? state.set('list', List()) : state;
case FETCH_ERROR_STACK.SUCCESS:
- return state.set('errorStack', List(action.data.trace).map(ErrorStack)).set('sourcemapUploaded', action.data.sourcemapUploaded);
+ return state.set('errorStack', List(action.data.trace).map(es => new ErrorStack(es))).set('sourcemapUploaded', action.data.sourcemapUploaded);
case FETCH_LIVE_LIST.SUCCESS:
- const liveList = List(action.data.sessions).map((s) => new Session({ ...s, live: true }));
+ const liveList = action.data.sessions.map((s) => new Session({ ...s, live: true }));
return state.set('liveSessions', liveList);
case FETCH_LIST.SUCCESS:
const { sessions, total } = action.data;
- const list = List(sessions).map(Session);
+ const list = sessions.map(s => new Session(s));
+ console.log(sessions, list, action)
return state
.set('list', list)
- .set('sessionIds', list.map(({ sessionId }) => sessionId).toJS())
- .set(
- 'favoriteList',
- list.filter(({ favorite }) => favorite)
- )
+ .set('sessionIds', list.map(({ sessionId }) => sessionId))
+ .set('favoriteList', list.filter(({ favorite }) => favorite))
.set('total', total);
case FETCH_AUTOPLAY_LIST.SUCCESS:
let sessionIds = state.get('sessionIds');
@@ -110,29 +112,30 @@ 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
const events = action.filter.events;
- // const filters = action.filter.filters;
- const current = state.get('list').find(({ sessionId }) => sessionId === action.data.sessionId) || Session();
- const session = Session(action.data);
+ const session = new Session(action.data);
- const matching = [];
+ const matching: number[] = [];
- const visitedEvents = [];
- const tmpMap = {};
+ const visitedEvents: Location[] = [];
+ const tmpMap = new Set();
session.events.forEach((event) => {
- if (event.type === 'LOCATION' && !tmpMap.hasOwnProperty(event.url)) {
- tmpMap[event.url] = event.url;
+ if (event.type === 'LOCATION' && !tmpMap.has(event.url)) {
+ tmpMap.add(event.url);
visitedEvents.push(event);
}
});
@@ -151,23 +154,34 @@ const reducer = (state = initialState, action = {}) => {
});
});
return state
- .set('current', current.merge(session))
+ .set('current', session)
.set('eventsIndex', matching)
.set('visitedEvents', visitedEvents)
.set('host', visitedEvents[0] && visitedEvents[0].host);
}
case FETCH_FAVORITE_LIST.SUCCESS:
- return state.set('favoriteList', List(action.data).map(Session));
+ return state.set('favoriteList', action.data.map(s => new Session(s)));
case TOGGLE_FAVORITE.SUCCESS: {
const id = action.sessionId;
- const session = state.get('list').find(({ sessionId }) => sessionId === id);
+ let mutableState = state
+ const list = state.get('list') as unknown as Session[]
+ const sessionIdx = list.findIndex(({ sessionId }) => sessionId === id);
+ const session = list[sessionIdx]
+ const current = state.get('current') as unknown as Session;
const wasInFavorite = state.get('favoriteList').findIndex(({ sessionId }) => sessionId === id) > -1;
- return state
- .update('current', (currentSession) => (currentSession.sessionId === id ? currentSession.set('favorite', !wasInFavorite) : currentSession))
- .update('list', (list) => list.map((listSession) => (listSession.sessionId === id ? listSession.set('favorite', !wasInFavorite) : listSession)))
- .update('favoriteList', (list) => session ?
- wasInFavorite ? list.filter(({ sessionId }) => sessionId !== id) : list.push(session.set('favorite', true)) : list
+ if (session && !wasInFavorite) {
+ session.favorite = true
+ mutableState = mutableState.updateIn(['list', sessionIdx], () => session)
+ }
+ if (current.sessionId === id) {
+ mutableState = mutableState.update('current',
+ (s: Session) => ({ ...s, favorite: !wasInFavorite})
+ )
+ }
+ return mutableState
+ .update('favoriteList', (list: Session[]) => session ?
+ wasInFavorite ? list.filter(({ sessionId }) => sessionId !== id) : list.push(session) : list
);
}
case SORT: {
@@ -176,7 +190,7 @@ const reducer = (state = initialState, action = {}) => {
diff = diff === 0 ? s1.startedAt - s2.startedAt : diff;
return action.sign * diff;
};
- return state.update('list', (list) => list.sort(comparator)).update('favoriteList', (list) => list.sort(comparator));
+ return state.update('list', (list: Session[]) => list.sort(comparator)).update('favoriteList', (list: Session[]) => list.sort(comparator));
}
case REDEFINE_TARGET: {
// TODO: update for list
@@ -209,8 +223,16 @@ const reducer = (state = initialState, action = {}) => {
case SET_EDIT_NOTE_TOOLTIP:
return state.set('createNoteTooltip', action.noteTooltip);
case FILTER_OUT_NOTE:
- return state.updateIn(['current', 'notesWithEvents'], (list) =>
- list.filter(evt => !evt.noteId || evt.noteId !== action.noteId)
+ return state.updateIn(['current'],
+ (session: Session) => ({
+ ...session,
+ notesWithEvents: session.notesWithEvents.filter(item => {
+ if ('noteId' in item) {
+ return item.noteId !== action.noteId
+ }
+ return true
+ })
+ })
)
case ADD_NOTE:
return state.updateIn(['current', 'notesWithEvents'], (list) =>
@@ -222,25 +244,21 @@ const reducer = (state = initialState, action = {}) => {
})
)
case UPDATE_NOTE:
- const index = state.getIn(['current', 'notesWithEvents']).findIndex(item => item.noteId === action.note.noteId)
- return state.setIn(['current', 'notesWithEvents', index], action.note)
+ const noteIndex = state.getIn(['current']).notesWithEvents.findIndex(item => item.noteId === action.note.noteId)
+ return state.setIn(['current', 'notesWithEvents', noteIndex], action.note)
case SET_SESSION_PATH:
return state.set('sessionPath', action.path);
case LAST_PLAYED_SESSION_ID:
- return updateListItem(state, action.sessionId, { viewed: true }).set('lastPlayedSessionId', action.sessionId);
+ const sessionList = state.get('list') as unknown as Session[];
+ const sIndex = sessionList.findIndex(({ sessionId }) => sessionId === action.sessionId);
+ if (sIndex === -1) return state;
+
+ return state.updateIn(['list', sIndex], (session: Session) => ({ ...session, viewed: true }));
default:
return state;
}
};
-function updateListItem(state, sourceSessionId, instance) {
- const list = state.get('list');
- const index = list.findIndex(({ sessionId }) => sessionId === sourceSessionId);
- if (index === -1) return state;
-
- return state.updateIn(['list', index], (session) => session.merge(instance));
-}
-
export default withRequestState(
{
_: [FETCH, FETCH_LIST],
@@ -253,13 +271,6 @@ export default withRequestState(
reducer
);
-function init(session) {
- return {
- type: INIT,
- session,
- };
-}
-
export const fetchList =
(params = {}, force = false) =>
(dispatch) => {
@@ -316,13 +327,6 @@ export function toggleFavorite(sessionId) {
};
}
-export function fetchFavoriteList() {
- return {
- types: FETCH_FAVORITE_LIST.toArray(),
- call: (client) => client.get('/sessions/favorite'),
- };
-}
-
export function fetchInsights(params) {
return {
types: FETCH_INSIGHTS.toArray(),
@@ -353,13 +357,6 @@ export function sort(sortKey, sign = 1, listName = 'list') {
};
}
-export function redefineTarget(target) {
- return {
- type: REDEFINE_TARGET,
- target,
- };
-}
-
export const setAutoplayValues = (sessionId) => {
return {
type: SET_AUTOPLAY_VALUES,
diff --git a/frontend/app/duck/target.js b/frontend/app/duck/target.js
deleted file mode 100644
index b1f0e337b..000000000
--- a/frontend/app/duck/target.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Map } from 'immutable';
-import Target from 'Types/target';
-import { RequestTypes } from 'Duck/requestStateCreator';
-import crudDuckGenerator from 'Duck/tools/crudDuck';
-import { reduceDucks } from 'Duck/tools';
-
-const FETCH_DEFINED = new RequestTypes('targets/FETCH_DEFINED');
-
-const initialState = Map({
- definedPercent: 0,
-});
-
-const reducer = (state = initialState, action = {}) => {
- switch (action.type) {
- case FETCH_DEFINED.SUCCESS:
- return state.set(
- 'definedPercent',
- Math.round((action.data.labeled / action.data.total) * 100),
- );
- }
- return state;
-};
-
-const crudDuck = crudDuckGenerator('target', Target);
-export const { fetchList, init, edit, save, remove } = crudDuck.actions;
-export default reduceDucks(crudDuck, { initialState, reducer }).reducer;
-
-export function fetchDefinedTargetsCount() {
- return {
- types: FETCH_DEFINED.toArray(),
- call: client => client.get('/targets/count'),
- };
-}
diff --git a/frontend/app/duck/targetCustom.js b/frontend/app/duck/targetCustom.js
deleted file mode 100644
index fd63ed657..000000000
--- a/frontend/app/duck/targetCustom.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Map } from 'immutable';
-import TargetCustom from 'Types/targetCustom';
-import crudDuckGenerator from 'Duck/tools/crudDuck';
-import { reduceDucks } from 'Duck/tools';
-
-
-const crudDuck = crudDuckGenerator('customTarget', TargetCustom, { endpoints: {
- fetchList: '/targets_temp',
- save: '/targets_temp',
- remove: '/targets_temp',
-}});
-export const { fetchList, init, edit, save, remove } = crudDuck.actions;
-export default crudDuck.reducer;
diff --git a/frontend/app/duck/variables.js b/frontend/app/duck/variables.js
deleted file mode 100644
index 21a0131c4..000000000
--- a/frontend/app/duck/variables.js
+++ /dev/null
@@ -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;
diff --git a/frontend/app/duck/watchdogs.js b/frontend/app/duck/watchdogs.js
deleted file mode 100644
index 87966264a..000000000
--- a/frontend/app/duck/watchdogs.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import { List, Map } from 'immutable';
-import Watchdog from 'Types/watchdog';
-import { mergeReducers, success, array, request } from './funcTools/tools';
-import { createRequestReducer } from './funcTools/request';
-import {
- createCRUDReducer,
- getCRUDRequestTypes,
- createFetchList,
- createInit,
- createEdit,
- createRemove,
- createSave,
-} from './funcTools/crud';
-
-const name = 'issue_type';
-const idKey = 'id';
-const SET_ACTIVE_TAB = 'watchdogs/SET_ACTIVE_TAB';
-const FETCH_WATCHDOG_STATUS = 'watchdogs/FETCH_WATCHDOG_STATUS';
-const FETCH_WATCHDOG_STATUS_SUCCESS = success(FETCH_WATCHDOG_STATUS);
-const FETCH_RULES = 'watchdogs/FETCH_RULES';
-const FETCH_RULES_SUCCESS = success(FETCH_RULES);
-const SAVE_CAPTURE_RATE = 'watchdogs/SAVE_CAPTURE_RATE';
-const EDIT_CAPTURE_RATE = 'watchdogs/SAVE_CAPTURE_RATE';
-
-const initialState = Map({
- activeTab: Map(),
- instance: Watchdog(),
- list: List(),
- rules: List(),
- captureRate: Map()
-});
-
-const reducer = (state = initialState, action = {}) => {
- switch (action.type) {
- case SET_ACTIVE_TAB:
- return state.set('activeTab', action.instance);
- case FETCH_RULES_SUCCESS:
- return state.set('rules', action.data);
- case FETCH_WATCHDOG_STATUS_SUCCESS:
- case success(SAVE_CAPTURE_RATE):
- return state.set('captureRate', Map(action.data));
- case request(SAVE_CAPTURE_RATE):
- return state.mergeIn(['captureRate'], action.params);
- case EDIT_CAPTURE_RATE:
- return state.mergeIn(['captureRate'], {rate: action.rate});
- }
- return state;
-};
-
-
-export const fetchList = createFetchList(name);
-export const init = createInit(name);
-export const edit = createEdit(name);
-export const save = createSave(name);
-export const remove = createRemove(name);
-
-export function setActiveTab(instance) {
- return {
- type: SET_ACTIVE_TAB,
- instance,
- };
-}
-
-export const fetchRules = () => {
- return {
- types: array(FETCH_RULES),
- call: client => client.get(`/watchdogs/rules`),
- };
-}
-
-export default mergeReducers(
- reducer,
- createCRUDReducer(name, Watchdog, idKey),
- createRequestReducer({
- fetchWatchdogStatus: FETCH_WATCHDOG_STATUS,
- savingCaptureRate: SAVE_CAPTURE_RATE,
- ...getCRUDRequestTypes(name),
- }),
-);
-
-export const saveCaptureRate = (params) => {
- return {
- params,
- types: array(SAVE_CAPTURE_RATE),
- call: client => client.post(`/sample_rate`, params),
- }
-}
-
-export const editCaptureRate = rate => {
- return {
- type: EDIT_CAPTURE_RATE,
- rate
- }
-}
-
-export const fetchWatchdogStatus = () => {
- return {
- types: array(FETCH_WATCHDOG_STATUS),
- call: client => client.get('/sample_rate'),
- };
-}
diff --git a/frontend/app/hooks/useCellMeasurerCache.ts b/frontend/app/hooks/useCellMeasurerCache.ts
new file mode 100644
index 000000000..692f2629f
--- /dev/null
+++ b/frontend/app/hooks/useCellMeasurerCache.ts
@@ -0,0 +1,12 @@
+import { useMemo } from 'react'
+import { CellMeasurerCache, CellMeasurerCacheParams } from 'react-virtualized';
+import useLatestRef from 'App/hooks/useLatestRef'
+
+export default function useCellMeasurerCache(itemList?: any[], options?: CellMeasurerCacheParams) {
+ const filteredListRef = itemList ? useLatestRef(itemList) : undefined
+ return useMemo(() => new CellMeasurerCache({
+ fixedWidth: true,
+ keyMapper: filteredListRef ? (index) => filteredListRef.current[index] : undefined,
+ ...options
+ }), [])
+}
\ No newline at end of file
diff --git a/frontend/app/mstore/assistMultiviewStore.ts b/frontend/app/mstore/assistMultiviewStore.ts
index e0895918f..379f852cb 100644
--- a/frontend/app/mstore/assistMultiviewStore.ts
+++ b/frontend/app/mstore/assistMultiviewStore.ts
@@ -95,7 +95,7 @@ export default class AssistMultiviewStore {
const matchingSessions = data.sessions.filter(
(s: Record) => ids.includes(s.sessionID) || ids.includes(s.sessionId)
);
- const immutMatchingSessions = List(matchingSessions).map(Session);
+ const immutMatchingSessions = List(matchingSessions).map(s => new Session(s));
immutMatchingSessions.forEach((session: Record) => {
this.addSession(session);
this.fetchAgentTokenInfo(session.sessionId);
diff --git a/frontend/app/mstore/index.tsx b/frontend/app/mstore/index.tsx
index a4fdbe884..707fb175a 100644
--- a/frontend/app/mstore/index.tsx
+++ b/frontend/app/mstore/index.tsx
@@ -15,6 +15,7 @@ import {
errorService,
notesService,
recordingsService,
+ configService,
} from 'App/services';
import SettingsStore from './settingsStore';
import AuditStore from './auditStore';
@@ -25,6 +26,7 @@ import NotesStore from './notesStore';
import BugReportStore from './bugReportStore'
import RecordingsStore from './recordingsStore'
import AssistMultiviewStore from './assistMultiviewStore';
+import WeeklyReportStore from './weeklyReportConfigStore'
export class RootStore {
dashboardStore: DashboardStore;
@@ -41,6 +43,7 @@ export class RootStore {
bugReportStore: BugReportStore;
recordingsStore: RecordingsStore;
assistMultiviewStore: AssistMultiviewStore;
+ weeklyReportStore: WeeklyReportStore
constructor() {
this.dashboardStore = new DashboardStore();
@@ -57,6 +60,7 @@ export class RootStore {
this.bugReportStore = new BugReportStore();
this.recordingsStore = new RecordingsStore();
this.assistMultiviewStore = new AssistMultiviewStore();
+ this.weeklyReportStore = new WeeklyReportStore();
}
initClient() {
@@ -70,6 +74,7 @@ export class RootStore {
errorService.initClient(client);
notesService.initClient(client)
recordingsService.initClient(client);
+ configService.initClient(client);
}
}
diff --git a/frontend/app/mstore/types/session.ts b/frontend/app/mstore/types/session.ts
index 12b031d8a..d0984593e 100644
--- a/frontend/app/mstore/types/session.ts
+++ b/frontend/app/mstore/types/session.ts
@@ -1,6 +1,6 @@
import { runInAction, makeAutoObservable, observable } from 'mobx'
-import { List, Map } from 'immutable';
-import { DateTime, Duration } from 'luxon';
+import { Map } from 'immutable';
+import { Duration } from 'luxon';
const HASH_MOD = 1610612741;
const HASH_P = 53;
diff --git a/frontend/app/mstore/userStore.ts b/frontend/app/mstore/userStore.ts
index 8e8913de6..b46f776a3 100644
--- a/frontend/app/mstore/userStore.ts
+++ b/frontend/app/mstore/userStore.ts
@@ -44,7 +44,7 @@ export default class UserStore {
}
initUser(user?: any ): Promise {
- 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') {
diff --git a/frontend/app/mstore/weeklyReportConfigStore.ts b/frontend/app/mstore/weeklyReportConfigStore.ts
new file mode 100644
index 000000000..7b0eec8a6
--- /dev/null
+++ b/frontend/app/mstore/weeklyReportConfigStore.ts
@@ -0,0 +1,32 @@
+import { makeAutoObservable }from "mobx"
+import { configService } from "App/services";
+
+export default class weeklyReportConfigStore {
+ public weeklyReport = false
+
+ constructor() {
+ makeAutoObservable(this)
+ }
+
+ setReport(value: boolean) {
+ this.weeklyReport = value
+ }
+
+ async fetchReport() {
+ try {
+ const { weeklyReport } = await configService.fetchWeeklyReport()
+ return this.setReport(weeklyReport)
+ } catch (e) {
+ console.error(e)
+ }
+ }
+
+ async fetchEditReport(value: boolean) {
+ try {
+ const { weeklyReport } = await configService.editWeeklyReport({ weeklyReport: value })
+ return this.setReport(weeklyReport)
+ } catch (e) {
+ console.error(e)
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts
index 83b82d0b6..6158ae0f9 100644
--- a/frontend/app/player/web/WebPlayer.ts
+++ b/frontend/app/player/web/WebPlayer.ts
@@ -26,11 +26,12 @@ export default class WebPlayer extends Player {
private targetMarker: TargetMarker
constructor(protected wpState: Store, session: any, live: boolean) {
+ console.log(session.events, session.stackEvents, session.resources, session.errors)
let initialLists = live ? {} : {
- event: session.events.toJSON(),
- stack: session.stackEvents.toJSON(),
- resource: session.resources.toJSON(), // MBTODO: put ResourceTiming in file
- exceptions: session.errors.toJSON().map(({ time, errorId, name }: any) =>
+ event: session.events,
+ stack: session.stackEvents,
+ resource: session.resources, // MBTODO: put ResourceTiming in file
+ exceptions: session.errors.map(({ time, errorId, name }: any) =>
Log({
level: LogLevel.ERROR,
value: name,
diff --git a/frontend/app/services/BaseService.ts b/frontend/app/services/BaseService.ts
index 2af1897f8..5cca28d4e 100644
--- a/frontend/app/services/BaseService.ts
+++ b/frontend/app/services/BaseService.ts
@@ -1,4 +1,5 @@
import APIClient from 'App/api_client';
+
export default class BaseService {
client: APIClient;
diff --git a/frontend/app/services/ConfigService.ts b/frontend/app/services/ConfigService.ts
new file mode 100644
index 000000000..6afda4858
--- /dev/null
+++ b/frontend/app/services/ConfigService.ts
@@ -0,0 +1,17 @@
+import BaseService from './BaseService';
+
+export interface WeeklyReport {
+ weeklyReport: boolean
+}
+
+export default class ConfigService extends BaseService {
+ async fetchWeeklyReport(): Promise {
+ return this.client.get('/config/weekly_report')
+ .then(r => r.json()).then(j => j.data)
+ }
+
+ async editWeeklyReport(config: WeeklyReport): Promise {
+ return this.client.post('/config/weekly_report', config)
+ .then(r => r.json()).then(j => j.data)
+ }
+}
\ No newline at end of file
diff --git a/frontend/app/services/index.ts b/frontend/app/services/index.ts
index 2bcf5981e..5033c4ed3 100644
--- a/frontend/app/services/index.ts
+++ b/frontend/app/services/index.ts
@@ -7,6 +7,7 @@ import AuditService from './AuditService';
import ErrorService from "./ErrorService";
import NotesService from "./NotesService";
import RecordingsService from "./RecordingsService";
+import ConfigService from './ConfigService'
export const dashboardService = new DashboardService();
export const metricService = new MetricService();
@@ -17,3 +18,4 @@ export const auditService = new AuditService();
export const errorService = new ErrorService();
export const notesService = new NotesService();
export const recordingsService = new RecordingsService();
+export const configService = new ConfigService();
\ No newline at end of file
diff --git a/frontend/app/types/address.js b/frontend/app/types/address.js
deleted file mode 100644
index 8d8e2ccf2..000000000
--- a/frontend/app/types/address.js
+++ /dev/null
@@ -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;
- },
- },
-});
diff --git a/frontend/app/types/appTest.js b/frontend/app/types/appTest.js
deleted file mode 100644
index 248b759b7..000000000
--- a/frontend/app/types/appTest.js
+++ /dev/null
@@ -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;
diff --git a/frontend/app/types/dashboard/index.js b/frontend/app/types/dashboard/index.js
index c250d64d6..670f44a2c 100644
--- a/frontend/app/types/dashboard/index.js
+++ b/frontend/app/types/dashboard/index.js
@@ -97,14 +97,14 @@ export const WIDGET_LIST = [{
name: "Recent Frustrations",
description: "List of recent sessions where users experienced some kind of frustrations, such as click rage.",
thumb: 'recent_frustrations.png',
- dataWrapper: list => List(list).map(Session),
+ dataWrapper: list => List(list).map(s => new Session(s)),
},
{
key: "sessionsFeedback",
name: "Recent Negative Feedback",
description: "List of recent sessions where users reported an issue or a bad experience.",
thumb: 'negative_feedback.png',
- dataWrapper: list => List(list).map(Session),
+ dataWrapper: list => List(list).map(s => new Session(s)),
},
{
key: "missingResources",
diff --git a/frontend/app/types/errorInfo.js b/frontend/app/types/errorInfo.js
index b3bb48e1d..7da9c3a9b 100644
--- a/frontend/app/types/errorInfo.js
+++ b/frontend/app/types/errorInfo.js
@@ -37,12 +37,12 @@ const ErrorInfo = Record({
chart30: [],
tags: [],
customTags: [],
- lastHydratedSession: Session(),
+ lastHydratedSession: new Session(),
disabled: false,
}, {
fromJS: ({ stack, lastHydratedSession, ...other }) => ({
...other,
- lastHydratedSession: Session(lastHydratedSession),
+ lastHydratedSession: new Session(lastHydratedSession),
stack0InfoString: getStck0InfoString(stack || []),
})
});
diff --git a/frontend/app/types/filter/savedFilter.js b/frontend/app/types/filter/savedFilter.js
index f10b0686b..7b414430f 100644
--- a/frontend/app/types/filter/savedFilter.js
+++ b/frontend/app/types/filter/savedFilter.js
@@ -11,7 +11,6 @@ export default Record({
filter: Filter(),
createdAt: undefined,
count: 0,
- watchdogs: List(),
isPublic: false,
}, {
idKey: 'searchId',
diff --git a/frontend/app/types/member.ts b/frontend/app/types/member.ts
index d3914eac9..0a88d51ba 100644
--- a/frontend/app/types/member.ts
+++ b/frontend/app/types/member.ts
@@ -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,
}),
});
diff --git a/frontend/app/types/session/activity.js b/frontend/app/types/session/activity.js
deleted file mode 100644
index bb9be45fa..000000000
--- a/frontend/app/types/session/activity.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import Record from 'Types/Record';
-import { DateTime } from 'luxon';
-
-const ASSIGN = 'assign';
-const MESSAGE = 'message';
-const OPEN = 'open';
-const CLOSE = 'close';
-
-export const TYPES = { ASSIGN, MESSAGE, OPEN, CLOSE };
-
-const Activity = Record({
- id: undefined,
- type: '',
- author: '',
- // thread_id: undefined,
- createdAt: undefined,
- // assigned_to: undefined,
- // user_id: undefined,
- message: '',
- user: ''
-})
-
-// const Assign = Activity.extend({
-// type: ASSIGN,
-// })
-
-// const Message = Activity.extend({
-// type: MESSAGE,
-// })
-
-// const Open = Activity.extend({
-// type: OPEN,
-// })
-
-// const Close = Activity.extend({
-// type: CLOSE,
-// })
-
-// const Open = Activity.extend({
-// type: OPEN,
-// })
-
-export default function(activity = {}) {
- // if (activity.type === ASSIGN) return Assign(activity);
- // if (activity.type === MESSAGE) return Message(activity);
- // if (activity.type === OPEN) return Open(activity);
- // if (activity.type === CLOSE) return Close(activity);
- return Activity({
- ...activity,
- createdAt: activity.createdAt ? DateTime.fromMillis(activity.createdAt, {}).toUTC() : undefined,
- });
-}
-
diff --git a/frontend/app/types/session/activity.ts b/frontend/app/types/session/activity.ts
new file mode 100644
index 000000000..97e5e2a5c
--- /dev/null
+++ b/frontend/app/types/session/activity.ts
@@ -0,0 +1,49 @@
+import { DateTime } from 'luxon';
+
+const ASSIGN = 'assign';
+const MESSAGE = 'message';
+const OPEN = 'open';
+const CLOSE = 'close';
+
+export const TYPES = { ASSIGN, MESSAGE, OPEN, CLOSE } as const;
+
+
+type TypeKeys = keyof typeof TYPES
+type TypeValues = typeof TYPES[TypeKeys]
+
+
+export interface IActivity {
+ id: string;
+ type: TypeValues;
+ author: string;
+ createdAt: number;
+ message: string;
+ user: string;
+}
+
+export default class Activity {
+ id: IActivity["id"];
+ type: IActivity["type"];
+ author: IActivity["author"];
+ createdAt?: DateTime;
+ message: IActivity["message"];
+ user: IActivity["user"];
+
+ constructor(activity?: IActivity) {
+ if (activity) {
+ Object.assign(this, {
+ ...activity,
+ createdAt: activity.createdAt ? DateTime.fromMillis(activity.createdAt, {}).toUTC() : undefined,
+ })
+ } else {
+ Object.assign(this, {
+ id: undefined,
+ type: '',
+ author: '',
+ createdAt: undefined,
+ message: '',
+ user: ''
+ })
+ }
+ }
+}
diff --git a/frontend/app/types/session/assignment.js b/frontend/app/types/session/assignment.js
deleted file mode 100644
index 0c599c85c..000000000
--- a/frontend/app/types/session/assignment.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import Record from 'Types/Record';
-import Activity from './activity';
-import { List } from 'immutable';
-import { DateTime } from 'luxon';
-import { validateName, notEmptyString } from 'App/validate';
-
-export default Record({
- id: undefined,
- title: '',
- timestamp: undefined,
- creatorId: undefined,
- sessionId: undefined,
- projectId: '',
- siteId: undefined,
- activities: List(),
- closed: false,
- assignee: '',
- commentsCount: undefined,
- issueType: '',
- description: '',
- iconUrl: ''
-}, {
- fromJS: (assignment) => ({
- ...assignment,
- timestamp: assignment.createdAt ? DateTime.fromISO(assignment.createdAt) : undefined,
- activities: assignment.comments ? List(assignment.comments).map(activity => {
- if (assignment.users) {
- activity.user = assignment.users.filter(user => user.id === activity.author).first();
- }
- return Activity(activity)
- }) : List()
- }),
- methods: {
- validate: function() {
- return !!this.projectId && !!this.issueType &&
- notEmptyString(this.title) && notEmptyString(this.description)
- },
- toCreate: function() {
- return {
- title: this.title,
- description: this.description,
- assignee: this.assignee,
- issueType: this.issueType
- }
- }
- }
-})
diff --git a/frontend/app/types/session/assignment.ts b/frontend/app/types/session/assignment.ts
new file mode 100644
index 000000000..a16361ee8
--- /dev/null
+++ b/frontend/app/types/session/assignment.ts
@@ -0,0 +1,77 @@
+import Activity, { IActivity } from './activity';
+import { DateTime } from 'luxon';
+import { notEmptyString } from 'App/validate';
+
+interface IAssignment {
+ id: string;
+ title: string;
+ timestamp: number;
+ creatorId: string;
+ sessionId: string;
+ projectId: string;
+ siteId: string;
+ activities: [];
+ closed: boolean;
+ assignee: string;
+ commentsCount: number;
+ issueType: string;
+ description: string;
+ iconUrl: string;
+ createdAt?: string;
+ comments: IActivity[]
+ users: { id: string }[]
+}
+
+export default class Assignment {
+ id: IAssignment["id"];
+ title: IAssignment["title"] = '';
+ timestamp: IAssignment["timestamp"];
+ creatorId: IAssignment["creatorId"];
+ sessionId: IAssignment["sessionId"];
+ projectId: IAssignment["projectId"] = '';
+ siteId: IAssignment["siteId"];
+ activities: IAssignment["activities"];
+ closed: IAssignment["closed"];
+ assignee: IAssignment["assignee"] = '';
+ commentsCount: IAssignment["commentsCount"];
+ issueType: IAssignment["issueType"] = '';
+ description: IAssignment["description"] = '';
+ iconUrl: IAssignment["iconUrl"] = '';
+
+ constructor(assignment?: IAssignment) {
+ if (assignment) {
+ Object.assign(this, {
+ ...assignment,
+ timestamp: assignment.createdAt ? DateTime.fromISO(assignment.createdAt) : undefined,
+ activities: assignment.comments ? assignment.comments.map(activity => {
+ if (assignment.users) {
+ // @ts-ignore ???
+ activity.user = assignment.users.filter(user => user.id === activity.author)[0];
+ }
+ return new Activity(activity)
+ }) : []
+ })
+ }
+ }
+
+ toJS() {
+ return this
+ }
+
+ validate() {
+ return !!this.projectId && !!this.issueType && notEmptyString(this.title) && notEmptyString(this.description)
+ }
+
+ get isValid() {
+ return !!this.projectId && !!this.issueType && notEmptyString(this.title) && notEmptyString(this.description)
+ }
+
+ toCreate() {
+ return {
+ title: this.title,
+ description: this.description,
+ assignee: this.assignee,
+ issueType: this.issueType
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/app/types/session/author.js b/frontend/app/types/session/author.js
deleted file mode 100644
index 12edc3c1e..000000000
--- a/frontend/app/types/session/author.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import Record from 'Types/Record';
-
-export default Record({
- id: undefined,
- avatarUrls: undefined,
- name: undefined,
-}, {
- fromJS: author => ({
- ...author,
- })
-})
diff --git a/frontend/app/types/session/customField.js b/frontend/app/types/session/customField.js
deleted file mode 100644
index 79f26b914..000000000
--- a/frontend/app/types/session/customField.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import CustomField from 'Types/customField';
-
-export default CustomField.extend({
- value: undefined,
-});
\ No newline at end of file
diff --git a/frontend/app/types/session/error.js b/frontend/app/types/session/error.js
deleted file mode 100644
index 9b0fcb278..000000000
--- a/frontend/app/types/session/error.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import Record from 'Types/Record';
-
-
-function getStck0InfoString(stack) {
- const stack0 = stack[0];
- if (!stack0) return "";
- let s = stack0.function || "";
- if (stack0.url) {
- s += ` (${stack0.url})`;
- }
- return s;
-}
-
-
-export default Record({
- sessionId: undefined,
- messageId: undefined,
- timestamp: undefined,
- errorId: undefined,
- projectId: undefined,
- source: undefined,
- name: undefined,
- message: undefined,
- time: undefined,
- function: '?',
-}, {
- fromJS: ({ stack, ...rest }) => ({
- ...rest,
- stack0InfoString: getStck0InfoString(stack || []),
- function: (stack && stack[0] && stack[0].function) || '?',
- }),
-});
diff --git a/frontend/app/types/session/error.ts b/frontend/app/types/session/error.ts
new file mode 100644
index 000000000..66d2db096
--- /dev/null
+++ b/frontend/app/types/session/error.ts
@@ -0,0 +1,48 @@
+import Record from 'Types/Record';
+
+function getStck0InfoString(stack: Stack) {
+ const stack0 = stack[0];
+ if (!stack0) return "";
+ let s = stack0.function || "";
+ if (stack0.url) {
+ s += ` (${stack0.url})`;
+ }
+ return s;
+}
+
+type Stack = { function: string; url: string }[]
+
+export interface IError {
+ sessionId: string
+ messageId: string
+ timestamp: number
+ errorId: string
+ projectId: string
+ source: string
+ name: string
+ message: string
+ time: number
+ function: string
+ stack: Stack
+}
+
+export default class Error {
+ sessionId: IError["sessionId"];
+ messageId: IError["messageId"];
+ timestamp: IError["timestamp"];
+ errorId: IError["errorId"];
+ projectId: IError["projectId"];
+ source: IError["source"];
+ name: IError["name"];
+ message: IError["message"];
+ time: IError["time"];
+ function: IError["function"];
+
+ constructor({ stack, ...rest }: IError) {
+ Object.assign(this, {
+ ...rest,
+ stack0InfoString: getStck0InfoString(stack || []),
+ function: (stack && stack[0] && stack[0].function) || '?',
+ })
+ }
+}
diff --git a/frontend/app/types/session/errorStack.js b/frontend/app/types/session/errorStack.js
deleted file mode 100644
index b92610229..000000000
--- a/frontend/app/types/session/errorStack.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import Record from 'Types/Record';
-
-export default Record({
- // url: undefined,
- absPath: undefined,
- filename: undefined,
- // args: [],
- function: undefined,
- lineNo: undefined,
- colNo: undefined,
- offset: 0,
- context: undefined
-});
diff --git a/frontend/app/types/session/errorStack.ts b/frontend/app/types/session/errorStack.ts
new file mode 100644
index 000000000..59e63d16f
--- /dev/null
+++ b/frontend/app/types/session/errorStack.ts
@@ -0,0 +1,26 @@
+interface IErrorStack {
+ absPath?: string,
+ filename?: string,
+ function?: string,
+ lineNo?: number,
+ colNo?: number,
+ offset?: number,
+ context?: string
+}
+
+export default class ErrorStack {
+ absPath: IErrorStack["absPath"]
+ filename: IErrorStack["filename"]
+ function: IErrorStack["function"]
+ lineNo: IErrorStack["lineNo"]
+ colNo: IErrorStack["colNo"]
+ offset: IErrorStack["offset"]
+ context: IErrorStack["context"]
+
+ constructor(es: IErrorStack) {
+ Object.assign(this, {
+ ...es,
+ offset: es.offset || 0,
+ })
+ }
+}
\ No newline at end of file
diff --git a/frontend/app/types/session/event.js b/frontend/app/types/session/event.js
deleted file mode 100644
index 537de1724..000000000
--- a/frontend/app/types/session/event.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import Record from 'Types/Record';
-import Target from 'Types/target';
-
-const CONSOLE = 'CONSOLE';
-const CLICK = 'CLICK';
-const INPUT = 'INPUT';
-const LOCATION = 'LOCATION';
-const CUSTOM = 'CUSTOM';
-const CLICKRAGE = 'CLICKRAGE';
-const IOS_VIEW = 'VIEW';
-export const TYPES = { CONSOLE, CLICK, INPUT, LOCATION, CUSTOM, CLICKRAGE, IOS_VIEW};
-
-
-const Event = Record({
- time: 0,
- label: ''
-}, {
- fromJS: event => ({
- ...event,
- target: Target(event.target || { path: event.targetPath }),
- })
-})
-
-const Console = Event.extend({
- type: CONSOLE,
- subtype: '', // level ???
- value: '',
-},{
- name: 'Console'
-})
-
-const Click = Event.extend({
- type: CLICK,
- targetContent: '',
- target: Target(),
- count: undefined
-}, {
- name: 'Click'
-});
-
-const Input = Event.extend({
- type: INPUT,
- target: Target(),
- value: '',
-},{
- name: 'Input'
-});
-
-const View = Event.extend({
- type: IOS_VIEW,
- name: '',
-},{
- name: 'View'
-})
-
-const Location = Event.extend({
- type: LOCATION,
- url: '',
- host: '',
- pageLoad: false,
- fcpTime: undefined,
- //fpTime: undefined,
- loadTime: undefined,
- domContentLoadedTime: undefined,
- domBuildingTime: undefined,
- speedIndex: undefined,
- visuallyComplete: undefined,
- timeToInteractive: undefined,
- referrer: '',
-}, {
- fromJS: event => ({
- ...event,
- //fpTime: event.firstPaintTime,
- fcpTime: event.firstContentfulPaintTime || event.firstPaintTime,
- }),
- name: 'Location'
-});
-
-const TYPE_CONSTRUCTOR_MAP = {
- [CONSOLE]: Console,
- [CLICK]: Click,
- [INPUT]: Input,
- [LOCATION]: Location,
- [CLICKRAGE]: Click,
- [IOS_VIEW]: View,
-}
-
-export default function(event = {}) {
- return (TYPE_CONSTRUCTOR_MAP[event.type] || Event)(event);
-}
-
diff --git a/frontend/app/types/session/event.ts b/frontend/app/types/session/event.ts
new file mode 100644
index 000000000..575183b93
--- /dev/null
+++ b/frontend/app/types/session/event.ts
@@ -0,0 +1,162 @@
+import Record from 'Types/Record';
+import Target from 'Types/target';
+
+const CONSOLE = 'CONSOLE';
+const CLICK = 'CLICK';
+const INPUT = 'INPUT';
+const LOCATION = 'LOCATION';
+const CUSTOM = 'CUSTOM';
+const CLICKRAGE = 'CLICKRAGE';
+const IOS_VIEW = 'VIEW';
+export const TYPES = { CONSOLE, CLICK, INPUT, LOCATION, CUSTOM, CLICKRAGE, IOS_VIEW};
+
+interface IEvent {
+ time: number;
+ timestamp: number;
+ type: typeof CONSOLE | typeof CLICK | typeof INPUT | typeof LOCATION | typeof CUSTOM | typeof CLICKRAGE;
+ name: string;
+ key: number;
+ label: string;
+ targetPath: string;
+ target: {
+ path: string;
+ label: string;
+ }
+}
+interface ConsoleEvent extends IEvent {
+ subtype: string
+ value: string
+}
+interface ClickEvent extends IEvent {
+ targetContent: string;
+ count: number;
+}
+interface InputEvent extends IEvent {
+ value: string;
+}
+
+interface LocationEvent extends IEvent {
+ url: string;
+ host: string;
+ pageLoad: boolean;
+ fcpTime: number;
+ loadTime: number;
+ domContentLoadedTime: number;
+ domBuildingTime: number;
+ speedIndex: number;
+ visuallyComplete: number;
+ timeToInteractive: number;
+ referrer: string;
+ firstContentfulPaintTime: number;
+ firstPaintTime: number;
+}
+
+export type EventData = ConsoleEvent | ClickEvent | InputEvent | LocationEvent | IEvent;
+
+class Event {
+ key: IEvent["key"]
+ time: IEvent["time"];
+ label: IEvent["label"];
+ target: IEvent["target"];
+
+
+ constructor(event: IEvent) {
+ Object.assign(this, {
+ time: event.time,
+ label: event.label,
+ key: event.key,
+ target: {
+ path: event.target?.path || event.targetPath,
+ label: event.target?.label
+ }
+ })
+ }
+}
+
+class Console extends Event {
+ readonly type = CONSOLE;
+ readonly name = 'Console'
+ subtype: string;
+ value: string;
+
+ constructor(evt: ConsoleEvent) {
+ super(evt);
+ this.subtype = evt.subtype
+ this.value = evt.value
+ }
+}
+
+class Click extends Event {
+ readonly type = CLICK;
+ readonly name = 'Click'
+ targetContent = '';
+ count: number
+
+ constructor(evt: ClickEvent) {
+ super(evt);
+ this.targetContent = evt.targetContent
+ this.count = evt.count
+ }
+}
+
+class Input extends Event {
+ readonly type = INPUT;
+ readonly name = 'Input'
+ value = ''
+
+ constructor(evt: InputEvent) {
+ super(evt);
+ this.value = evt.value
+ }
+}
+
+
+export class Location extends Event {
+ readonly name = 'Location';
+ readonly type = LOCATION;
+ url: LocationEvent["url"]
+ host: LocationEvent["host"];
+ pageLoad: LocationEvent["pageLoad"];
+ fcpTime: LocationEvent["fcpTime"];
+ loadTime: LocationEvent["loadTime"];
+ domContentLoadedTime: LocationEvent["domContentLoadedTime"];
+ domBuildingTime: LocationEvent["domBuildingTime"];
+ speedIndex: LocationEvent["speedIndex"];
+ visuallyComplete: LocationEvent["visuallyComplete"];
+ timeToInteractive: LocationEvent["timeToInteractive"];
+ referrer: LocationEvent["referrer"];
+
+ constructor(evt: LocationEvent) {
+ super(evt);
+ Object.assign(this, {
+ ...evt,
+ fcpTime: evt.firstContentfulPaintTime || evt.firstPaintTime
+ });
+ }
+}
+
+export type InjectedEvent = Console | Click | Input | Location;
+
+export default function(event: EventData) {
+ if (event.type && event.type === CONSOLE) {
+ return new Console(event as ConsoleEvent)
+ }
+ if (event.type && event.type === CLICK) {
+ return new Click(event as ClickEvent)
+ }
+ if (event.type && event.type === INPUT) {
+ return new Input(event as InputEvent)
+ }
+ if (event.type && event.type === LOCATION) {
+ return new Location(event as LocationEvent)
+ }
+ if (event.type && event.type === CLICKRAGE) {
+ return new Click(event as ClickEvent)
+ }
+ // not used right now?
+ // if (event.type === CUSTOM || !event.type) {
+ // return new Event(event)
+ // }
+ console.error(`Unknown event type: ${event.type}`)
+}
+
diff --git a/frontend/app/types/session/issue.js b/frontend/app/types/session/issue.js
deleted file mode 100644
index 308bf32be..000000000
--- a/frontend/app/types/session/issue.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import Record from 'Types/Record';
-import { List } from 'immutable';
-import Watchdog from 'Types/watchdog'
-export const issues_types = List([
- { 'type': 'all', 'visible': true, 'order': 0, 'name': 'All', 'icon': '' },
- { 'type': 'js_exception', 'visible': true, 'order': 1, 'name': 'Errors', 'icon': 'funnel/exclamation-circle' },
- { 'type': 'bad_request', 'visible': true, 'order': 2, 'name': 'Bad Requests', 'icon': 'funnel/file-medical-alt' },
- { 'type': 'click_rage', 'visible': true, 'order': 3, 'name': 'Click Rage', 'icon': 'funnel/emoji-angry' },
- { 'type': 'crash', 'visible': true, 'order': 4, 'name': 'Crashes', 'icon': 'funnel/file-earmark-break' },
- // { 'type': 'memory', 'visible': true, 'order': 4, 'name': 'High Memory', 'icon': 'funnel/sd-card' },
- // { 'type': 'vault', 'visible': true, 'order': 5, 'name': 'Vault', 'icon': 'safe' },
- // { 'type': 'bookmark', 'visible': true, 'order': 5, 'name': 'Bookmarks', 'icon': 'safe' },
- // { 'type': 'bad_request', 'visible': true, 'order': 1, 'name': 'Bad Requests', 'icon': 'funnel/file-medical-alt' },
- // { 'type': 'missing_resource', 'visible': true, 'order': 2, 'name': 'Missing Images', 'icon': 'funnel/image' },
- // { 'type': 'dead_click', 'visible': true, 'order': 4, 'name': 'Dead Clicks', 'icon': 'funnel/dizzy' },
- // { 'type': 'cpu', 'visible': true, 'order': 6, 'name': 'High CPU', 'icon': 'funnel/cpu' },
- // { 'type': 'custom', 'visible': false, 'order': 8, 'name': 'Custom', 'icon': 'funnel/exclamation-circle' }
-]).map(Watchdog)
-
-export const issues_types_map = {}
-issues_types.forEach(i => {
- issues_types_map[i.type] = { type: i.type, visible: i.visible, order: i.order, name: i.name, }
-});
-
-export default Record({
- issueId: undefined,
- name: '',
- visible: true,
- sessionId: undefined,
- time: undefined,
- seqIndex: undefined,
- payload: {},
- projectId: undefined,
- type: '',
- contextString: '',
- context: '',
- icon: 'info'
-}, {
- idKey: 'issueId',
- fromJS: ({ type, ...rest }) => ({
- ...rest,
- type,
- icon: issues_types_map[type]?.icon,
- name: issues_types_map[type]?.name,
- }),
-});
diff --git a/frontend/app/types/session/issue.ts b/frontend/app/types/session/issue.ts
new file mode 100644
index 000000000..68ab64001
--- /dev/null
+++ b/frontend/app/types/session/issue.ts
@@ -0,0 +1,85 @@
+import Record from 'Types/Record';
+
+const types = {
+ ALL: 'all',
+ JS_EXCEPTION: 'js_exception',
+ BAD_REQUEST: 'bad_request',
+ CRASH: 'crash',
+ CLICK_RAGE: 'click_rage'
+} as const
+
+type TypeKeys = keyof typeof types
+type TypeValues = typeof types[TypeKeys]
+
+type IssueType = {
+ [issueTypeKey in TypeValues]: { type: issueTypeKey; visible: boolean; order: number; name: string; icon: string };
+};
+
+export const issues_types = [
+ { 'type': types.ALL, 'visible': true, 'order': 0, 'name': 'All', 'icon': '' },
+ { 'type': types.JS_EXCEPTION, 'visible': true, 'order': 1, 'name': 'Errors', 'icon': 'funnel/exclamation-circle' },
+ { 'type': types.BAD_REQUEST, 'visible': true, 'order': 2, 'name': 'Bad Requests', 'icon': 'funnel/file-medical-alt' },
+ { 'type': types.CLICK_RAGE, 'visible': true, 'order': 3, 'name': 'Click Rage', 'icon': 'funnel/emoji-angry' },
+ { 'type': types.CRASH, 'visible': true, 'order': 4, 'name': 'Crashes', 'icon': 'funnel/file-earmark-break' },
+ // { 'type': 'memory', 'visible': true, 'order': 4, 'name': 'High Memory', 'icon': 'funnel/sd-card' },
+ // { 'type': 'vault', 'visible': true, 'order': 5, 'name': 'Vault', 'icon': 'safe' },
+ // { 'type': 'bookmark', 'visible': true, 'order': 5, 'name': 'Bookmarks', 'icon': 'safe' },
+ // { 'type': 'bad_request', 'visible': true, 'order': 1, 'name': 'Bad Requests', 'icon': 'funnel/file-medical-alt' },
+ // { 'type': 'missing_resource', 'visible': true, 'order': 2, 'name': 'Missing Images', 'icon': 'funnel/image' },
+ // { 'type': 'dead_click', 'visible': true, 'order': 4, 'name': 'Dead Clicks', 'icon': 'funnel/dizzy' },
+ // { 'type': 'cpu', 'visible': true, 'order': 6, 'name': 'High CPU', 'icon': 'funnel/cpu' },
+ // { 'type': 'custom', 'visible': false, 'order': 8, 'name': 'Custom', 'icon': 'funnel/exclamation-circle' }
+] as const
+
+const issues_types_map = {}
+issues_types.forEach((i) => {
+ Object.assign(issues_types_map, {
+ [i.type]: {
+ type: i.type,
+ visible: i.visible,
+ order: i.order,
+ name: i.name,
+ icon: i.icon,
+ }
+ })
+});
+
+export interface IIssue {
+ issueId: string
+ name: string
+ visible: boolean
+ sessionId: string
+ time: number
+ payload: Record
+ projectId: string
+ type: TypeValues
+ contextString: string
+ context: string
+ icon: string
+ timestamp: number
+ startedAt: number
+}
+
+export default class Issue {
+ issueId: IIssue["issueId"]
+ name: IIssue["name"]
+ visible: IIssue["visible"]
+ sessionId: IIssue["sessionId"]
+ time: IIssue["time"]
+ payload: IIssue["payload"]
+ projectId: IIssue["projectId"]
+ type: IIssue["type"]
+ contextString: IIssue["contextString"]
+ context: IIssue["context"]
+ icon: IIssue["icon"]
+ key: number
+
+ constructor({ type, ...rest }: IIssue & { key: number }) {
+ Object.assign(this, {
+ ...rest,
+ type,
+ icon: issues_types_map[type]?.icon,
+ name: issues_types_map[type]?.name
+ })
+ }
+}
\ No newline at end of file
diff --git a/frontend/app/types/session/profile.js b/frontend/app/types/session/profile.js
deleted file mode 100644
index 98b9bc345..000000000
--- a/frontend/app/types/session/profile.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import { List } from 'immutable';
-import Record from 'Types/Record';
-
-export default Record({
- name: '',
- args: List(),
- result: undefined,
- time: undefined,
- index: undefined,
- duration: undefined,
-}, {
- fromJS: ({ start_time, end_time, args, ...profile }) => ({
- ...profile,
- args: List(args),
- time: Math.round(start_time),
- duration: Math.round(end_time - start_time || 0),
- }),
-});
-
-
diff --git a/frontend/app/types/session/reduxAction.js b/frontend/app/types/session/reduxAction.js
deleted file mode 100644
index e03ab9ce9..000000000
--- a/frontend/app/types/session/reduxAction.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import Record from 'Types/Record';
-
-export default Record({
- time: undefined,
- index: undefined,
- action: {},
- state: undefined,
- diff: [],
- duration: undefined,
-});
-
-
diff --git a/frontend/app/types/session/resource.js b/frontend/app/types/session/resource.js
deleted file mode 100644
index 3b0523f78..000000000
--- a/frontend/app/types/session/resource.js
+++ /dev/null
@@ -1,118 +0,0 @@
-import { List } from 'immutable';
-import Record from 'Types/Record';
-import { getResourceName } from 'App/utils';
-
-const XHR = 'xhr';
-const FETCH = 'fetch';
-const JS = 'script';
-const CSS = 'css';
-const IMG = 'img';
-const MEDIA = 'media';
-const OTHER = 'other';
-
-
-//
-// const IMG_EXTENTIONS = [ "png", "gif", "jpg", "jpeg", "svg" ];
-// const MEDIA_EXTENTIONS = [ 'mp4', 'mkv', 'ogg', 'webm', 'avi', 'mp3' ];
-//
-// function getResourceType(type, initiator, url) {
-// if (type === 'xmlhttprequest') return XHR; // bad?
-// if (type !== undefined) return type;
-// if (initiator === 'xmlhttprequest' || initiator === 'fetch') return XHR;
-// if (initiator === 'img') return IMG;
-// const pathnameSplit = new URL(url).pathname.split('.');
-// if (pathnameSplit.length > 1) {
-// const extention = pathnameSplit.pop();
-// if (extention === 'css') return CSS;
-// if (extention === 'js') return JS;
-// if (IMG_EXTENTIONS.includes(extention)) return IMG
-// if (MEDIA_EXTENTIONS.includes(extention)) return MEDIA;
-// }
-// return OTHER;
-// }
-
-const TYPES_MAP = {
- "stylesheet": CSS,
-}
-
-function getResourceStatus(status, success) {
- if (status != null) return String(status);
- if (typeof success === 'boolean' || typeof success === 'number') {
- return !!success
- ? '2xx-3xx'
- : '4xx-5xx';
- }
- return '2xx-3xx';
-}
-
-function getResourceSuccess(success, status) {
- if (success != null) { return !!success }
- if (status != null) { return status < 400 }
- return true
-}
-
-export const TYPES = {
- XHR,
- FETCH,
- JS,
- CSS,
- IMG,
- MEDIA,
- OTHER,
-}
-
-const YELLOW_BOUND = 10;
-const RED_BOUND = 80;
-
-export function isRed(r) {
- return !r.success || r.score >= RED_BOUND;
-}
-export function isYellow(r) {
- return r.score < RED_BOUND && r.score >= YELLOW_BOUND;
-}
-
-export default Record({
- type: OTHER,
- url: '',
- name: '',
- status: '2xx-3xx',
- duration: 0,
- index: undefined,
- time: undefined,
- ttfb: 0,
- timewidth: 0,
- success: true,
- score: 0,
- // initiator: "other",
- // pagePath: "",
- method: '',
- request:'',
- response: '',
- headerSize: 0,
- encodedBodySize: 0,
- decodedBodySize: 0,
- responseBodySize: 0,
- timings: List(),
-}, {
- fromJS: ({ type, initiator, status, success, time, datetime, timestamp, timings, ...resource }) => ({
- ...resource,
- type: TYPES_MAP[type] || type,
- name: getResourceName(resource.url),
- status: getResourceStatus(status, success),
- success: getResourceSuccess(success, status),
- time: typeof time === 'number' ? time : datetime || timestamp,
- ttfb: timings && timings.ttfb,
- timewidth: timings && timings.timewidth,
- timings,
- }),
- name: 'Resource',
- methods: {
- isRed() {
- return isRed(this);
- },
- isYellow() {
- return isYellow(this);
- }
- }
-});
-
diff --git a/frontend/app/types/session/resource.ts b/frontend/app/types/session/resource.ts
new file mode 100644
index 000000000..170e0713d
--- /dev/null
+++ b/frontend/app/types/session/resource.ts
@@ -0,0 +1,117 @@
+import Record from 'Types/Record';
+import { getResourceName } from 'App/utils';
+
+const XHR = 'xhr' as const;
+const FETCH = 'fetch' as const;
+const JS = 'script' as const;
+const CSS = 'css' as const;
+const IMG = 'img' as const;
+const MEDIA = 'media' as const;
+const OTHER = 'other' as const;
+
+function getResourceStatus(status: number, success: boolean) {
+ if (status != null) return String(status);
+ if (typeof success === 'boolean' || typeof success === 'number') {
+ return !!success
+ ? '2xx-3xx'
+ : '4xx-5xx';
+ }
+ return '2xx-3xx';
+}
+
+function getResourceSuccess(success: boolean, status: number) {
+ if (success != null) {
+ return !!success
+ }
+ if (status != null) {
+ return status < 400
+ }
+ return true
+}
+
+export const TYPES = {
+ XHR,
+ FETCH,
+ JS,
+ CSS,
+ IMG,
+ MEDIA,
+ OTHER,
+ "stylesheet": CSS,
+}
+
+const YELLOW_BOUND = 10;
+const RED_BOUND = 80;
+
+export function isRed(r: IResource) {
+ return !r.success || r.score >= RED_BOUND;
+}
+
+interface IResource {
+ type: keyof typeof TYPES,
+ url: string,
+ name: string,
+ status: number,
+ duration: number,
+ index: number,
+ time: number,
+ ttfb: number,
+ timewidth: number,
+ success: boolean,
+ score: number,
+ method: string,
+ request: string,
+ response: string,
+ headerSize: number,
+ encodedBodySize: number,
+ decodedBodySize: number,
+ responseBodySize: number,
+ timings: Record
+ datetime: number
+ timestamp: number
+}
+
+export default class Resource {
+ name = 'Resource'
+ type: IResource["type"]
+ status: string
+ success: IResource["success"]
+ time: IResource["time"]
+ ttfb: IResource["ttfb"]
+ url: IResource["url"]
+ duration: IResource["duration"]
+ index: IResource["index"]
+ timewidth: IResource["timewidth"]
+ score: IResource["score"]
+ method: IResource["method"]
+ request: IResource["request"]
+ response: IResource["response"]
+ headerSize: IResource["headerSize"]
+ encodedBodySize: IResource["encodedBodySize"]
+ decodedBodySize: IResource["decodedBodySize"]
+ responseBodySize: IResource["responseBodySize"]
+ timings: IResource["timings"]
+
+ constructor({ status, success, time, datetime, timestamp, timings, ...resource }: IResource) {
+
+ Object.assign(this, {
+ ...resource,
+ name: getResourceName(resource.url),
+ status: getResourceStatus(status, success),
+ success: getResourceSuccess(success, status),
+ time: typeof time === 'number' ? time : datetime || timestamp,
+ ttfb: timings && timings.ttfb,
+ timewidth: timings && timings.timewidth,
+ timings,
+ })
+ }
+
+ isRed() {
+ return !this.success || this.score >= RED_BOUND;
+ }
+
+ isYellow() {
+ return this.score < RED_BOUND && this.score >= YELLOW_BOUND;
+ }
+}
+
diff --git a/frontend/app/types/session/session.ts b/frontend/app/types/session/session.ts
index 49d5f6962..d0bf42936 100644
--- a/frontend/app/types/session/session.ts
+++ b/frontend/app/types/session/session.ts
@@ -1,14 +1,14 @@
-import Record from 'Types/Record';
-import { List, Map } from 'immutable';
import { Duration } from 'luxon';
-import SessionEvent, { TYPES } from './event';
+import SessionEvent, { TYPES, EventData, InjectedEvent } from './event';
import StackEvent from './stackEvent';
import Resource from './resource';
-import SessionError from './error';
-import Issue from './issue';
+import SessionError, { IError } from './error';
+import Issue, { IIssue } from './issue';
+import { Note } from 'App/services/NotesService'
const HASH_MOD = 1610612741;
const HASH_P = 53;
+
function hashString(s: string): number {
let mul = 1;
let hash = 0;
@@ -19,164 +19,257 @@ function hashString(s: string): number {
return hash;
}
-export default Record(
- {
- sessionId: '',
- pageTitle: '',
- active: false,
- siteId: '',
- projectKey: '',
- peerId: '',
- live: false,
- startedAt: 0,
- duration: 0,
- events: List(),
- stackEvents: List(),
- resources: List(),
- missedResources: List(),
- metadata: Map(),
- favorite: false,
- filterId: '',
- messagesUrl: '',
- domURL: [],
- devtoolsURL: [],
- mobsUrl: [], // @depricated
- userBrowser: '',
- userBrowserVersion: '?',
- userCountry: '',
- userDevice: '',
- userDeviceType: '',
- isMobile: false,
- userOs: '',
- userOsVersion: '',
- userId: '',
- userAnonymousId: '',
- userUuid: undefined,
- userDisplayName: '',
- userNumericHash: 0,
- viewed: false,
- consoleLogCount: '?',
- eventsCount: '?',
- pagesCount: '?',
- clickRage: undefined,
- clickRageTime: undefined,
- resourcesScore: 0,
- consoleError: undefined,
- resourceError: undefined,
- returningLocation: undefined,
- returningLocationTime: undefined,
- errorsCount: 0,
- watchdogs: [],
- issueTypes: [],
- issues: [],
- userDeviceHeapSize: 0,
- userDeviceMemorySize: 0,
- errors: List(),
- crashes: [],
- socket: null,
- isIOS: false,
- revId: '',
- userSessionsCount: 0,
- agentIds: [],
- isCallActive: false,
- agentToken: '',
- notes: [],
- notesWithEvents: [],
- fileKey: '',
- },
- {
- fromJS: ({
+export interface ISession {
+ sessionId: string,
+ pageTitle: string,
+ active: boolean,
+ siteId: string,
+ projectKey: string,
+ peerId: string,
+ live: boolean,
+ startedAt: number,
+ duration: number,
+ events: InjectedEvent[],
+ stackEvents: StackEvent[],
+ resources: Resource[],
+ missedResources: Resource[],
+ metadata: [],
+ favorite: boolean,
+ filterId?: string,
+ domURL: string[],
+ devtoolsURL: string[],
+ /**
+ * @deprecated
+ */
+ mobsUrl: string[],
+ userBrowser: string,
+ userBrowserVersion: string,
+ userCountry: string,
+ userDevice: string,
+ userDeviceType: string,
+ isMobile: boolean,
+ userOs: string,
+ userOsVersion: string,
+ userId: string,
+ userAnonymousId: string,
+ userUuid: string,
+ userDisplayName: string,
+ userNumericHash: number,
+ viewed: boolean,
+ consoleLogCount: number,
+ eventsCount: number,
+ pagesCount: number,
+ errorsCount: number,
+ issueTypes: [],
+ issues: [],
+ referrer: string | null,
+ userDeviceHeapSize: number,
+ userDeviceMemorySize: number,
+ errors: SessionError[],
+ crashes?: [],
+ socket: string,
+ isIOS: boolean,
+ revId: string | null,
+ agentIds?: string[],
+ isCallActive?: boolean,
+ agentToken: string,
+ notes: Note[],
+ notesWithEvents: Array,
+ fileKey: string,
+ platform: string,
+ projectId: string,
+ startTs: number,
+ timestamp: number,
+ backendErrors: number,
+ consoleErrors: number,
+ sessionID?: string,
+ userID: string,
+ userUUID: string,
+ userEvents: any[],
+}
+
+const emptyValues = {
+ startTs: 0,
+ timestamp: 0,
+ backendErrors: 0,
+ consoleErrors: 0,
+ sessionID: '',
+ projectId: '',
+ errors: [],
+ stackEvents: [],
+ issues: [],
+ sessionId: '',
+ domURL: [],
+ devtoolsURL: [],
+ mobsUrl: [],
+ notes: [],
+ metadata: {},
+ startedAt: 0,
+}
+
+export default class Session {
+ sessionId: ISession["sessionId"]
+ pageTitle: ISession["pageTitle"]
+ active: ISession["active"]
+ siteId: ISession["siteId"]
+ projectKey: ISession["projectKey"]
+ peerId: ISession["peerId"]
+ live: ISession["live"]
+ startedAt: ISession["startedAt"]
+ duration: ISession["duration"]
+ events: ISession["events"]
+ stackEvents: ISession["stackEvents"]
+ resources: ISession["resources"]
+ metadata: ISession["metadata"]
+ favorite: ISession["favorite"]
+ filterId?: ISession["filterId"]
+ domURL: ISession["domURL"]
+ devtoolsURL: ISession["devtoolsURL"]
+ /**
+ * @deprecated
+ */
+ mobsUrl: ISession["mobsUrl"]
+ userBrowser: ISession["userBrowser"]
+ userBrowserVersion: ISession["userBrowserVersion"]
+ userCountry: ISession["userCountry"]
+ userDevice: ISession["userDevice"]
+ userDeviceType: ISession["userDeviceType"]
+ isMobile: ISession["isMobile"]
+ userOs: ISession["userOs"]
+ userOsVersion: ISession["userOsVersion"]
+ userId: ISession["userId"]
+ userAnonymousId: ISession["userAnonymousId"]
+ userUuid: ISession["userUuid"]
+ userDisplayName: ISession["userDisplayName"]
+ userNumericHash: ISession["userNumericHash"]
+ viewed: ISession["viewed"]
+ consoleLogCount: ISession["consoleLogCount"]
+ eventsCount: ISession["eventsCount"]
+ pagesCount: ISession["pagesCount"]
+ errorsCount: ISession["errorsCount"]
+ issueTypes: ISession["issueTypes"]
+ issues: ISession["issues"]
+ referrer: ISession["referrer"]
+ userDeviceHeapSize: ISession["userDeviceHeapSize"]
+ userDeviceMemorySize: ISession["userDeviceMemorySize"]
+ errors: ISession["errors"]
+ crashes?: ISession["crashes"]
+ socket: ISession["socket"]
+ isIOS: ISession["isIOS"]
+ revId: ISession["revId"]
+ agentIds?: ISession["agentIds"]
+ isCallActive?: ISession["isCallActive"]
+ agentToken: ISession["agentToken"]
+ notes: ISession["notes"]
+ notesWithEvents: ISession["notesWithEvents"]
+ fileKey: ISession["fileKey"]
+
+ constructor(plainSession?: ISession) {
+ const sessionData = plainSession || (emptyValues as unknown as ISession)
+ const {
startTs = 0,
timestamp = 0,
backendErrors = 0,
consoleErrors = 0,
- projectId,
- errors,
+ sessionID = '',
+ projectId = '',
+ errors = [],
stackEvents = [],
issues = [],
- sessionId,
- sessionID,
+ sessionId = '',
domURL = [],
devtoolsURL = [],
mobsUrl = [],
notes = [],
+ resources = [],
...session
- }) => {
- const duration = Duration.fromMillis(session.duration < 1000 ? 1000 : session.duration);
- const durationSeconds = duration.valueOf();
- const startedAt = +startTs || +timestamp;
+ } = sessionData
+ const duration = Duration.fromMillis(session.duration < 1000 ? 1000 : session.duration);
+ const durationSeconds = duration.valueOf();
+ const startedAt = +startTs || +timestamp;
- const userDevice = session.userDevice || session.userDeviceType || 'Other';
- const userDeviceType = session.userDeviceType || 'other';
- const isMobile = ['console', 'mobile', 'tablet'].includes(userDeviceType);
+ const userDevice = session.userDevice || session.userDeviceType || 'Other';
+ const userDeviceType = session.userDeviceType || 'other';
+ const isMobile = ['console', 'mobile', 'tablet'].includes(userDeviceType);
- const events = List(session.events)
- .map((e) => SessionEvent({ ...e, time: e.timestamp - startedAt }))
- .filter(({ type, time }) => type !== TYPES.CONSOLE && time <= durationSeconds);
+ const events: InjectedEvent[] = []
+ const rawEvents: (EventData & { key: number })[] = []
- let resources = List(session.resources).map(Resource);
- resources = resources
- .map((r) => r.set('time', Math.max(0, r.time - startedAt)))
- .sort((r1, r2) => r1.time - r2.time);
- const missedResources = resources.filter(({ success }) => !success);
+ if (session.events?.length) {
+ (session.events as EventData[]).forEach((event: EventData, k) => {
+ const time = event.timestamp - startedAt
+ if (event.type !== TYPES.CONSOLE && time <= durationSeconds) {
+ const EventClass = SessionEvent({ ...event, time, key: k })
+ if (EventClass) {
+ events.push(EventClass);
+ }
+ rawEvents.push({ ...event, time, key: k });
+ }
+ })
+ }
- const stackEventsList = List(stackEvents)
- .concat(List(session.userEvents))
- .sortBy((se) => se.timestamp)
- .map((se) => StackEvent({ ...se, time: se.timestamp - startedAt }));
- const exceptions = List(errors).map(SessionError);
+ let resourcesList = resources.map((r) => new Resource(r as any));
+ resourcesList.forEach((r: Resource) => {
+ r.time = Math.max(0, r.time - startedAt)
+ })
+ resourcesList = resourcesList.sort((r1, r2) => r1.time - r2.time);
+ const missedResources = resourcesList.filter(({ success }) => !success);
- const issuesList = List(issues).map((e) => Issue({ ...e, time: e.timestamp - startedAt }));
+ const stackEventsList: StackEvent[] = []
+ if (stackEvents?.length || session.userEvents?.length) {
+ const mergedArrays = [...stackEvents, ...session.userEvents]
+ .sort((a, b) => a.timestamp - b.timestamp)
+ .map((se) => new StackEvent({ ...se, time: se.timestamp - startedAt }))
+ stackEventsList.push(...mergedArrays);
+ }
- const rawEvents = !session.events
- ? []
- : // @ts-ignore
- session.events
- .map((evt) => ({ ...evt, time: evt.timestamp - startedAt }))
- .filter(({ type, time }) => type !== TYPES.CONSOLE && time <= durationSeconds) || [];
- const rawNotes = notes;
- const notesWithEvents = [...rawEvents, ...rawNotes].sort((a, b) => {
- const aTs = a.time || a.timestamp;
- const bTs = b.time || b.timestamp;
+ const exceptions = (errors as IError[]).map(e => new SessionError(e)) || [];
- return aTs - bTs;
- });
+ const issuesList = (issues as IIssue[]).map(
+ (i, k) => new Issue({ ...i, time: i.timestamp - startedAt, key: k })) || [];
- return {
- ...session,
- isIOS: session.platform === 'ios',
- watchdogs: session.watchdogs || [],
- errors: exceptions,
- siteId: projectId,
- events,
- stackEvents: stackEventsList,
- resources,
- missedResources,
- userDevice,
- userDeviceType,
- isMobile,
- startedAt,
- duration,
- userNumericHash: hashString(
- session.userId ||
- session.userAnonymousId ||
- session.userUuid ||
- session.userID ||
- session.userUUID ||
- ''
- ),
- userDisplayName:
- session.userId || session.userAnonymousId || session.userID || 'Anonymous User',
- issues: issuesList,
- sessionId: sessionId || sessionID,
- userId: session.userId || session.userID,
- mobsUrl: Array.isArray(mobsUrl) ? mobsUrl : [mobsUrl],
- domURL,
- devtoolsURL,
- notes,
- notesWithEvents: List(notesWithEvents),
- };
- },
- idKey: 'sessionId',
+ const rawNotes = notes;
+ const notesWithEvents = [...rawEvents, ...rawNotes].sort((a, b) => {
+ // @ts-ignore just in case
+ const aTs = a.timestamp || a.time;
+ // @ts-ignore
+ const bTs = b.timestamp || b.time;
+
+ return aTs - bTs;
+ }) || [];
+
+ Object.assign(this, {
+ ...session,
+ isIOS: session.platform === 'ios',
+ errors: exceptions,
+ siteId: projectId,
+ events,
+ stackEvents: stackEventsList,
+ resources: resourcesList,
+ missedResources,
+ userDevice,
+ userDeviceType,
+ isMobile,
+ startedAt,
+ duration,
+ userNumericHash: hashString(
+ session.userId ||
+ session.userAnonymousId ||
+ session.userUuid ||
+ session.userID ||
+ session.userUUID ||
+ ''
+ ),
+ userDisplayName:
+ session.userId || session.userAnonymousId || session.userID || 'Anonymous User',
+ issues: issuesList,
+ sessionId: sessionId || sessionID,
+ userId: session.userId || session.userID,
+ mobsUrl: Array.isArray(mobsUrl) ? mobsUrl : [mobsUrl],
+ domURL,
+ devtoolsURL,
+ notes,
+ notesWithEvents: notesWithEvents,
+ })
}
-);
+}
\ No newline at end of file
diff --git a/frontend/app/types/session/stackEvent.js b/frontend/app/types/session/stackEvent.js
deleted file mode 100644
index 762407916..000000000
--- a/frontend/app/types/session/stackEvent.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import Record from 'Types/Record';
-
-export const OPENREPLAY = 'openreplay';
-export const SENTRY = 'sentry';
-export const DATADOG = 'datadog';
-export const STACKDRIVER = 'stackdriver';
-export const ROLLBAR = 'rollbar';
-export const NEWRELIC = 'newrelic';
-export const BUGSNAG = 'bugsnag';
-export const CLOUDWATCH = 'cloudwatch';
-export const ELASTICSEARCH = 'elasticsearch';
-export const SUMOLOGIC = 'sumologic';
-
-export const typeList = [ OPENREPLAY, SENTRY, DATADOG, STACKDRIVER, ROLLBAR, BUGSNAG, CLOUDWATCH, ELASTICSEARCH, SUMOLOGIC ];
-
-export function isRed(event) {
- if (!event.payload) return false;
- switch(event.source) {
- case SENTRY:
- return event.payload['event.type'] === 'error';
- case DATADOG:
- return true;
- case STACKDRIVER:
- return false;
- case ROLLBAR:
- return true;
- case NEWRELIC:
- return true;
- case BUGSNAG:
- return true;
- case CLOUDWATCH:
- return true;
- case SUMOLOGIC:
- return false;
- default:
- return event.level==='error';
- }
-}
-
-export default Record({
- time: undefined,
- index: undefined,
- name: '',
- message: "",
- payload: null,
- source: null,
- level: "",
-}, {
- fromJS: ue => ({
- ...ue,
- source: ue.source || OPENREPLAY,
- }),
- methods: {
- isRed() {
- return isRed(this);
- }
- }
-});
-
diff --git a/frontend/app/types/session/stackEvent.ts b/frontend/app/types/session/stackEvent.ts
new file mode 100644
index 000000000..06910e7d4
--- /dev/null
+++ b/frontend/app/types/session/stackEvent.ts
@@ -0,0 +1,70 @@
+export const OPENREPLAY = 'openreplay';
+export const SENTRY = 'sentry';
+export const DATADOG = 'datadog';
+export const STACKDRIVER = 'stackdriver';
+export const ROLLBAR = 'rollbar';
+export const NEWRELIC = 'newrelic';
+export const BUGSNAG = 'bugsnag';
+export const CLOUDWATCH = 'cloudwatch';
+export const ELASTICSEARCH = 'elasticsearch';
+export const SUMOLOGIC = 'sumologic';
+
+export const typeList = [OPENREPLAY, SENTRY, DATADOG, STACKDRIVER, ROLLBAR, BUGSNAG, CLOUDWATCH, ELASTICSEARCH, SUMOLOGIC];
+
+export function isRed(event: StackEvent) {
+ if (!event.payload) return false;
+ switch (event.source) {
+ case SENTRY:
+ return event.payload['event.type'] === 'error';
+ case DATADOG:
+ return true;
+ case STACKDRIVER:
+ return false;
+ case ROLLBAR:
+ return true;
+ case NEWRELIC:
+ return true;
+ case BUGSNAG:
+ return true;
+ case CLOUDWATCH:
+ return true;
+ case SUMOLOGIC:
+ return false;
+ default:
+ return event.level === 'error';
+ }
+}
+
+export interface IStackEvent {
+ time: number;
+ timestamp: number;
+ index: number;
+ name: string;
+ message: string;
+ payload: any;
+ source: any;
+ level: string;
+
+ isRed: () => boolean;
+}
+
+export default class StackEvent {
+ time: IStackEvent["time"]
+ index: IStackEvent["index"];
+ name: IStackEvent["name"];
+ message: IStackEvent["message"];
+ payload: IStackEvent["payload"];
+ source: IStackEvent["source"];
+ level: IStackEvent["level"];
+
+ constructor(evt: IStackEvent) {
+ Object.assign(this, {
+ ...evt,
+ source: evt.source || OPENREPLAY
+ });
+ }
+
+ isRed() {
+ return isRed(this);
+ }
+}
diff --git a/frontend/app/types/ts/search.ts b/frontend/app/types/ts/search.ts
index 1c45bbe56..32660818b 100644
--- a/frontend/app/types/ts/search.ts
+++ b/frontend/app/types/ts/search.ts
@@ -10,5 +10,4 @@ export interface SavedSearch {
projectId: number;
searchId: number;
userId: number;
- watchdogs: List
}