diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Event.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Event.js deleted file mode 100644 index e8f985aa0..000000000 --- a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Event.js +++ /dev/null @@ -1,174 +0,0 @@ -import React from 'react'; -import copy from 'copy-to-clipboard'; -import cn from 'classnames'; -import { Icon, TextEllipsis } from 'UI'; -import { TYPES } from 'Types/session/event'; -import { prorata } from 'App/utils'; -import withOverlay from 'Components/hocs/withOverlay'; -import LoadInfo from './LoadInfo'; -import cls from './event.module.css'; -import { numberWithCommas } from 'App/utils'; - -@withOverlay() -export default class Event extends React.PureComponent { - state = { - menuOpen: false, - } - - componentDidMount() { - this.wrapper.addEventListener('contextmenu', this.onContextMenu); - } - - onContextMenu = (e) => { - e.preventDefault(); - this.setState({ menuOpen: true }); - } - onMouseLeave = () => this.setState({ menuOpen: false }) - - copyHandler = (e) => { - e.stopPropagation(); - //const ctrlOrCommandPressed = e.ctrlKey || e.metaKey; - //if (ctrlOrCommandPressed && e.keyCode === 67) { - const { event } = this.props; - copy(event.getIn([ 'target', 'path' ]) || event.url || ''); - this.setState({ menuOpen: false }); - } - - toggleInfo = (e) => { - e.stopPropagation(); - this.props.toggleInfo(); - } - - // eslint-disable-next-line complexity - renderBody = () => { - const { event } = this.props; - let title = event.type; - let body; - switch (event.type) { - case TYPES.LOCATION: - title = 'Visited'; - body = event.url; - break; - case TYPES.CLICK: - title = 'Clicked'; - body = event.label; - break; - case TYPES.INPUT: - title = 'Input'; - body = event.value; - break; - case TYPES.CLICKRAGE: - title = `${ event.count } Clicks`; - body = event.label; - break; - case TYPES.IOS_VIEW: - title = 'View'; - body = event.name; - break; - } - const isLocation = event.type === TYPES.LOCATION; - const isClickrage = event.type === TYPES.CLICKRAGE; - - return ( -
-
- { event.type && } -
-
-
- { title } - {/* { body && !isLocation &&
{ body }
} */} - { body && !isLocation && - - } -
- { isLocation && event.speedIndex != null && -
-
{"Speed Index"}
-
{ numberWithCommas(event.speedIndex || 0) }
-
- } -
- { event.target && event.target.label && -
{ event.target.label }
- } -
-
- { isLocation && -
- { body } -
- } -
- ); - }; - - render() { - const { - event, - selected, - isCurrent, - onClick, - showSelection, - onCheckboxClick, - showLoadInfo, - toggleLoadInfo, - isRed, - extended, - highlight = false, - presentInSearch = false, - isLastInGroup, - whiteBg, - } = this.props; - const { menuOpen } = this.state; - return ( -
{ this.wrapper = ref } } - onMouseLeave={ this.onMouseLeave } - data-openreplay-label="Event" - data-type={event.type} - className={ cn(cls.event, { - [ cls.menuClosed ]: !menuOpen, - [ cls.highlighted ]: showSelection ? selected : isCurrent, - [ cls.selected ]: selected, - [ cls.showSelection ]: showSelection, - [ cls.red ]: isRed, - [ cls.clickType ]: event.type === TYPES.CLICK, - [ cls.inputType ]: event.type === TYPES.INPUT, - [ cls.clickrageType ]: event.type === TYPES.CLICKRAGE, - [ cls.highlight ] : presentInSearch, - [ cls.lastInGroup ]: whiteBg, - }) } - onClick={ onClick } - > - { menuOpen && - - } -
-
- { this.renderBody() } -
- {/* { event.type === TYPES.LOCATION && -
{event.url}
- } */} -
- { event.type === TYPES.LOCATION && (event.fcpTime || event.visuallyComplete || event.timeToInteractive) && - elements / 1.2, - // eslint-disable-next-line no-mixed-operators - divisorFn: (elements, parts) => elements / (2 * parts + 1), - }) } - /> - } -
- ); - } -} diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventGroupWrapper.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventGroupWrapper.js deleted file mode 100644 index 924be9f2c..000000000 --- a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventGroupWrapper.js +++ /dev/null @@ -1,130 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { connect } from 'react-redux' -import { TextEllipsis } from 'UI'; -import withToggle from 'HOCs/withToggle'; -import { TYPES } from 'Types/session/event'; -import Event from './Event' -import stl from './eventGroupWrapper.module.css'; -import NoteEvent from './NoteEvent'; -import { setEditNoteTooltip } from 'Duck/sessions';; - -// TODO: incapsulate toggler in LocationEvent -@withToggle('showLoadInfo', 'toggleLoadInfo') -@connect( - (state) => ({ - members: state.getIn(['members', 'list']), - currentUserId: state.getIn(['user', 'account', 'id']), - }), - { setEditNoteTooltip } -) -class EventGroupWrapper extends React.Component { - toggleLoadInfo = (e) => { - e.stopPropagation(); - this.props.toggleLoadInfo(); - }; - - componentDidUpdate(prevProps) { - if ( - prevProps.showLoadInfo !== this.props.showLoadInfo || - prevProps.query !== this.props.query || - prevProps.event.timestamp !== this.props.event.timestamp || - prevProps.isNote !== this.props.isNote - ) { - this.props.mesureHeight(); - } - } - componentDidMount() { - this.props.toggleLoadInfo(this.props.isFirst); - this.props.mesureHeight(); - } - - onEventClick = (e) => this.props.onEventClick(e, this.props.event); - - onCheckboxClick = (e) => this.props.onCheckboxClick(e, this.props.event); - - render() { - const { - event, - isLastEvent, - isLastInGroup, - isSelected, - isCurrent, - isEditing, - showSelection, - showLoadInfo, - isFirst, - presentInSearch, - isNote, - filterOutNote, - } = this.props; - const isLocation = event.type === TYPES.LOCATION; - - const whiteBg = - (isLastInGroup && event.type !== TYPES.LOCATION) || - (!isLastEvent && event.type !== TYPES.LOCATION); - const safeRef = String(event.referrer || ''); - - return ( -
- {isFirst && isLocation && event.referrer && ( -
- - Referrer: {safeRef} - -
- )} - {isNote ? ( - - ) : isLocation ? ( - - ) : ( - - )} -
- ); - } -} - -export default EventGroupWrapper diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventsBlock.tsx b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventsBlock.tsx deleted file mode 100644 index 5421ebbc4..000000000 --- a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventsBlock.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import cn from 'classnames'; -import { Icon } from 'UI'; -import { List, AutoSizer, CellMeasurer } from "react-virtualized"; -import { TYPES } from 'Types/session/event'; -import { setEventFilter, filterOutNote } from 'Duck/sessions'; -import EventGroupWrapper from './EventGroupWrapper'; -import styles from './eventsBlock.module.css'; -import EventSearch from './EventSearch/EventSearch'; -import { PlayerContext } from 'App/components/Session/playerContext'; -import { observer } from 'mobx-react-lite'; -import { RootStore } from 'App/duck' -import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache' -import { InjectedEvent } from 'Types/session/event' -import Session from 'Types/session' - -interface IProps { - setEventFilter: (filter: { query: string }) => void - filteredEvents: InjectedEvent[] - setActiveTab: (tab?: string) => void - query: string - events: Session['events'] - notesWithEvents: Session['notesWithEvents'] - filterOutNote: (id: string) => void - eventsIndex: number[] -} - -function EventsBlock(props: IProps) { - const [mouseOver, setMouseOver] = React.useState(true) - const scroller = React.useRef(null) - const cache = useCellMeasurerCache( { - fixedWidth: true, - defaultHeight: 300 - }); - - const { store, player } = React.useContext(PlayerContext) - - const { eventListNow, playing } = store.get() - - const { - filteredEvents, - eventsIndex, - filterOutNote, - query, - setActiveTab, - events, - notesWithEvents, - } = props - - const currentTimeEventIndex = eventListNow.length > 0 ? eventListNow.length - 1 : 0 - const usedEvents = filteredEvents || notesWithEvents - - const write = ({ target: { value } }: React.ChangeEvent) => { - props.setEventFilter({ query: value }) - - setTimeout(() => { - if (!scroller.current) return; - - scroller.current.scrollToRow(0); - }, 100) - } - - const clearSearch = () => { - props.setEventFilter({ query: '' }) - if (scroller.current) { - scroller.current.forceUpdateGrid(); - } - - setTimeout(() => { - if (!scroller.current) return; - - scroller.current.scrollToRow(0); - }, 100) - } - - React.useEffect(() => { - return () => { - clearSearch() - } - }, []) - React.useEffect(() => { - if (scroller.current) { - scroller.current.forceUpdateGrid(); - if (!mouseOver) { - scroller.current.scrollToRow(currentTimeEventIndex); - } - } - }, [currentTimeEventIndex]) - - const onEventClick = (_: React.MouseEvent, event: { time: number }) => player.jump(event.time) - const onMouseOver = () => setMouseOver(true) - const onMouseLeave = () => setMouseOver(false) - - const renderGroup = ({ index, key, style, parent }: { index: number; key: string; style: React.CSSProperties; parent: any }) => { - const isLastEvent = index === usedEvents.length - 1; - const isLastInGroup = isLastEvent || usedEvents[index + 1]?.type === TYPES.LOCATION; - const event = usedEvents[index]; - const isNote = 'noteId' in event - const isCurrent = index === currentTimeEventIndex; - - const heightBug = index === 0 && event?.type === TYPES.LOCATION && 'referrer' in event ? { top: 2 } : {} - return ( - - {({measure, registerChild}) => ( -
- -
- )} -
- ); - } - - const isEmptySearch = query && (usedEvents.length === 0 || !usedEvents) - return ( - <> -
-
- User Events { events.length }
- } - /> -
- -
- {isEmptySearch && ( -
- - No Matching Results -
- )} - - {({ height }) => ( - - )} - -
- - ); -} - -export default connect((state: RootStore) => ({ - session: state.getIn([ 'sessions', 'current' ]), - notesWithEvents: state.getIn([ 'sessions', 'current' ]).notesWithEvents, - events: state.getIn([ 'sessions', 'current' ]).events, - filteredEvents: state.getIn([ 'sessions', 'filteredEvents' ]), - query: state.getIn(['sessions', 'eventsQuery']), - eventsIndex: state.getIn([ 'sessions', 'eventsIndex' ]), -}), { - setEventFilter, - filterOutNote -})(observer(EventsBlock)) diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/LoadInfo.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/LoadInfo.js deleted file mode 100644 index 664caeb9b..000000000 --- a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/LoadInfo.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import styles from './loadInfo.module.css'; -import { numberWithCommas } from 'App/utils' - -const LoadInfo = ({ showInfo = false, onClick, event: { fcpTime, visuallyComplete, timeToInteractive }, prorata: { a, b, c } }) => ( -
-
- { typeof fcpTime === 'number' &&
} - { typeof visuallyComplete === 'number' &&
} - { typeof timeToInteractive === 'number' &&
} -
-
- { typeof fcpTime === 'number' && -
-
-
{ 'Time to Render' }
-
{ `${ numberWithCommas(fcpTime || 0) }ms` }
-
- } - { typeof visuallyComplete === 'number' && -
-
-
{ 'Visually Complete' }
-
{ `${ numberWithCommas(visuallyComplete || 0) }ms` }
-
- } - { typeof timeToInteractive === 'number' && -
-
-
{ 'Time To Interactive' }
-
{ `${ numberWithCommas(timeToInteractive || 0) }ms` }
-
- } -
-
-); - -LoadInfo.displayName = 'LoadInfo'; - -export default LoadInfo; diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/NoteEvent.tsx b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/NoteEvent.tsx deleted file mode 100644 index a09869ba5..000000000 --- a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/NoteEvent.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React from 'react'; -import { Icon } from 'UI'; -import { tagProps, Note } from 'App/services/NotesService'; -import { formatTimeOrDate } from 'App/date'; -import { useStore } from 'App/mstore'; -import { observer } from 'mobx-react-lite'; -import { ItemMenu } from 'UI'; -import copy from 'copy-to-clipboard'; -import { toast } from 'react-toastify'; -import { session } from 'App/routes'; -import { confirm } from 'UI'; -import { TeamBadge } from 'Shared/SessionListContainer/components/Notes'; - -interface Props { - note: Note; - noEdit: boolean; - filterOutNote: (id: number) => void; - onEdit: (noteTooltipObj: Record) => void; -} - -function NoteEvent(props: Props) { - const { settingsStore, notesStore } = useStore(); - const { timezone } = settingsStore.sessionSettings; - - const onEdit = () => { - props.onEdit({ - isVisible: true, - isEdit: true, - time: props.note.timestamp, - note: { - timestamp: props.note.timestamp, - tag: props.note.tag, - isPublic: props.note.isPublic, - message: props.note.message, - sessionId: props.note.sessionId, - noteId: props.note.noteId, - }, - }); - }; - - const onCopy = () => { - copy( - `${window.location.origin}/${window.location.pathname.split('/')[1]}${session( - props.note.sessionId - )}${props.note.timestamp > 0 ? `?jumpto=${props.note.timestamp}¬e=${props.note.noteId}` : `?note=${props.note.noteId}`}` - ); - toast.success('Note URL copied to clipboard'); - }; - - const onDelete = async () => { - if ( - await confirm({ - header: 'Confirm', - confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to delete this note?`, - }) - ) { - notesStore.deleteNote(props.note.noteId).then((r) => { - props.filterOutNote(props.note.noteId); - toast.success('Note deleted'); - }); - } - }; - const menuItems = [ - { icon: 'pencil', text: 'Edit', onClick: onEdit, disabled: props.noEdit }, - { icon: 'link-45deg', text: 'Copy URL', onClick: onCopy }, - { icon: 'trash', text: 'Delete', onClick: onDelete }, - ]; - return ( -
-
-
- -
-
-
- {props.note.userName} -
-
- {formatTimeOrDate(props.note.createdAt as unknown as number, timezone)} -
-
-
- -
-
-
- {props.note.message} -
-
-
- {props.note.tag ? ( -
- {props.note.tag} -
- ) : null} - {!props.note.isPublic ? null : } -
-
-
- ); -} - -export default observer(NoteEvent); diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/index.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/index.js deleted file mode 100644 index 47e4d4efb..000000000 --- a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './EventsBlock'; \ No newline at end of file diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx index 54e6e87e9..a3d7a84c7 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx @@ -29,14 +29,14 @@ interface IProps { function EventsBlock(props: IProps) { const [mouseOver, setMouseOver] = React.useState(true); const scroller = React.useRef(null); - const cache = useCellMeasurerCache( { + const cache = useCellMeasurerCache({ fixedWidth: true, defaultHeight: 300, }); const { store, player } = React.useContext(PlayerContext); - const { eventListNow, playing } = store.get(); + const { playing, tabStates } = store.get(); const { filteredEvents, @@ -44,10 +44,14 @@ function EventsBlock(props: IProps) { filterOutNote, query, setActiveTab, - events, notesWithEvents, } = props; + // TODO! multitab tab id + const eventListNow = Object.values(tabStates).reduce((acc: any[], tab) => { + return acc.concat(tab.eventListNow) + }, []) + const currentTimeEventIndex = eventListNow.length > 0 ? eventListNow.length - 1 : 0; const usedEvents = filteredEvents || notesWithEvents; diff --git a/frontend/app/components/Session_/Performance/Performance.tsx b/frontend/app/components/Session_/Performance/Performance.tsx index 330defb26..727683060 100644 --- a/frontend/app/components/Session_/Performance/Performance.tsx +++ b/frontend/app/components/Session_/Performance/Performance.tsx @@ -21,6 +21,7 @@ import stl from './performance.module.css'; import BottomBlock from '../BottomBlock'; import InfoLine from '../BottomBlock/InfoLine'; +import { toJS } from "mobx"; const CPU_VISUAL_OFFSET = 10; @@ -183,17 +184,22 @@ function Performance({ const [_data, setData] = React.useState([]) const { - performanceChartTime, - performanceChartData, connType, connBandwidth, - performanceAvailability: availability, + tabStates, + currentTab, } = store.get(); - React.useState(() => { + const { + performanceChartTime = [], + performanceChartData = [], + performanceAvailability: availability = {} + } = tabStates[currentTab]; + + React.useEffect(() => { setTicks(generateTicks(performanceChartData)); setData(addFpsMetadata(performanceChartData)); - }) + }, [currentTab]) const onDotClick = ({ index: pointer }: { index: number }) => { diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index 373f44746..0b0ccb209 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -151,11 +151,16 @@ function NetworkPanel({ startedAt }: { startedAt: number }) { domContentLoadedTime, loadTime, domBuildingTime, - fetchList, - resourceList, - fetchListNow, - resourceListNow, + tabStates, + currentTab } = store.get() + const { + fetchList = [], + resourceList = [], + fetchListNow = [], + resourceListNow = [] + } = tabStates[currentTab] + const { showModal } = useModal(); const [sortBy, setSortBy] = useState('time'); const [sortAscending, setSortAscending] = useState(true); diff --git a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx index 109e29c97..16b6b8289 100644 --- a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx +++ b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx @@ -22,7 +22,12 @@ const TABS = TAB_KEYS.map((tab) => ({ text: tab, key: tab })) function StackEventPanel() { const { player, store } = React.useContext(PlayerContext) const jump = (t: number) => player.jump(t) - const { stackList: list, stackListNow: listNow } = store.get() + const { currentTab, tabStates } = store.get() + + const { + stackList: list = [], + stackListNow: listNow = [], + } = tabStates[currentTab] const { sessionStore: { devTools }, diff --git a/frontend/app/components/shared/DevTools/useAutoscroll.ts b/frontend/app/components/shared/DevTools/useAutoscroll.ts index 290a27297..5225f59ea 100644 --- a/frontend/app/components/shared/DevTools/useAutoscroll.ts +++ b/frontend/app/components/shared/DevTools/useAutoscroll.ts @@ -6,7 +6,6 @@ import useCancelableTimeout from 'App/hooks/useCancelableTimeout' const TIMEOUT_DURATION = 5000; export function getLastItemTime(...lists: Timed[][]) { - console.log(lists) return Math.max(...lists.map(l => l.length ? l[l.length-1].time : 0)) } diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index 03d35cc57..f20ba7ee2 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -64,9 +64,7 @@ export const visualChanges = [ export default class MessageManager { static INITIAL_STATE: State = { ...SCREEN_INITIAL_STATE, - tabStates: { - '': { ...TabSessionManager.INITIAL_STATE }, - }, + tabStates: {}, skipIntervals: [], error: false, ready: false, @@ -125,6 +123,7 @@ export default class MessageManager { this.activityManager.end() this.state.update({ skipIntervals: this.activityManager.list }) } + Object.values(this.tabs).forEach(tab => tab.onFileReadSuccess?.()) } public onFileReadFailed = (e: any) => { @@ -176,7 +175,7 @@ export default class MessageManager { this.activeTab = tabId } if (!this.tabs[this.activeTab]) { - console.log(this.tabs, this.activeTab, tabId, this.activeTabManager.list) + console.error('missing tab state', this.tabs, this.activeTab, tabId, this.activeTabManager.list) } // console.log(this.tabs, this.activeTab) this.tabs[this.activeTab].move(t) @@ -187,7 +186,7 @@ export default class MessageManager { } } - public changeTab(tabId) { + public changeTab(tabId: string) { this.activeTab = tabId this.state.update({ currentTab: tabId }) this.tabs[tabId].move(this.state.get().time) diff --git a/frontend/app/player/web/TabManager.ts b/frontend/app/player/web/TabManager.ts index b0e20f26b..0ab1cc97f 100644 --- a/frontend/app/player/web/TabManager.ts +++ b/frontend/app/player/web/TabManager.ts @@ -1,7 +1,7 @@ import ListWalker from "Player/common/ListWalker"; import { ConnectionInformation, - Message, MType, + Message, MType, ResourceTiming, SetPageLocation, SetViewportScroll, SetViewportSize @@ -22,13 +22,20 @@ import { isDOMType } from "Player/web/messages/filters.gen"; export interface TabState extends ListsState { performanceAvailability?: PerformanceTrackManager['availability'] performanceChartData: PerformanceChartPoint[], + performanceChartTime: PerformanceChartPoint[] cssLoading: boolean, } +/** + * DO NOT DELETE UNUSED METHODS + * THEY'RE ALL USED IN MESSAGE MANAGER VIA this.tabs[id] + * */ + export default class TabSessionManager { static INITIAL_STATE: TabState = { ...LISTS_INITIAL_STATE, performanceChartData: [], + performanceChartTime: [], cssLoading: false, } @@ -155,7 +162,7 @@ export default class TabSessionManager { case MType.ResourceTiming: // TODO: merge `resource` and `fetch` lists into one here instead of UI if (msg.initiator !== ResourceType.FETCH && msg.initiator !== ResourceType.XHR) { - this.lists.lists.resource.insert(getResourceFromResourceTiming(msg, this.sessionStart)) + this.lists.lists.resource.insert(getResourceFromResourceTiming(msg as ResourceTiming, this.sessionStart)) } break; case MType.Fetch: @@ -211,7 +218,7 @@ export default class TabSessionManager { } move(t: number, index?: number): void { - const stateToUpdate: Partial = {}; + const stateToUpdate: Record = {}; /* == REFACTOR_ME == */ const lastLoadedLocationMsg = this.loadedLocationManager.moveGetLast(t, index); if (!!lastLoadedLocationMsg) { @@ -300,7 +307,8 @@ export default class TabSessionManager { } public onFileReadSuccess = () => { - const stateToUpdate : Partial= { + console.log('triggered', this.performanceTrackManager) + const stateToUpdate : Partial> = { performanceChartData: this.performanceTrackManager.chartData, performanceAvailability: this.performanceTrackManager.availability, ...this.lists.getFullListsState(),