From 6e17e59b57b1c82c1fefb56fcbd72c9a4579a3ad Mon Sep 17 00:00:00 2001 From: sylenien Date: Thu, 29 Dec 2022 16:01:21 +0100 Subject: [PATCH] change(ui): rewrite issue and resource types --- .../app/components/Session/playerContext.ts | 11 +- .../Session_/EventsBlock/EventsBlock.tsx | 3 - .../Player/Controls/Controls.DEPRECATED.js | 435 ------------------ .../Session_/Player/Controls/Timeline.tsx | 44 +- .../Player/Controls/timeline.module.css | 16 + .../app/components/Session_/Player/Player.js | 2 - .../Session_/TimeTable/TimeTable.tsx | 19 +- .../shared/DevTools/TimeTable/TimeTable.tsx | 5 +- frontend/app/duck/funcTools/tools.js | 5 - frontend/app/duck/index.ts | 2 - frontend/app/duck/targetCustom.js | 13 - frontend/app/mstore/types/session.ts | 4 +- frontend/app/types/session/issue.js | 46 -- frontend/app/types/session/issue.ts | 85 ++++ frontend/app/types/session/resource.js | 118 ----- frontend/app/types/session/resource.ts | 113 +++++ frontend/app/types/session/session.ts | 17 +- 17 files changed, 275 insertions(+), 663 deletions(-) delete mode 100644 frontend/app/components/Session_/Player/Controls/Controls.DEPRECATED.js delete mode 100644 frontend/app/duck/targetCustom.js delete mode 100644 frontend/app/types/session/issue.js create mode 100644 frontend/app/types/session/issue.ts delete mode 100644 frontend/app/types/session/resource.js create mode 100644 frontend/app/types/session/resource.ts diff --git a/frontend/app/components/Session/playerContext.ts b/frontend/app/components/Session/playerContext.ts index 1c3ae9ab5..fed19e0a4 100644 --- a/frontend/app/components/Session/playerContext.ts +++ b/frontend/app/components/Session/playerContext.ts @@ -1,12 +1,15 @@ import { createContext } from 'react'; import { IWebPlayer, - IWebPlayerStore + IWebPlayerStore, + IWebLivePlayer, + IWebLivePlayerStore, } from 'Player' export interface IPlayerContext { - player: IWebPlayer - store: IWebPlayerStore, + player: IWebPlayer | IWebLivePlayer + store: IWebPlayerStore | IWebLivePlayerStore, } -export const defaultContextValue: IPlayerContext = { player: undefined, store: undefined} +export const defaultContextValue = { player: undefined, store: undefined} +// @ts-ignore export const PlayerContext = createContext(defaultContextValue); diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx index 8df9be6ff..5e9494a11 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx @@ -5,7 +5,6 @@ import { Icon } from 'UI'; import { List, AutoSizer, CellMeasurer } from "react-virtualized"; import { TYPES } from 'Types/session/event'; import { setEventFilter, filterOutNote } from 'Duck/sessions'; -import { show as showTargetDefiner } from 'Duck/components/targetDefiner'; import EventGroupWrapper from './EventGroupWrapper'; import styles from './eventsBlock.module.css'; import EventSearch from './EventSearch/EventSearch'; @@ -179,9 +178,7 @@ export default connect((state: RootStore) => ({ filteredEvents: state.getIn([ 'sessions', 'filteredEvents' ]), query: state.getIn(['sessions', 'eventsQuery']), eventsIndex: state.getIn([ 'sessions', 'eventsIndex' ]), - targetDefinerDisplayed: state.getIn([ 'components', 'targetDefiner', 'isDisplayed' ]), }), { - showTargetDefiner, setEventFilter, filterOutNote })(observer(EventsBlock)) diff --git a/frontend/app/components/Session_/Player/Controls/Controls.DEPRECATED.js b/frontend/app/components/Session_/Player/Controls/Controls.DEPRECATED.js deleted file mode 100644 index 005403731..000000000 --- a/frontend/app/components/Session_/Player/Controls/Controls.DEPRECATED.js +++ /dev/null @@ -1,435 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { connect } from 'react-redux'; -import { - connectPlayer, - STORAGE_TYPES, - selectStorageType, - selectStorageListNow, -} from 'Player'; -import LiveTag from 'Shared/LiveTag'; -import { jumpToLive } from 'Player'; - -import { Icon, Tooltip } from 'UI'; -import { toggleInspectorMode } from 'Player'; -import { - fullscreenOn, - fullscreenOff, - toggleBottomBlock, - changeSkipInterval, - OVERVIEW, - CONSOLE, - NETWORK, - STACKEVENTS, - STORAGE, - PROFILER, - PERFORMANCE, - GRAPHQL, - INSPECTOR, -} from 'Duck/components/player'; -import { AssistDuration } from './Time'; -import Timeline from './Timeline'; -import ControlButton from './ControlButton'; -import PlayerControls from './components/PlayerControls'; - -import styles from './controls.module.css'; -import XRayButton from 'Shared/XRayButton'; - -const SKIP_INTERVALS = { - 2: 2e3, - 5: 5e3, - 10: 1e4, - 15: 15e3, - 20: 2e4, - 30: 3e4, - 60: 6e4, -}; - -function getStorageName(type) { - switch (type) { - case STORAGE_TYPES.REDUX: - return 'REDUX'; - case STORAGE_TYPES.MOBX: - return 'MOBX'; - case STORAGE_TYPES.VUEX: - return 'VUEX'; - case STORAGE_TYPES.NGRX: - return 'NGRX'; - case STORAGE_TYPES.ZUSTAND: - return 'ZUSTAND'; - case STORAGE_TYPES.NONE: - return 'STATE'; - } -} - -@connectPlayer((state) => ({ - time: state.time, - endTime: state.endTime, - live: state.live, - livePlay: state.livePlay, - playing: state.playing, - completed: state.completed, - skip: state.skip, - skipToIssue: state.skipToIssue, - speed: state.speed, - disabled: state.cssLoading || state.messagesLoading || state.inspectorMode || state.markedTargets, - inspectorMode: state.inspectorMode, - fullscreenDisabled: state.messagesLoading, - // logCount: state.logList.length, - logRedCount: state.logMarkedCount, - showExceptions: state.exceptionsList.length > 0, - resourceRedCount: state.resourceMarkedCount, - fetchRedCount: state.fetchMarkedCount, - showStack: state.stackList.length > 0, - stackCount: state.stackList.length, - stackRedCount: state.stackMarkedCount, - profilesCount: state.profilesList.length, - storageCount: selectStorageListNow(state).length, - storageType: selectStorageType(state), - showStorage: selectStorageType(state) !== STORAGE_TYPES.NONE, - showProfiler: state.profilesList.length > 0, - showGraphql: state.graphqlList.length > 0, - showFetch: state.fetchCount > 0, - fetchCount: state.fetchCount, - graphqlCount: state.graphqlList.length, - liveTimeTravel: state.liveTimeTravel, -})) -@connect( - (state, props) => { - const permissions = state.getIn(['user', 'account', 'permissions']) || []; - const isEnterprise = state.getIn(['user', 'account', 'edition']) === 'ee'; - return { - disabled: props.disabled || (isEnterprise && !permissions.includes('DEV_TOOLS')), - fullscreen: state.getIn(['components', 'player', 'fullscreen']), - bottomBlock: state.getIn(['components', 'player', 'bottomBlock']), - showStorage: - props.showStorage || !state.getIn(['components', 'player', 'hiddenHints', 'storage']), - showStack: props.showStack || !state.getIn(['components', 'player', 'hiddenHints', 'stack']), - closedLive: - !!state.getIn(['sessions', 'errors']) || !state.getIn(['sessions', 'current', 'live']), - skipInterval: state.getIn(['components', 'player', 'skipInterval']), - }; - }, - { - fullscreenOn, - fullscreenOff, - toggleBottomBlock, - changeSkipInterval, - } -) -export default class Controls extends React.Component { - componentDidMount() { - document.addEventListener('keydown', this.onKeyDown); - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.onKeyDown); - //this.props.toggleInspectorMode(false); - } - - shouldComponentUpdate(nextProps) { - if ( - nextProps.fullscreen !== this.props.fullscreen || - nextProps.bottomBlock !== this.props.bottomBlock || - nextProps.live !== this.props.live || - nextProps.livePlay !== this.props.livePlay || - nextProps.playing !== this.props.playing || - nextProps.completed !== this.props.completed || - nextProps.skip !== this.props.skip || - nextProps.skipToIssue !== this.props.skipToIssue || - nextProps.speed !== this.props.speed || - nextProps.disabled !== this.props.disabled || - nextProps.fullscreenDisabled !== this.props.fullscreenDisabled || - // nextProps.inspectorMode !== this.props.inspectorMode || - // nextProps.logCount !== this.props.logCount || - nextProps.logRedCount !== this.props.logRedCount || - nextProps.showExceptions !== this.props.showExceptions || - nextProps.resourceRedCount !== this.props.resourceRedCount || - nextProps.fetchRedCount !== this.props.fetchRedCount || - nextProps.showStack !== this.props.showStack || - nextProps.stackCount !== this.props.stackCount || - nextProps.stackRedCount !== this.props.stackRedCount || - nextProps.profilesCount !== this.props.profilesCount || - nextProps.storageCount !== this.props.storageCount || - nextProps.storageType !== this.props.storageType || - nextProps.showStorage !== this.props.showStorage || - nextProps.showProfiler !== this.props.showProfiler || - nextProps.showGraphql !== this.props.showGraphql || - nextProps.showFetch !== this.props.showFetch || - nextProps.fetchCount !== this.props.fetchCount || - nextProps.graphqlCount !== this.props.graphqlCount || - nextProps.liveTimeTravel !== this.props.liveTimeTravel || - nextProps.skipInterval !== this.props.skipInterval - ) - return true; - return false; - } - - onKeyDown = (e) => { - if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { - return; - } - if (this.props.inspectorMode) { - if (e.key === 'Esc' || e.key === 'Escape') { - toggleInspectorMode(false); - } - } - // if (e.key === ' ') { - // document.activeElement.blur(); - // this.props.togglePlay(); - // } - if (e.key === 'Esc' || e.key === 'Escape') { - this.props.fullscreenOff(); - } - if (e.key === 'ArrowRight') { - this.forthTenSeconds(); - } - if (e.key === 'ArrowLeft') { - this.backTenSeconds(); - } - if (e.key === 'ArrowDown') { - this.props.speedDown(); - } - if (e.key === 'ArrowUp') { - this.props.speedUp(); - } - }; - - forthTenSeconds = () => { - const { time, endTime, jump, skipInterval } = this.props; - jump(Math.min(endTime, time + SKIP_INTERVALS[skipInterval])); - }; - - backTenSeconds = () => { - //shouldComponentUpdate - const { time, jump, skipInterval } = this.props; - jump(Math.max(1, time - SKIP_INTERVALS[skipInterval])); - }; - - goLive = () => this.props.jump(this.props.endTime); - - renderPlayBtn = () => { - const { completed, playing } = this.props; - let label; - let icon; - if (completed) { - icon = 'arrow-clockwise'; - label = 'Replay this session'; - } else if (playing) { - icon = 'pause-fill'; - label = 'Pause'; - } else { - icon = 'play-fill-new'; - label = 'Pause'; - label = 'Play'; - } - - return ( - -
- -
-
- ); - }; - - controlIcon = (icon, size, action, isBackwards, additionalClasses) => ( -
- -
- ); - - render() { - const { - bottomBlock, - toggleBottomBlock, - live, - livePlay, - skip, - speed, - disabled, - logRedCount, - showExceptions, - resourceRedCount, - fetchRedCount, - showStack, - stackRedCount, - showStorage, - storageType, - showProfiler, - showGraphql, - fullscreen, - inspectorMode, - closedLive, - toggleSpeed, - toggleSkip, - liveTimeTravel, - changeSkipInterval, - skipInterval, - } = this.props; - - const toggleBottomTools = (blockName) => { - if (blockName === INSPECTOR) { - toggleInspectorMode(); - bottomBlock && toggleBottomBlock(); - } else { - toggleInspectorMode(false); - toggleBottomBlock(blockName); - } - }; - - return ( -
- - {!fullscreen && ( -
-
- {!live && ( - <> - -
- toggleBottomTools(OVERVIEW)} - /> - - )} - - {live && !closedLive && ( -
- (livePlay ? null : jumpToLive())} /> -
- -
-
- )} -
- -
- toggleBottomTools(CONSOLE)} - active={bottomBlock === CONSOLE && !inspectorMode} - label="CONSOLE" - noIcon - labelClassName="!text-base font-semibold" - hasErrors={logRedCount > 0 || showExceptions} - containerClassName="mx-2" - /> - {!live && ( - toggleBottomTools(NETWORK)} - active={bottomBlock === NETWORK && !inspectorMode} - label="NETWORK" - hasErrors={resourceRedCount > 0 || fetchRedCount > 0} - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && ( - toggleBottomTools(PERFORMANCE)} - active={bottomBlock === PERFORMANCE && !inspectorMode} - label="PERFORMANCE" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && showGraphql && ( - toggleBottomTools(GRAPHQL)} - active={bottomBlock === GRAPHQL && !inspectorMode} - label="GRAPHQL" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && showStorage && ( - toggleBottomTools(STORAGE)} - active={bottomBlock === STORAGE && !inspectorMode} - label={getStorageName(storageType)} - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && ( - toggleBottomTools(STACKEVENTS)} - active={bottomBlock === STACKEVENTS && !inspectorMode} - label="EVENTS" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - hasErrors={stackRedCount > 0} - /> - )} - {!live && showProfiler && ( - toggleBottomTools(PROFILER)} - active={bottomBlock === PROFILER && !inspectorMode} - label="PROFILER" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && ( - - {this.controlIcon( - 'arrows-angle-extend', - 16, - this.props.fullscreenOn, - false, - 'rounded hover:bg-gray-light-shade color-gray-medium' - )} - - )} -
-
- )} -
- ); - } -} diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.tsx b/frontend/app/components/Session_/Player/Controls/Timeline.tsx index 89ce80e65..7bd8b7464 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.tsx +++ b/frontend/app/components/Session_/Player/Controls/Timeline.tsx @@ -12,6 +12,7 @@ import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; import { useStore } from 'App/mstore'; import { DateTime, Duration } from 'luxon'; +import Issue from "Types/session/issue"; function getTimelinePosition(value: number, scale: number) { const pos = value * scale; @@ -19,7 +20,14 @@ function getTimelinePosition(value: number, scale: number) { return pos > 100 ? 99 : pos; } -function Timeline(props) { +interface IProps { + issues: Issue[] + setTimelineHoverTime: (t: number) => void + startedAt: number + tooltipVisible: boolean +} + +function Timeline(props: IProps) { const { player, store } = useContext(PlayerContext) const [wasPlaying, setWasPlaying] = useState(false) const { notesStore, settingsStore } = useStore(); @@ -35,17 +43,17 @@ function Timeline(props) { live, liveTimeTravel, } = store.get() + const { issues } = props; const notes = notesStore.sessionNotes - const progressRef = useRef() - const timelineRef = useRef() + const progressRef = useRef(null) + const timelineRef = useRef(null) const scale = 100 / endTime; useEffect(() => { - const { issues } = props; - const firstIssue = issues.get(0); + const firstIssue = issues[0]; if (firstIssue && skipToIssue) { player.jump(firstIssue.time); @@ -64,7 +72,7 @@ function Timeline(props) { }; const onDrag: OnDragCallback = (offset) => { - if (live && !liveTimeTravel) return; + if ((live && !liveTimeTravel) || !progressRef.current) return; const p = (offset.x) / progressRef.current.offsetWidth; const time = Math.max(Math.round(p * endTime), 0); @@ -76,7 +84,7 @@ function Timeline(props) { } }; - const getLiveTime = (e) => { + const getLiveTime = (e: React.MouseEvent) => { const duration = new Date().getTime() - props.startedAt; const p = e.nativeEvent.offsetX / e.target.offsetWidth; const time = Math.max(Math.round(p * duration), 0); @@ -84,7 +92,7 @@ function Timeline(props) { return [time, duration]; }; - const showTimeTooltip = (e) => { + const showTimeTooltip = (e: React.MouseEvent) => { if (e.target !== progressRef.current && e.target !== timelineRef.current) { return props.tooltipVisible && hideTimeTooltip(); } @@ -118,13 +126,13 @@ function Timeline(props) { debouncedTooltipChange(timeLineTooltip); }; - const seekProgress = (e) => { + const seekProgress = (e: React.MouseEvent) => { const time = getTime(e); player.jump(time); hideTimeTooltip(); }; - const loadAndSeek = async (e) => { + const loadAndSeek = async (e: React.MouseEvent) => { e.persist(); await player.toggleTimetravel(); @@ -133,7 +141,7 @@ function Timeline(props) { }); }; - const jumpToTime: React.MouseEventHandler = (e) => { + const jumpToTime = (e: React.MouseEvent) => { if (live && !liveTimeTravel) { loadAndSeek(e); } else { @@ -141,7 +149,7 @@ function Timeline(props) { } }; - const getTime = (e: React.MouseEvent, customEndTime?: number) => { + const getTime = (e: React.MouseEvent, customEndTime?: number) => { const p = e.nativeEvent.offsetX / e.target.offsetWidth; const targetTime = customEndTime || endTime; const time = Math.max(Math.round(p * targetTime), 0); @@ -161,7 +169,7 @@ function Timeline(props) { >
(
))} + {issues.map((i: Issue) => ( +
+ ))} {notes.map((note) => note.timestamp > 0 ? (
({ + (state: any) => ({ issues: state.getIn(['sessions', 'current', 'issues']), startedAt: state.getIn(['sessions', 'current', 'startedAt']), tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']), 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..da6fb0eac 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, @@ -116,7 +115,6 @@ export default connect((state) => { }; }, { - hideTargetDefiner, fullscreenOff, updateLastPlayedSession, } 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/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 }) => (
- 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/index.ts b/frontend/app/duck/index.ts index 69a1ae994..4e8e24d8f 100644 --- a/frontend/app/duck/index.ts +++ b/frontend/app/duck/index.ts @@ -4,7 +4,6 @@ import { combineReducers } from 'redux-immutable'; import user from './user'; import sessions from './sessions'; import assignments from './assignments'; -import targetCustom from './targetCustom'; import filters from './filters'; import funnelFilters from './funnelFilters'; import templates from './templates'; @@ -29,7 +28,6 @@ const rootReducer = combineReducers({ user, sessions, assignments, - targetCustom, filters, funnelFilters, 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/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/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/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..7256a00c0 --- /dev/null +++ b/frontend/app/types/session/resource.ts @@ -0,0 +1,113 @@ +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 2b362ceff..0f454029e 100644 --- a/frontend/app/types/session/session.ts +++ b/frontend/app/types/session/session.ts @@ -5,7 +5,7 @@ import SessionEvent, { TYPES } from './event'; import StackEvent from './stackEvent'; import Resource from './resource'; import SessionError from './error'; -import Issue from './issue'; +import Issue, { IIssue } from './issue'; const HASH_MOD = 1610612741; const HASH_P = 53; @@ -32,8 +32,8 @@ export default Record( duration: 0, events: List(), stackEvents: List(), - resources: List(), - missedResources: List(), + resources: [], + missedResources: [], metadata: Map(), favorite: false, filterId: '', @@ -113,10 +113,11 @@ export default Record( .map((e) => SessionEvent({ ...e, time: e.timestamp - startedAt })) .filter(({ type, time }) => type !== TYPES.CONSOLE && time <= durationSeconds); - 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); + let resources = List(session.resources).map((r) => new Resource(r as any)); + resources.forEach((r: Resource) => { + r.time = Math.max(0, r.time - startedAt) + }) + resources = resources.sort((r1, r2) => r1.time - r2.time); const missedResources = resources.filter(({ success }) => !success); const stackEventsList = List(stackEvents) @@ -125,7 +126,7 @@ export default Record( .map((se) => StackEvent({ ...se, time: se.timestamp - startedAt })); const exceptions = List(errors).map(SessionError); - const issuesList = List(issues).map((e) => Issue({ ...e, time: e.timestamp - startedAt })); + const issuesList = (issues as IIssue[]).map((i, k) => new Issue({ ...i, time: i.timestamp - startedAt, key: k })); const rawEvents = !session.events ? []