From 78d7df72a58210d9abe949ec567d471fd360d797 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 19 Jul 2022 14:43:43 +0200 Subject: [PATCH] Session list - redesign (#621) * change(ui) - removed env * change(ui) - no content component updates * feat(ui) - session list - wip * feat(ui) - session list - wip * feat(ui) - session list - wip * feat(ui) - session list - wip * fix(ui) - live session list key * feat(ui) - session list - wip * feat(ui) - session list - wip * feat(backend): set default size of first part of session mob file to 1mb * feat(backend): added extra information for db metrics * fix(ui) - siteform loader, trash btn project exists check, IconButton replace Co-authored-by: Alexander Zavorotynskiy --- frontend/app/Router.js | 2 +- frontend/app/components/Overview/Overview.tsx | 28 +++++ frontend/app/components/Overview/index.ts | 1 + .../LiveSessionList/LiveSessionList.tsx | 2 +- .../SessionListContainer.tsx | 17 +++ .../components/NoContentMessage.tsx | 20 ++++ .../SessionHeader/SessionHeader.tsx | 48 ++++++++ .../components/SessionHeader/index.ts | 1 + .../components/SessionList/SessionList.tsx | 103 ++++++++++++++++++ .../components/SessionList/index.ts | 1 + .../components/SessionSort/SessionSort.tsx | 42 +++++++ .../components/SessionSort/index.ts | 1 + .../SessionSort/sortDropdown.module.css | 23 ++++ .../components/SessionTags/SessionTags.tsx | 66 +++++++++++ .../components/SessionTags/index.ts | 1 + .../shared/SessionListContainer/index.ts | 0 .../app/components/ui/NoContent/NoContent.js | 30 ----- .../app/components/ui/NoContent/NoContent.tsx | 29 +++++ .../ui/NoContent/{index.js => index.ts} | 0 .../ui/NoContent/noContent.module.css | 12 -- frontend/app/duck/search.js | 4 +- frontend/app/styles/global.scss | 4 + frontend/app/types/session/issue.js | 21 ++-- frontend/env.js | 29 ----- 24 files changed, 401 insertions(+), 84 deletions(-) create mode 100644 frontend/app/components/Overview/Overview.tsx create mode 100644 frontend/app/components/Overview/index.ts create mode 100644 frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx create mode 100644 frontend/app/components/shared/SessionListContainer/components/NoContentMessage.tsx create mode 100644 frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx create mode 100644 frontend/app/components/shared/SessionListContainer/components/SessionHeader/index.ts create mode 100644 frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx create mode 100644 frontend/app/components/shared/SessionListContainer/components/SessionList/index.ts create mode 100644 frontend/app/components/shared/SessionListContainer/components/SessionSort/SessionSort.tsx create mode 100644 frontend/app/components/shared/SessionListContainer/components/SessionSort/index.ts create mode 100644 frontend/app/components/shared/SessionListContainer/components/SessionSort/sortDropdown.module.css create mode 100644 frontend/app/components/shared/SessionListContainer/components/SessionTags/SessionTags.tsx create mode 100644 frontend/app/components/shared/SessionListContainer/components/SessionTags/index.ts create mode 100644 frontend/app/components/shared/SessionListContainer/index.ts delete mode 100644 frontend/app/components/ui/NoContent/NoContent.js create mode 100644 frontend/app/components/ui/NoContent/NoContent.tsx rename frontend/app/components/ui/NoContent/{index.js => index.ts} (100%) delete mode 100644 frontend/env.js diff --git a/frontend/app/Router.js b/frontend/app/Router.js index f5dc4c593..acbdd9cbb 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -31,7 +31,7 @@ const LiveSessionPure = lazy(() => import('Components/Session/LiveSession')); const OnboardingPure = lazy(() => import('Components/Onboarding/Onboarding')); const ClientPure = lazy(() => import('Components/Client/Client')); const AssistPure = lazy(() => import('Components/Assist')); -const BugFinderPure = lazy(() => import('Components/BugFinder/BugFinder')); +const BugFinderPure = lazy(() => import('Components/Overview')); const DashboardPure = lazy(() => import('Components/Dashboard/NewDashboard')); const ErrorsPure = lazy(() => import('Components/Errors/Errors')); const FunnelDetailsPure = lazy(() => import('Components/Funnels/FunnelDetails')); diff --git a/frontend/app/components/Overview/Overview.tsx b/frontend/app/components/Overview/Overview.tsx new file mode 100644 index 000000000..78b4bfe2b --- /dev/null +++ b/frontend/app/components/Overview/Overview.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import withPageTitle from 'HOCs/withPageTitle'; +import NoSessionsMessage from 'Shared/NoSessionsMessage'; +import MainSearchBar from 'Shared/MainSearchBar'; +import SessionSearch from 'Shared/SessionSearch'; +import SessionListContainer from 'Shared/SessionListContainer/SessionListContainer'; + +function Overview() { + return ( +
+
+
+ + +
+ + + +
+ +
+
+
+
+ ); +} + +export default withPageTitle('Sessions - OpenReplay')(Overview); diff --git a/frontend/app/components/Overview/index.ts b/frontend/app/components/Overview/index.ts new file mode 100644 index 000000000..44bcc2216 --- /dev/null +++ b/frontend/app/components/Overview/index.ts @@ -0,0 +1 @@ +export { default } from './Overview'; \ No newline at end of file diff --git a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx index f4bb1f45d..fae2e40cf 100644 --- a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx +++ b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { Fragment, useEffect } from 'react'; import { connect } from 'react-redux'; import { NoContent, Loader, Pagination } from 'UI'; import { List } from 'immutable'; diff --git a/frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx b/frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx new file mode 100644 index 000000000..156f845c4 --- /dev/null +++ b/frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import SessionList from './components/SessionList'; +import SessionHeader from './components/SessionHeader'; + +interface Props {} +function SessionListContainer(props: Props) { + return ( +
+ +
+ +
+
+ ); +} + +export default SessionListContainer; diff --git a/frontend/app/components/shared/SessionListContainer/components/NoContentMessage.tsx b/frontend/app/components/shared/SessionListContainer/components/NoContentMessage.tsx new file mode 100644 index 000000000..c80ec6555 --- /dev/null +++ b/frontend/app/components/shared/SessionListContainer/components/NoContentMessage.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { connect } from 'react-redux'; + +function NoContentMessage({ activeTab }: any) { + return
{getNoContentMessage(activeTab)}
; +} + +export default connect((state: any) => ({ + activeTab: state.getIn(['search', 'activeTab']), +}))(NoContentMessage); + +function getNoContentMessage(activeTab: any) { + let str = 'No recordings found'; + if (activeTab.type !== 'all') { + str += ' with ' + activeTab.name; + return str; + } + + return str + '!'; +} diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx new file mode 100644 index 000000000..5c5107310 --- /dev/null +++ b/frontend/app/components/shared/SessionListContainer/components/SessionHeader/SessionHeader.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { numberWithCommas } from 'App/utils'; +import { applyFilter } from 'Duck/search'; +import Period from 'Types/app/period'; +import SelectDateRange from 'Shared/SelectDateRange'; +import SessionTags from '../SessionTags'; +import { connect } from 'react-redux'; +import SessionSort from '../SessionSort'; + +interface Props { + listCount: number; + filter: any; + applyFilter: (filter: any) => void; +} +function SessionHeader(props: Props) { + const { listCount, filter: { startDate, endDate, rangeValue } } = props; + const period = Period({ start: startDate, end: endDate, rangeName: rangeValue }); + + const onDateChange = (e: any) => { + const dateValues = e.toJSON(); + props.applyFilter(dateValues); + }; + + return ( +
+
+
+ Sessions {listCount} +
+ +
+ +
+ +
+ +
+
+ ); +} + +export default connect( + (state: any) => ({ + filter: state.getIn(['search', 'instance']), + listCount: numberWithCommas(state.getIn(['sessions', 'total'])), + }), + { applyFilter } +)(SessionHeader); diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionHeader/index.ts b/frontend/app/components/shared/SessionListContainer/components/SessionHeader/index.ts new file mode 100644 index 000000000..ad3beb4fd --- /dev/null +++ b/frontend/app/components/shared/SessionListContainer/components/SessionHeader/index.ts @@ -0,0 +1 @@ +export { default } from './SessionHeader'; \ No newline at end of file diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx new file mode 100644 index 000000000..60d1aec6c --- /dev/null +++ b/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx @@ -0,0 +1,103 @@ +import React, { useEffect } from 'react'; +import { connect } from 'react-redux'; +import { FilterKey } from 'Types/filter/filterType'; +import SessionItem from 'Shared/SessionItem'; +import { NoContent, Loader, Pagination } from 'UI'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import NoContentMessage from '../NoContentMessage'; +import { fetchSessions, addFilterByKeyAndValue, updateCurrentPage, setScrollPosition } from 'Duck/search'; + +interface Props { + loading: boolean; + list: any; + currentPage: number; + total: number; + filters: any; + lastPlayedSessionId: string; + metaList: any; + scrollY: number; + addFilterByKeyAndValue: (key: string, value: any, operator?: string) => void; + updateCurrentPage: (page: number) => void; + setScrollPosition: (scrollPosition: number) => void; + fetchSessions: () => void; +} +function SessionList(props: Props) { + const { loading, list, currentPage, total, filters, lastPlayedSessionId, metaList } = props; + const _filterKeys = filters.map((i: any) => i.key); + const hasUserFilter = _filterKeys.includes(FilterKey.USERID) || _filterKeys.includes(FilterKey.USERANONYMOUSID); + + useEffect(() => { + const { scrollY } = props; + window.scrollTo(0, scrollY); + if (total === 0) { + props.fetchSessions() + } + + return () => { + props.setScrollPosition(window.scrollY); + }; + }, []); + + const onUserClick = (userId: any) => { + if (userId) { + props.addFilterByKeyAndValue(FilterKey.USERID, userId); + } else { + props.addFilterByKeyAndValue(FilterKey.USERID, '', 'isUndefined'); + } + }; + + return ( + + + +
+ +
+ } + subtext={
Please try changing your search parameters.
} + show={!loading && list.size === 0} + > + {list.map((session: any) => ( + + +
+ + ))} + + + {total > 0 && ( +
+ props.updateCurrentPage(page)} + limit={10} + debounceRequest={1000} + /> +
+ )} + + ); +} + +export default connect( + (state: any) => ({ + list: state.getIn(['sessions', 'list']), + filters: state.getIn(['search', 'instance', 'filters']), + lastPlayedSessionId: state.getIn(['sessions', 'lastPlayedSessionId']), + metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key), + loading: state.getIn(['sessions', 'loading']), + currentPage: state.getIn(['search', 'currentPage']) || 1, + total: state.getIn(['sessions', 'total']) || 0, + scrollY: state.getIn(['search', 'scrollY']), + }), + { updateCurrentPage, addFilterByKeyAndValue, setScrollPosition, fetchSessions } +)(SessionList); diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionList/index.ts b/frontend/app/components/shared/SessionListContainer/components/SessionList/index.ts new file mode 100644 index 000000000..779c9df2a --- /dev/null +++ b/frontend/app/components/shared/SessionListContainer/components/SessionList/index.ts @@ -0,0 +1 @@ +export { default } from './SessionList'; \ No newline at end of file diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionSort/SessionSort.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionSort/SessionSort.tsx new file mode 100644 index 000000000..01e770310 --- /dev/null +++ b/frontend/app/components/shared/SessionListContainer/components/SessionSort/SessionSort.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import Select from 'Shared/Select'; +import { sort } from 'Duck/sessions'; +import { applyFilter } from 'Duck/search'; + +const sortOptionsMap = { + 'startTs-desc': 'Newest', + 'startTs-asc': 'Oldest', + 'eventsCount-asc': 'Events Ascending', + 'eventsCount-desc': 'Events Descending', +}; + +const sortOptions = Object.entries(sortOptionsMap).map(([value, label]) => ({ value, label })); + +interface Props { + filter: any; + options: any; + applyFilter: (filter: any) => void; + sort: (sort: string, sign: number) => void; +} + +function SessionSort(props: Props) { + const { sort, order } = props.filter; + const onSort = ({ value }: any) => { + value = value.value; + const [sort, order] = value.split('-'); + const sign = order === 'desc' ? -1 : 1; + props.applyFilter({ order, sort }); + props.sort(sort, sign); + }; + + const defaultOption = `${sort}-${order}`; + return