diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index 48d881c29..96a4859cf 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -42,6 +42,7 @@ import { updateLastPlayedSession } from 'Duck/sessions'; import OverviewPanel from '../OverviewPanel'; import ConsolePanel from 'Shared/DevTools/ConsolePanel'; import ProfilerPanel from 'Shared/DevTools/ProfilerPanel'; +import StackEventPanel from 'Shared/DevTools/StackEventPanel'; @connectPlayer((state) => ({ live: state.live, @@ -115,7 +116,8 @@ export default class Player extends React.PureComponent { // )} - {bottomBlock === STACKEVENTS && } + {/* {bottomBlock === STACKEVENTS && } */} + {bottomBlock === STACKEVENTS && } {bottomBlock === STORAGE && } {bottomBlock === PROFILER && } {bottomBlock === PERFORMANCE && } diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx index 8f5835cfa..efc4f735a 100644 --- a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx +++ b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx @@ -95,7 +95,7 @@ function ConsolePanel(props: Props) { ); }; - let filtered = React.useMemo(() => { + const filtered = React.useMemo(() => { const filterRE = getRE(filter, 'i'); let list = logs; diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index 6be39a77a..ee0ce8a55 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -233,9 +233,9 @@ function NetworkPanel(props: Props) { (list = list.filter((networkCall: any) => networkCall.url !== fetchCall.url)) ); list = list.concat(fetchList); - list = list.sort((a: any, b: any) => { - return compare(a, b, sortBy); - }); + // list = list.sort((a: any, b: any) => { + // return compare(a, b, sortBy); + // }); // if (!sortAscending) { // list = list.reverse(); diff --git a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx new file mode 100644 index 000000000..57abe1808 --- /dev/null +++ b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx @@ -0,0 +1,183 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { hideHint } from 'Duck/components/player'; +import { Tooltip, Tabs, Input, NoContent, Icon, Toggler } from 'UI'; +import { getRE } from 'App/utils'; +import { List, CellMeasurer, CellMeasurerCache, AutoSizer } from 'react-virtualized'; + +import TimeTable from '../TimeTable'; +import BottomBlock from '../BottomBlock'; +import { connectPlayer, jump } from 'Player'; +import { useModal } from 'App/components/Modal'; +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; +import { DATADOG, SENTRY, STACKDRIVER, typeList } from 'Types/session/stackEvent'; +import { connect } from 'react-redux'; +import StackEventRow from 'Shared/DevTools/StackEventRow'; + +let timeOut: any = null; +const TIMEOUT_DURATION = 5000; +const INDEX_KEY = 'stackEvent'; +const ALL = 'ALL'; +const TABS = [ALL, ...typeList].map((tab) => ({ text: tab, key: tab })); + +interface Props { + list: any; + hideHint: any; + time: any; +} +function StackEventPanel(props: Props) { + const { list, time } = props; + const additionalHeight = 0; + const { + sessionStore: { devTools }, + } = useStore(); + const [isDetailsModalActive, setIsDetailsModalActive] = useState(false); + const [filteredList, setFilteredList] = useState([]); + const filter = useObserver(() => devTools[INDEX_KEY].filter); + const activeTab = useObserver(() => devTools[INDEX_KEY].activeTab); + const activeIndex = useObserver(() => devTools[INDEX_KEY].index); + const [pauseSync, setPauseSync] = useState(activeIndex > 0); + const synRef: any = useRef({}); + synRef.current = { + pauseSync, + activeIndex, + }; + const _list = React.useRef(); + + const onTabClick = (activeTab: any) => devTools.update(INDEX_KEY, { activeTab }); + const onFilterChange = ({ target: { value } }: any) => { + devTools.update(INDEX_KEY, { filter: value }); + }; + + const getCurrentIndex = () => { + return filteredList.filter((item: any) => item.time <= time).length - 1; + }; + + const removePause = () => { + clearTimeout(timeOut); + timeOut = setTimeout(() => { + devTools.update(INDEX_KEY, { index: getCurrentIndex() }); + setPauseSync(false); + }, TIMEOUT_DURATION); + }; + + useEffect(() => { + const currentIndex = getCurrentIndex(); + if (currentIndex !== activeIndex && !pauseSync) { + devTools.update(INDEX_KEY, { index: currentIndex }); + } + }, [time]); + + const onMouseLeave = () => { + if (isDetailsModalActive) return; + removePause(); + }; + + React.useMemo(() => { + const filterRE = getRE(filter, 'i'); + let list = props.list; + + list = list.filter( + ({ name, source }: any) => + (!!filter ? filterRE.test(name) : true) && (activeTab === ALL || activeTab === source) + ); + + setFilteredList(list); + }, [filter, activeTab]); + + const tabs = useMemo(() => { + return TABS.filter(({ key }) => key === ALL || list.some(({ source }: any) => key === source)); + }, []); + + const cache = new CellMeasurerCache({ + fixedWidth: true, + keyMapper: (index: number) => filteredList[index], + }); + + const _rowRenderer = ({ index, key, parent, style }: any) => { + const item = filteredList[index]; + + return ( + // @ts-ignore + + {() => ( + jump(item.time)} + /> + )} + + ); + }; + + return ( + setPauseSync(true)} + onMouseLeave={onMouseLeave} + > + +
+ Stack Events + +
+ +
+ + + + No Data + + } + size="small" + show={filteredList.length === 0} + > + + {({ height, width }: any) => ( + + )} + + + +
+ ); +} + +export default connect( + (state: any) => ({ + hintIsHidden: + state.getIn(['components', 'player', 'hiddenHints', 'stack']) || + !state.getIn(['site', 'list']).some((s: any) => s.stackIntegrations), + }), + { hideHint } +)( + connectPlayer((state: any) => ({ + list: state.stackList, + time: state.time, + }))(StackEventPanel) +); diff --git a/frontend/app/components/shared/DevTools/StackEventPanel/index.ts b/frontend/app/components/shared/DevTools/StackEventPanel/index.ts new file mode 100644 index 000000000..bb0ca8cb6 --- /dev/null +++ b/frontend/app/components/shared/DevTools/StackEventPanel/index.ts @@ -0,0 +1 @@ +export { default } from './StackEventPanel'; diff --git a/frontend/app/components/shared/DevTools/StackEventRow/StackEventRow.tsx b/frontend/app/components/shared/DevTools/StackEventRow/StackEventRow.tsx index b6b1a8a6f..e5af72207 100644 --- a/frontend/app/components/shared/DevTools/StackEventRow/StackEventRow.tsx +++ b/frontend/app/components/shared/DevTools/StackEventRow/StackEventRow.tsx @@ -9,9 +9,11 @@ import StackEventModal from '../StackEventModal'; interface Props { event: any; onJump: any; + style?: any; + isActive?: boolean; } function StackEventRow(props: Props) { - const { event, onJump } = props; + const { event, onJump, style, isActive } = props; let message = event.payload[0] || ''; message = typeof message === 'string' ? message : JSON.stringify(message); const onClickDetails = () => { @@ -30,11 +32,13 @@ function StackEventRow(props: Props) { return (
diff --git a/frontend/app/mstore/sessionStore.ts b/frontend/app/mstore/sessionStore.ts index b947bd01a..f19f747fd 100644 --- a/frontend/app/mstore/sessionStore.ts +++ b/frontend/app/mstore/sessionStore.ts @@ -49,15 +49,20 @@ class UserFilter { } } -class DevTools { - networkIndex: 0; - network: any; +interface BaseDevState { + index: number; + filter: string; + activeTab: string; + isError: boolean; +} - consoleIndex: 0; - eventsIndex: 0; +class DevTools { + network: BaseDevState; + stackEvent: BaseDevState; constructor() { - this.network = { index: 0, search: '', activeTab: 'ALL', isError: false }; + this.network = { index: 0, filter: '', activeTab: 'ALL', isError: false }; + this.stackEvent = { index: 0, filter: '', activeTab: 'ALL', isError: false }; makeAutoObservable(this, { update: action, });