diff --git a/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx b/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx
new file mode 100644
index 000000000..8f0641200
--- /dev/null
+++ b/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx
@@ -0,0 +1,68 @@
+import React, { useEffect, useState } from 'react';
+import { connect } from 'react-redux';
+import { applyFilter, addAttribute } from 'Duck/filters';
+import { fetchList } from 'Duck/sessions';
+import { KEYS } from 'Types/filter/customFilter';
+import { Link } from 'UI';
+import Filter from 'Types/filter';
+import { List } from 'immutable';
+import Counter from 'App/components/shared/SessionItem/Counter';
+import { fetchLiveList } from 'Duck/sessions';
+import { session as sessionRoute } from 'App/routes';
+import { session } from 'App/components/Session_/session.css';
+
+const RowItem = ({ startedAt, sessionId }) => {
+ return (
+
+
+ Tab1
+
+
+
+ );
+}
+
+interface Props {
+ list: List,
+ session: any,
+ fetchLiveList: () => void,
+ applyFilter: () => void,
+ filters: Filter
+ addAttribute: (obj) => void,
+ loading: boolean
+}
+
+const AssistTabs = React.memo((props: Props) => {
+ const [showMenu, setShowMenu] = useState(false)
+ useEffect(() => {
+ if (!props.loading && props.list.size === 0) {
+ props.fetchLiveList();
+ }
+ }, [props.list])
+
+ return (
+
+
setShowMenu(!showMenu)}>Active Tabs
+ {showMenu && (
+
+ {props.list.map((item, index) => (
+
+ ))}
+
+ )}
+
+ );
+});
+
+export default connect(state => {
+ const session = state.getIn([ 'sessions', 'current' ]);
+ return {
+ loading: state.getIn([ 'sessions', 'loading' ]),
+ list: state.getIn(['sessions', 'liveSessions']).filter(i => i.userId === session.userId),
+ session,
+ filters: state.getIn([ 'filters', 'appliedFilter' ]),
+ }
+}, { applyFilter, addAttribute, fetchLiveList })(AssistTabs);
\ No newline at end of file
diff --git a/frontend/app/components/Assist/components/AssistTabs/assistTabs.css b/frontend/app/components/Assist/components/AssistTabs/assistTabs.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/frontend/app/components/Assist/components/AssistTabs/index.ts b/frontend/app/components/Assist/components/AssistTabs/index.ts
new file mode 100644
index 000000000..b089b3734
--- /dev/null
+++ b/frontend/app/components/Assist/components/AssistTabs/index.ts
@@ -0,0 +1 @@
+export { default } from './AssistTabs';
\ No newline at end of file
diff --git a/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js b/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js
index fb89f6967..7275d9ac0 100644
--- a/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js
+++ b/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js
@@ -119,7 +119,7 @@ export default connect(state => ({
activeFlow: state.getIn([ 'filters', 'activeFlow' ]),
captureRate: state.getIn(['watchdogs', 'captureRate']),
filters: state.getIn([ 'filters', 'appliedFilter' ]),
- sessionsLoading: state.getIn([ 'sessions', 'loading' ]),
+ sessionsLoading: state.getIn([ 'sessions', 'fetchLiveListRequest', 'loading' ]),
}), {
fetchWatchdogStatus, setActiveFlow, clearEvents, setActiveTab, fetchSessionList
})(SessionsMenu);
diff --git a/frontend/app/components/Session/LivePlayer.js b/frontend/app/components/Session/LivePlayer.js
index cbc3410cd..07b8d3adc 100644
--- a/frontend/app/components/Session/LivePlayer.js
+++ b/frontend/app/components/Session/LivePlayer.js
@@ -31,7 +31,7 @@ const InitLoader = connectPlayer(state => ({
}))(Loader);
-function WebPlayer ({ showAssist, session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, loadingCredentials, assistCredendials, request }) {
+const WebPlayer = React.memo(({ showAssist, session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, loadingCredentials, assistCredendials, request }) => {
useEffect(() => {
if (!loadingCredentials) {
initPlayer(session, jwt, assistCredendials);
@@ -60,7 +60,7 @@ function WebPlayer ({ showAssist, session, toggleFullscreen, closeBottomBlock, l
);
-}
+});
export default withRequest({
initialData: null,
diff --git a/frontend/app/components/Session/Session.js b/frontend/app/components/Session/Session.js
index 6b90ff4c1..8ff629c55 100644
--- a/frontend/app/components/Session/Session.js
+++ b/frontend/app/components/Session/Session.js
@@ -6,6 +6,7 @@ import { fetchList as fetchSlackList } from 'Duck/integrations/slack';
import { Link, NoContent, Loader } from 'UI';
import { sessions as sessionsRoute } from 'App/routes';
import withPermissions from 'HOCs/withPermissions'
+import { fetchLiveList } from 'Duck/sessions';
import LivePlayer from './LivePlayer';
import WebPlayer from './WebPlayer';
@@ -20,6 +21,8 @@ function Session({
session,
fetchSession,
fetchSlackList,
+ fetchLiveList,
+ filters
}) {
usePageTitle("OpenReplay Session Player");
useEffect(() => {
@@ -36,6 +39,12 @@ function Session({
}
},[ sessionId ]);
+ // useEffect(() => {
+ // if (session && session.live) {
+ // fetchLiveList(filters.toJS())
+ // }
+ // }, [session])
+
return (
({
list: state.fetchList,
}))
+@connect(state => ({
+ timelinePointer: state.getIn(['sessions', 'timelinePointer']),
+}))
export default class Fetch extends React.PureComponent {
state = {
filter: "",
+ filteredList: this.props.list,
current: null,
+ currentIndex: 0,
+ showFetchDetails: false,
+ hasNextError: false,
+ hasPreviousError: false,
}
- onFilterChange = (e, { value }) => this.setState({ filter: value })
+
+ onFilterChange = (e, { value }) => {
+ const { list } = this.props;
+ const filterRE = getRE(value, 'i');
+ const filtered = list
+ .filter((r) => filterRE.test(r.name) || filterRE.test(r.url) || filterRE.test(r.method) || filterRE.test(r.status));
+ this.setState({ filter: value, filteredList: value ? filtered : list, currentIndex: 0 });
+ }
setCurrent = (item, index) => {
pause()
+ jump(item.time)
this.setState({ current: item, currentIndex: index });
}
- closeModal = () => this.setState({ current: null})
+ onRowClick = (item, index) => {
+ pause()
+ this.setState({ current: item, currentIndex: index, showFetchDetails: true });
+ }
+
+ closeModal = () => this.setState({ current: null, showFetchDetails: false });
nextClickHander = () => {
const { list } = this.props;
@@ -33,6 +54,7 @@ export default class Fetch extends React.PureComponent {
if (currentIndex === list.length - 1) return;
const newIndex = currentIndex + 1;
this.setCurrent(list[newIndex], newIndex);
+ this.setState({ showFetchDetails: true });
}
prevClickHander = () => {
@@ -42,15 +64,24 @@ export default class Fetch extends React.PureComponent {
if (currentIndex === 0) return;
const newIndex = currentIndex - 1;
this.setCurrent(list[newIndex], newIndex);
+ this.setState({ showFetchDetails: true });
+ }
+
+ static getDerivedStateFromProps(nextProps, prevState) {
+ const { filteredList } = prevState;
+ if (nextProps.timelinePointer) {
+ let activeItem = filteredList.find((r) => r.time >= nextProps.timelinePointer.time);
+ activeItem = activeItem || filteredList[filteredList.length - 1];
+ return {
+ current: activeItem,
+ currentIndex: filteredList.indexOf(activeItem),
+ };
+ }
}
render() {
- const { list } = this.props;
- const { filter, current, currentIndex } = this.state;
- const filterRE = getRE(filter, 'i');
- const filtered = list
- .filter((r) => filterRE.test(r.name) || filterRE.test(r.url) || filterRE.test(r.method) || filterRE.test(r.status));
-
+ // const { list } = this.props;
+ const { current, currentIndex, showFetchDetails, filteredList } = this.state;
return (
}
- isDisplayed={ current != null }
- content={ current &&
+ isDisplayed={ current != null && showFetchDetails }
+ content={ current && showFetchDetails &&
}
onClose={ this.closeModal }
@@ -88,25 +119,31 @@ export default class Fetch extends React.PureComponent {
Fetch
-
+
{[
@@ -120,7 +157,7 @@ export default class Fetch extends React.PureComponent {
width: 60,
}, {
label: "Name",
- width: 130,
+ width: 180,
render: renderName,
},
{
diff --git a/frontend/app/components/Session_/Network/Network.js b/frontend/app/components/Session_/Network/Network.js
index 878266bda..1d86394c5 100644
--- a/frontend/app/components/Session_/Network/Network.js
+++ b/frontend/app/components/Session_/Network/Network.js
@@ -3,14 +3,9 @@ import { connectPlayer, jump, pause } from 'Player';
import { QuestionMarkHint, Popup, Tabs, Input } from 'UI';
import { getRE } from 'App/utils';
import { TYPES } from 'Types/session/resource';
-import { formatBytes } from 'App/utils';
-import { formatMs } from 'App/date';
-
-import TimeTable from '../TimeTable';
-import BottomBlock from '../BottomBlock';
-import InfoLine from '../BottomBlock/InfoLine';
import stl from './network.css';
import NetworkContent from './NetworkContent';
+import { connect } from 'react-redux';
const ALL = 'ALL';
const XHR = 'xhr';
@@ -28,73 +23,24 @@ const TAB_TO_TYPE_MAP = {
[ MEDIA ]: TYPES.MEDIA,
[ OTHER ]: TYPES.OTHER
}
-const TABS = [ ALL, XHR, JS, CSS, IMG, MEDIA, OTHER ].map(tab => ({
- text: tab,
- key: tab,
-}));
-
-const DOM_LOADED_TIME_COLOR = "teal";
-const LOAD_TIME_COLOR = "red";
export function renderName(r) {
return (
- { r.name } }
- content={ { r.url }
}
- size="mini"
- position="right center"
- />
- );
-}
-
-const renderXHRText = () => (
-
- {XHR}
-
- Use our Fetch plugin
- {' to capture HTTP requests and responses, including status codes and bodies.'}
- We also provide support for GraphQL
- {' for easy debugging of your queries.'}
- >
- }
- className="ml-1"
- />
-
-);
-
-function renderSize(r) {
- let triggerText;
- let content;
- if (r.decodedBodySize == null) {
- triggerText = "x";
- content = "Not captured";
- } else {
- const headerSize = r.headerSize || 0;
- const encodedSize = r.encodedBodySize || 0;
- const transferred = headerSize + encodedSize;
- const showTransferred = r.headerSize != null;
-
- triggerText = formatBytes(r.decodedBodySize);
- content = (
-
- { showTransferred &&
- - {`${formatBytes( r.encodedBodySize + headerSize )} transfered over network`}
- }
- - {`Resource size: ${formatBytes(r.decodedBodySize)} `}
-
- );
- }
-
- return (
- { triggerText } }
- content={ content }
- size="mini"
- position="right center"
- />
+ }
+ content={ { r.url }
}
+ size="mini"
+ position="right center"
+ />
+ {
+ e.stopPropagation();
+ jump(r.time)
+ }}
+ >Jump
+
);
}
@@ -130,14 +76,18 @@ export function renderDuration(r) {
resources: state.resourceList,
domContentLoadedTime: state.domContentLoadedTime,
loadTime: state.loadTime,
- time: state.time,
+ // time: state.time,
playing: state.playing,
domBuildingTime: state.domBuildingTime,
fetchPresented: state.fetchList.length > 0,
}))
+@connect(state => ({
+ timelinePointer: state.getIn(['sessions', 'timelinePointer']),
+}))
export default class Network extends React.PureComponent {
state = {
filter: '',
+ filteredList: this.props.resources,
activeTab: ALL,
currentIndex: 0
}
@@ -149,7 +99,30 @@ export default class Network extends React.PureComponent {
}
onTabClick = activeTab => this.setState({ activeTab })
- onFilterChange = (e, { value }) => this.setState({ filter: value })
+
+ onFilterChange = (e, { value }) => {
+ const { resources } = this.props;
+ const filterRE = getRE(value, 'i');
+ const filtered = resources.filter(({ type, name }) =>
+ filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[ activeTab ]));
+
+ this.setState({ filter: value, filteredList: value ? filtered : resources, currentIndex: 0 });
+ }
+ // onFilterChange = (e, { value }) => this.setState({ filter: value })
+
+ componentDidUpdate() {
+ console.log('test')
+ }
+
+ static getDerivedStateFromProps(nextProps, prevState) {
+ const { filteredList } = prevState;
+ if (nextProps.timelinePointer) {
+ const activeItem = filteredList.find((r) => r.time >= nextProps.timelinePointer.time);
+ return {
+ currentIndex: activeItem ? filteredList.indexOf(activeItem) : filteredList.length - 1,
+ };
+ }
+ }
render() {
const {
@@ -159,50 +132,23 @@ export default class Network extends React.PureComponent {
loadTime,
domBuildingTime,
fetchPresented,
- time,
+ // time,
playing
} = this.props;
- const { filter, activeTab, currentIndex } = this.state;
- const filterRE = getRE(filter, 'i');
- let filtered = resources.filter(({ type, name }) =>
- filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[ activeTab ]));
-
-// const referenceLines = [];
-// if (domContentLoadedTime != null) {
-// referenceLines.push({
-// time: domContentLoadedTime,
-// color: DOM_LOADED_TIME_COLOR,
-// })
-// }
-// if (loadTime != null) {
-// referenceLines.push({
-// time: loadTime,
-// color: LOAD_TIME_COLOR,
-// })
-// }
-//
-// let tabs = TABS;
-// if (!fetchPresented) {
-// tabs = TABS.map(tab => tab.key === XHR
-// ? {
-// text: renderXHRText(),
-// key: XHR,
-// }
-// : tab
-// );
-// }
-
- const resourcesSize = filtered.reduce((sum, { decodedBodySize }) => sum + (decodedBodySize || 0), 0);
- const transferredSize = filtered
+ const { filter, activeTab, currentIndex, filteredList } = this.state;
+ // const filterRE = getRE(filter, 'i');
+ // let filtered = resources.filter(({ type, name }) =>
+ // filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[ activeTab ]));
+ const resourcesSize = filteredList.reduce((sum, { decodedBodySize }) => sum + (decodedBodySize || 0), 0);
+ const transferredSize = filteredList
.reduce((sum, { headerSize, encodedBodySize }) => sum + (headerSize || 0) + (encodedBodySize || 0), 0);
return (
- {/*
-
-
-
-
-
-
-
- 0 }
- />
- 0 }
- />
-
-
-
-
-
- {[
- {
- label: "Status",
- dataKey: 'status',
- width: 70,
- }, {
- label: "Type",
- dataKey: 'type',
- width: 60,
- }, {
- label: "Name",
- width: 130,
- render: renderName,
- },
- {
- label: "Size",
- width: 60,
- render: renderSize,
- },
- {
- label: "Time",
- width: 80,
- render: renderDuration,
- }
- ]}
-
-
- */}
);
}
diff --git a/frontend/app/components/Session_/Network/network.css b/frontend/app/components/Session_/Network/network.css
index eb37e49c9..e299211da 100644
--- a/frontend/app/components/Session_/Network/network.css
+++ b/frontend/app/components/Session_/Network/network.css
@@ -22,7 +22,7 @@
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
- max-width: 100%;
+ max-width: 80%;
width: fit-content;
}
.popupNameContent {
diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.js b/frontend/app/components/Session_/Player/Controls/Timeline.js
index c25101d69..27c1de1ab 100644
--- a/frontend/app/components/Session_/Player/Controls/Timeline.js
+++ b/frontend/app/components/Session_/Player/Controls/Timeline.js
@@ -7,6 +7,7 @@ import TimeTracker from './TimeTracker';
import { ReduxTime } from './Time';
import stl from './timeline.css';
import { TYPES } from 'Types/session/event';
+import { setTimelinePointer } from 'Duck/sessions';
const getPointerIcon = (type) => {
// exception,
@@ -69,7 +70,7 @@ const getPointerIcon = (type) => {
state.getIn([ 'sessions', 'current', 'clickRageTime' ]),
returningLocationTime: state.getIn([ 'sessions', 'current', 'returningLocation' ]) &&
state.getIn([ 'sessions', 'current', 'returningLocationTime' ]),
-}))
+}), { setTimelinePointer })
export default class Timeline extends React.PureComponent {
seekProgress = (e) => {
const { endTime } = this.props;
@@ -78,9 +79,10 @@ export default class Timeline extends React.PureComponent {
this.props.jump(time);
}
- createEventClickHandler = time => (e) => {
+ createEventClickHandler = pointer => (e) => {
e.stopPropagation();
- this.props.jump(time)
+ this.props.jump(pointer.time);
+ this.props.setTimelinePointer(pointer);
}
componentDidMount() {
@@ -144,7 +146,7 @@ export default class Timeline extends React.PureComponent {
//width: `${ 2000 * scale }%`
} }
className={ stl.clickRage }
- onClick={ this.createEventClickHandler(iss.time) }
+ onClick={ this.createEventClickHandler(iss) }
>
+ { live &&
}
{ live &&
}
{ !live && (
<>
diff --git a/frontend/app/components/Session_/TimeTable/TimeTable.js b/frontend/app/components/Session_/TimeTable/TimeTable.js
index 9753e3d44..e316dae56 100644
--- a/frontend/app/components/Session_/TimeTable/TimeTable.js
+++ b/frontend/app/components/Session_/TimeTable/TimeTable.js
@@ -135,7 +135,7 @@ export default class TimeTable extends React.PureComponent
{
...computeTimeLine(this.props.rows, this.state.firstVisibleRowIndex, this.visibleCount),
});
}
- if (this.props.activeIndex && prevProps.activeIndex !== this.props.activeIndex && this.scroller.current != null) {
+ if (this.props.activeIndex >= 0 && prevProps.activeIndex !== this.props.activeIndex && this.scroller.current != null) {
this.scroller.current.scrollToRow(this.props.activeIndex);
}
}
@@ -168,7 +168,7 @@ export default class TimeTable extends React.PureComponent {
onRowClick(row, index) : null }
id="table-row"
>
@@ -223,7 +223,7 @@ export default class TimeTable extends React.PureComponent
{
navigation=false,
referenceLines = [],
additionalHeight = 0,
- activeIndex
+ activeIndex,
} = this.props;
const {
timewidth,
@@ -247,7 +247,7 @@ export default class TimeTable extends React.PureComponent {
return (
{ navigation &&
-
+
{
@@ -242,13 +244,16 @@ const reducer = (state = initialState, action = {}) => {
return state.set('insights', List(action.data).sort((a, b) => b.count - a.count));
case SET_FUNNEL_PAGE_FLAG:
return state.set('funnelPage', action.funnelPage ? Map(action.funnelPage) : false);
+ case SET_TIMELINE_POINTER:
+ return state.set('timelinePointer', action.pointer);
default:
return state;
}
};
export default withRequestState({
- _: [ FETCH, FETCH_LIST, FETCH_LIVE_LIST ],
+ _: [ FETCH, FETCH_LIST ],
+ fetchLiveListRequest: FETCH_LIVE_LIST,
fetchFavoriteListRequest: FETCH_FAVORITE_LIST,
toggleFavoriteRequest: TOGGLE_FAVORITE,
fetchErrorStackList: FETCH_ERROR_STACK,
@@ -262,10 +267,10 @@ function init(session) {
}
}
-export const fetchList = (params = {}, clear = false) => (dispatch, getState) => {
+export const fetchList = (params = {}, clear = false, live = false) => (dispatch, getState) => {
const activeTab = getState().getIn([ 'sessions', 'activeTab' ]);
- return dispatch(activeTab && activeTab.type === 'live' ? {
+ return dispatch((activeTab && activeTab.type === 'live' || live )? {
types: FETCH_LIVE_LIST.toArray(),
call: client => client.post('/assist/sessions', params),
} : {
@@ -376,3 +381,9 @@ export function setFunnelPage(funnelPage) {
}
}
+export function setTimelinePointer(pointer) {
+ return {
+ type: SET_TIMELINE_POINTER,
+ pointer
+ }
+}
\ No newline at end of file
diff --git a/frontend/app/svg/icons/os/fedora.svg b/frontend/app/svg/icons/os/fedora.svg
new file mode 100644
index 000000000..86e9f115e
--- /dev/null
+++ b/frontend/app/svg/icons/os/fedora.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/frontend/app/types/filter/filter.js b/frontend/app/types/filter/filter.js
index 42637a2fa..8b3301aad 100644
--- a/frontend/app/types/filter/filter.js
+++ b/frontend/app/types/filter/filter.js
@@ -34,8 +34,8 @@ export default Record({
startDate,
endDate,
- sort: undefined,
- order: undefined,
+ sort: 'startTs',
+ order: 'desc',
viewed: undefined,
consoleLogCount: undefined,