From 7c9df8c196d821b11f1b9ee1575903b794b3cbfd Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 22 Feb 2022 18:08:21 +0100 Subject: [PATCH] feat(ui) - assist ui - wip --- frontend/app/Router.js | 9 +- .../app/components/BugFinder/BugFinder.js | 45 +----- .../LiveSessionList/LiveSessionList.tsx | 132 ------------------ .../BugFinder/LiveSessionList/index.js | 1 - .../BugFinder/SessionList/SessionList.js | 7 +- .../app/components/Header/SiteDropdown.js | 4 + .../components/Session_/PlayerBlockHeader.js | 20 ++- .../shared/DropdownPlain/DropdownPlain.css | 23 +++ .../shared/DropdownPlain/DropdownPlain.tsx | 29 ++++ .../components/shared/DropdownPlain/index.ts | 1 + .../shared/Filters/FilterItem/FilterItem.tsx | 2 +- .../LiveSessionList/LiveSessionList.tsx | 73 ++++++++-- .../SessionItem/ErrorBars/ErrorBars.tsx | 37 ++--- .../shared/SessionItem/SessionItem.js | 53 ++++--- .../SortOrderButton/SortOrderButton.tsx | 44 ++++++ .../shared/SortOrderButton/index.ts | 1 + .../components/ui/CountryFlag/CountryFlag.js | 2 +- frontend/app/duck/liveSearch.js | 12 +- frontend/app/duck/search.js | 5 +- frontend/app/types/filter/filter.js | 2 +- frontend/app/types/filter/newFilter.js | 2 +- frontend/app/types/session/session.js | 3 +- 22 files changed, 266 insertions(+), 241 deletions(-) delete mode 100644 frontend/app/components/BugFinder/LiveSessionList/LiveSessionList.tsx delete mode 100644 frontend/app/components/BugFinder/LiveSessionList/index.js create mode 100644 frontend/app/components/shared/DropdownPlain/DropdownPlain.css create mode 100644 frontend/app/components/shared/DropdownPlain/DropdownPlain.tsx create mode 100644 frontend/app/components/shared/DropdownPlain/index.ts create mode 100644 frontend/app/components/shared/SortOrderButton/SortOrderButton.tsx create mode 100644 frontend/app/components/shared/SortOrderButton/index.ts diff --git a/frontend/app/Router.js b/frontend/app/Router.js index d208c1baa..0c0e7433a 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -19,6 +19,7 @@ import Header from 'Components/Header/Header'; // import ResultsModal from 'Shared/Results/ResultsModal'; import FunnelDetails from 'Components/Funnels/FunnelDetails'; import FunnelIssueDetails from 'Components/Funnels/FunnelIssueDetails'; +import { fetchList as fetchIntegrationVariables } from 'Duck/customField'; import APIClient from './api_client'; import * as routes from './routes'; @@ -77,7 +78,7 @@ const ONBOARDING_REDIRECT_PATH = routes.onboarding(OB_DEFAULT_TAB); onboarding: state.getIn([ 'user', 'onboarding' ]) }; }, { - fetchUserInfo, fetchTenants, setSessionPath + fetchUserInfo, fetchTenants, setSessionPath, fetchIntegrationVariables }) class Router extends React.Component { state = { @@ -86,7 +87,11 @@ class Router extends React.Component { constructor(props) { super(props); if (props.isLoggedIn) { - Promise.all([props.fetchUserInfo()]) + Promise.all([ + props.fetchUserInfo().then(() => { + props.fetchIntegrationVariables() + }), + ]) // .then(() => this.onLoginLogout()); } props.fetchTenants(); diff --git a/frontend/app/components/BugFinder/BugFinder.js b/frontend/app/components/BugFinder/BugFinder.js index 0ebfa80bb..f0024c20a 100644 --- a/frontend/app/components/BugFinder/BugFinder.js +++ b/frontend/app/components/BugFinder/BugFinder.js @@ -14,7 +14,7 @@ import stl from './bugFinder.css'; import { fetchList as fetchSiteList } from 'Duck/site'; import withLocationHandlers from "HOCs/withLocationHandlers"; import { fetch as fetchFilterVariables } from 'Duck/sources'; -import { fetchList as fetchIntegrationVariables, fetchSources } from 'Duck/customField'; +import { fetchSources } from 'Duck/customField'; import { RehydrateSlidePanel } from './WatchDogs/components'; import { setActiveTab, setFunnelPage } from 'Duck/sessions'; import SessionsMenu from './SessionsMenu/SessionsMenu'; @@ -23,11 +23,8 @@ import { resetFunnel } from 'Duck/funnels'; import { resetFunnelFilters } from 'Duck/funnelFilters' import NoSessionsMessage from 'Shared/NoSessionsMessage'; import TrackerUpdateMessage from 'Shared/TrackerUpdateMessage'; -import LiveSessionList from './LiveSessionList' import SessionSearch from 'Shared/SessionSearch'; import MainSearchBar from 'Shared/MainSearchBar'; -import LiveSearchBar from 'Shared/LiveSearchBar'; -import LiveSessionSearch from 'Shared/LiveSessionSearch'; import { clearSearch, fetchSessions } from 'Duck/search'; const weakEqual = (val1, val2) => { @@ -54,7 +51,6 @@ const allowedQueryKeys = [ @withLocationHandlers() @connect(state => ({ filter: state.getIn([ 'filters', 'appliedFilter' ]), - showLive: state.getIn([ 'user', 'account', 'appearance', 'sessionsLive' ]), variables: state.getIn([ 'customFields', 'list' ]), sources: state.getIn([ 'customFields', 'sources' ]), filterValues: state.get('filterValues'), @@ -68,8 +64,7 @@ const allowedQueryKeys = [ fetchFavoriteSessionList, applyFilter, addAttribute, - fetchFilterVariables, - fetchIntegrationVariables, + fetchFilterVariables, fetchSources, clearEvents, setActiveTab, @@ -101,15 +96,6 @@ export default class BugFinder extends React.PureComponent { // keys: this.props.sources.filter(({type}) => type === 'logTool').map(({ label, key }) => ({ type: 'ERROR', source: key, label: label, key, icon: 'integrations/' + key, isFilter: false })).toJS() // }; // }); - // // TODO should cache the response - // props.fetchIntegrationVariables().then(() => { - // defaultFilters[5] = { - // category: 'Metadata', - // type: 'custom', - // keys: this.props.variables.map(({ key }) => ({ type: 'METADATA', key, label: key, icon: 'filters/metadata', isFilter: true })).toJS() - // }; - // }); - props.fetchSessions(); props.resetFunnel(); props.resetFunnelFilters(); @@ -172,28 +158,11 @@ export default class BugFinder extends React.PureComponent {
- - {/* Recorde Sessions */} - { activeTab.type !== 'live' && ( - <> -
- - -
- { activeTab.type !== 'live' && } - - )} - - {/* Live Sessions */} - { activeTab.type === 'live' && ( - <> -
- {/* */} - -
- { activeTab.type === 'live' && } - - )} +
+ + +
+
, - fetchLiveList: () => Promise, - applyFilter: () => void, - filters: any, - addAttribute: (obj) => void, - addFilterByKeyAndValue: (key: FilterKey, value: string) => void, - updateCurrentPage: (page: number) => void, - currentPage: number, -} - -function LiveSessionList(props: Props) { - const { loading, filters, list, currentPage } = props; - var timeoutId; - const hasUserFilter = filters.map(i => i.key).includes(KEYS.USERID); - const [sessions, setSessions] = React.useState(list); - - const displayedCount = Math.min(currentPage * PER_PAGE, sessions.size); - - const addPage = () => props.updateCurrentPage(props.currentPage + 1) - - useEffect(() => { - if (filters.size === 0) { - props.addFilterByKeyAndValue(FilterKey.USERID, ''); - } - }, []); - - useEffect(() => { - const filteredSessions = filters.size > 0 ? props.list.filter(session => { - let hasValidFilter = true; - filters.forEach(filter => { - if (!hasValidFilter) return; - - const _values = filter.value.filter(i => i !== '' && i !== null && i !== undefined).map(i => i.toLowerCase()); - if (filter.key === FilterKey.USERID) { - const _userId = session.userId ? session.userId.toLowerCase() : ''; - hasValidFilter = _values.length > 0 ? (_values.includes(_userId) && hasValidFilter) || _values.some(i => _userId.includes(i)) : hasValidFilter; - } - if (filter.category === FilterCategory.METADATA) { - const _source = session.metadata[filter.key] ? session.metadata[filter.key].toLowerCase() : ''; - hasValidFilter = _values.length > 0 ? (_values.includes(_source) && hasValidFilter) || _values.some(i => _source.includes(i)) : hasValidFilter; - } - }) - return hasValidFilter; - }) : props.list; - setSessions(filteredSessions); - }, [filters, list]); - - useEffect(() => { - props.fetchLiveList(); - timeout(); - return () => { - clearTimeout(timeoutId) - } - }, []) - - const onUserClick = (userId, userAnonymousId) => { - if (userId) { - props.addFilterByKeyAndValue(FilterKey.USERID, userId); - } else { - props.addFilterByKeyAndValue(FilterKey.USERANONYMOUSID, userAnonymousId); - } - } - - const timeout = () => { - timeoutId = setTimeout(() => { - props.fetchLiveList(); - timeout(); - }, AUTOREFRESH_INTERVAL); - } - - return ( -
- - See how to {'enable Assist'} and ensure you're using tracker-assist v3.5.0 or higher. - - } - image={} - show={ !loading && sessions && sessions.size === 0} - > - - {sessions && sessions.take(displayedCount).map(session => ( - - ))} - - - - -
- ) -} - -export default withPermissions(['ASSIST_LIVE', 'SESSION_REPLAY'])(connect( - (state) => ({ - list: state.getIn(['sessions', 'liveSessions']), - loading: state.getIn([ 'sessions', 'loading' ]), - filters: state.getIn([ 'liveSearch', 'instance', 'filters' ]), - currentPage: state.getIn(["liveSearch", "currentPage"]), - }), - { fetchLiveList, applyFilter, addAttribute, addFilterByKeyAndValue, updateCurrentPage } -)(LiveSessionList)); diff --git a/frontend/app/components/BugFinder/LiveSessionList/index.js b/frontend/app/components/BugFinder/LiveSessionList/index.js deleted file mode 100644 index eb38fa3e7..000000000 --- a/frontend/app/components/BugFinder/LiveSessionList/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './LiveSessionList' \ No newline at end of file diff --git a/frontend/app/components/BugFinder/SessionList/SessionList.js b/frontend/app/components/BugFinder/SessionList/SessionList.js index b2267e908..10db59c5b 100644 --- a/frontend/app/components/BugFinder/SessionList/SessionList.js +++ b/frontend/app/components/BugFinder/SessionList/SessionList.js @@ -19,6 +19,7 @@ var timeoutId; allList: state.getIn([ 'sessions', 'list' ]), total: state.getIn([ 'sessions', 'total' ]), filters: state.getIn([ 'search', 'instance', 'filters' ]), + metaList: state.getIn(['customFields', 'list']).map(i => i.key), }), { applyFilter, addAttribute, @@ -47,7 +48,7 @@ export default class SessionList extends React.PureComponent { if (userId) { this.props.addFilterByKeyAndValue(FilterKey.USERID, userId); } else { - this.props.addFilterByKeyAndValue(FilterKey.USERANONYMOUSID, userAnonymousId); + this.props.addFilterByKeyAndValue(FilterKey.USERID, '', 'isUndefined'); } } @@ -81,7 +82,8 @@ export default class SessionList extends React.PureComponent { filters, onMenuItemClick, allList, - activeTab + activeTab, + metaList, } = this.props; const _filterKeys = filters.map(i => i.key); const hasUserFilter = _filterKeys.includes(FilterKey.USERID) || _filterKeys.includes(FilterKey.USERANONYMOUSID); @@ -118,6 +120,7 @@ export default class SessionList extends React.PureComponent { session={ session } hasUserFilter={hasUserFilter} onUserClick={this.onUserClick} + metaList={metaList} /> ))} diff --git a/frontend/app/components/Header/SiteDropdown.js b/frontend/app/components/Header/SiteDropdown.js index 502b40085..55259c088 100644 --- a/frontend/app/components/Header/SiteDropdown.js +++ b/frontend/app/components/Header/SiteDropdown.js @@ -27,6 +27,10 @@ import { fetchList as fetchIntegrationVariables } from 'Duck/customField'; export default class SiteDropdown extends React.PureComponent { state = { showProductModal: false } + componentDidMount() { + this.props.fetchIntegrationVariables(); + } + closeModal = (e, newSite) => { this.setState({ showProductModal: false }) }; diff --git a/frontend/app/components/Session_/PlayerBlockHeader.js b/frontend/app/components/Session_/PlayerBlockHeader.js index 631a3a3c8..93483d961 100644 --- a/frontend/app/components/Session_/PlayerBlockHeader.js +++ b/frontend/app/components/Session_/PlayerBlockHeader.js @@ -11,6 +11,7 @@ import HeaderInfo from './HeaderInfo'; import SharePopup from '../shared/SharePopup/SharePopup'; import { fetchList as fetchListIntegration } from 'Duck/integrations/actions'; import { countries } from 'App/constants'; +import SessionMetaList from 'Shared/SessionItem/SessionMetaList'; import stl from './playerBlockHeader.css'; import Issues from './Issues/Issues'; @@ -44,6 +45,7 @@ function capitalise(str) { funnelRef: state.getIn(['funnels', 'navRef']), siteId: state.getIn([ 'user', 'siteId' ]), hasSessionsPath: hasSessioPath && !isAssist, + metaList: state.getIn(['customFields', 'list']).map(i => i.key), } }, { toggleFavorite, fetchListIntegration, setSessionPath @@ -94,6 +96,7 @@ export default class PlayerBlockHeader extends React.PureComponent { userBrowserVersion, userDeviceType, live, + metadata, }, loading, // live, @@ -102,8 +105,14 @@ export default class PlayerBlockHeader extends React.PureComponent { fullscreen, hasSessionsPath, sessionPath, + metaList, } = this.props; const _live = live && !hasSessionsPath; + console.log('metaList', metaList); + const _metaList = Object.keys(metadata).filter(i => metaList.includes(i)).map(key => { + const value = metadata[key]; + return { label: key, value }; + }); return (
@@ -127,10 +136,15 @@ export default class PlayerBlockHeader extends React.PureComponent {
{ live && hasSessionsPath && ( -
this.props.setSessionPath('')}> - This Session is Now Continuing Live -
+ <> +
this.props.setSessionPath('')}> + This Session is Now Continuing Live +
+
+ )} + +
void; + icon?: string; + direction?: string; + value: any; +} + +export default function DropdownPlain(props: Props) { + const { value, options, icon = "chevron-down", direction = "left" } = props; + return ( +
+ : null } + /> +
+ ) +} diff --git a/frontend/app/components/shared/DropdownPlain/index.ts b/frontend/app/components/shared/DropdownPlain/index.ts new file mode 100644 index 000000000..3b2d43dcf --- /dev/null +++ b/frontend/app/components/shared/DropdownPlain/index.ts @@ -0,0 +1 @@ +export { default } from './DropdownPlain'; \ No newline at end of file diff --git a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx index a8760428b..db0bedf32 100644 --- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx +++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx @@ -14,7 +14,7 @@ interface Props { } function FilterItem(props: Props) { const { isFilter = false, filterIndex, filter } = props; - const canShowValues = !(filter.operator === "isAny" || filter.operator === "onAny"); + const canShowValues = !(filter.operator === "isAny" || filter.operator === "onAny" || filter.operator === "isUndefined"); const replaceFilter = (filter) => { props.onUpdate({ ...filter, value: [""]}); diff --git a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx index 9fd3f8e0e..3a4fcc275 100644 --- a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx +++ b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx @@ -8,7 +8,11 @@ import withPermissions from 'HOCs/withPermissions' import { KEYS } from 'Types/filter/customFilter'; import { applyFilter, addAttribute } from 'Duck/filters'; import { FilterCategory, FilterKey } from 'App/types/filter/filterType'; -import { addFilterByKeyAndValue, updateCurrentPage } from 'Duck/liveSearch'; +import { addFilterByKeyAndValue, updateCurrentPage, toggleSortOrder } from 'Duck/liveSearch'; +import DropdownPlain from 'Shared/DropdownPlain'; +import SortOrderButton from 'Shared/SortOrderButton'; +import { TimezoneDropdown } from 'UI'; +import { capitalize } from 'App/utils'; const AUTOREFRESH_INTERVAL = .5 * 60 * 1000 const PER_PAGE = 20; @@ -23,14 +27,21 @@ interface Props { addFilterByKeyAndValue: (key: FilterKey, value: string) => void, updateCurrentPage: (page: number) => void, currentPage: number, + metaList: any, + sortOrder: string, + toggleSortOrder: (sortOrder: string) => void, } function LiveSessionList(props: Props) { - const { loading, filters, list, currentPage } = props; + const { loading, filters, list, currentPage, metaList = [], sortOrder } = props; var timeoutId; const hasUserFilter = filters.map(i => i.key).includes(KEYS.USERID); const [sessions, setSessions] = React.useState(list); - + const sortOptions = metaList.map(i => ({ + text: capitalize(i), value: i + })).toJS(); + + const [sortBy, setSortBy] = React.useState(''); const displayedCount = Math.min(currentPage * PER_PAGE, sessions.size); const addPage = () => props.updateCurrentPage(props.currentPage + 1) @@ -41,6 +52,12 @@ function LiveSessionList(props: Props) { } }, []); + useEffect(() => { + if (metaList.size === 0 || !!sortBy) return; + + setSortBy(sortOptions[0] && sortOptions[0].value) + }, [metaList]); + useEffect(() => { const filteredSessions = filters.size > 0 ? props.list.filter(session => { let hasValidFilter = true; @@ -78,6 +95,10 @@ function LiveSessionList(props: Props) { } } + const onSortChange = (e, { value }) => { + setSortBy(value); + } + const timeout = () => { timeoutId = setTimeout(() => { props.fetchLiveList(); @@ -87,6 +108,24 @@ function LiveSessionList(props: Props) { return (
+
+
+
+
+ Timezone + +
+
+ Sort By + +
+ +
+
- {sessions && sessions.take(displayedCount).map(session => ( + {sessions && sessions.sortBy(i => i.metadata[sortBy]).update(list => { + return sortOrder === 'desc' ? list.reverse() : list; + }).take(displayedCount).map(session => ( ))} - +
@@ -127,6 +169,15 @@ export default withPermissions(['ASSIST_LIVE', 'SESSION_REPLAY'])(connect( loading: state.getIn([ 'sessions', 'loading' ]), filters: state.getIn([ 'liveSearch', 'instance', 'filters' ]), currentPage: state.getIn(["liveSearch", "currentPage"]), + metaList: state.getIn(['customFields', 'list']).map(i => i.key), + sortOrder: state.getIn(['liveSearch', 'sortOrder']), }), - { fetchLiveList, applyFilter, addAttribute, addFilterByKeyAndValue, updateCurrentPage } + { + fetchLiveList, + applyFilter, + addAttribute, + addFilterByKeyAndValue, + updateCurrentPage, + toggleSortOrder, + } )(LiveSessionList)); diff --git a/frontend/app/components/shared/SessionItem/ErrorBars/ErrorBars.tsx b/frontend/app/components/shared/SessionItem/ErrorBars/ErrorBars.tsx index 31c233414..3b49db9c6 100644 --- a/frontend/app/components/shared/SessionItem/ErrorBars/ErrorBars.tsx +++ b/frontend/app/components/shared/SessionItem/ErrorBars/ErrorBars.tsx @@ -3,8 +3,8 @@ import cn from 'classnames' import stl from './ErrorBars.css' const GOOD = 'Good' -const LESS_CRITICAL = 'Less Critical' -const CRITICAL = 'Critical' +const LESS_CRITICAL = 'Few Issues' +const CRITICAL = 'Many Issues' const getErrorState = (count: number) => { if (count === 0) { return GOOD } if (count < 2) { return LESS_CRITICAL } @@ -18,20 +18,25 @@ interface Props { export default function ErrorBars(props: Props) { const { count = 2 } = props const state = React.useMemo(() => getErrorState(count), [count]) - const showSecondBar = (state === GOOD || state === LESS_CRITICAL || state === CRITICAL) - const showThirdBar = (state === GOOD || state === CRITICAL); - const bgColor = { 'bg-red' : state === CRITICAL, 'bg-green' : state === GOOD, 'bg-red2' : state === LESS_CRITICAL } - return ( -
-
-
- { showSecondBar &&
} - { showThirdBar &&
} -
-
-
-
-
+ const isGood = state === GOOD + const showFirstBar = (state === LESS_CRITICAL || state === CRITICAL) + const showSecondBar = (state === CRITICAL) + // const showThirdBar = (state === GOOD || state === CRITICAL); + // const bgColor = { 'bg-red' : state === CRITICAL, 'bg-red2' : state === LESS_CRITICAL } + const bgColor = 'bg-red2' + return isGood ? <> : ( +
+
+
+ { showFirstBar &&
} + { showSecondBar &&
} + {/* { showThirdBar &&
} */} +
+
+
+
+ {/*
*/} +
{state}
diff --git a/frontend/app/components/shared/SessionItem/SessionItem.js b/frontend/app/components/shared/SessionItem/SessionItem.js index 4956342f0..245dcb58b 100644 --- a/frontend/app/components/shared/SessionItem/SessionItem.js +++ b/frontend/app/components/shared/SessionItem/SessionItem.js @@ -56,24 +56,23 @@ export default class SessionItem extends React.PureComponent { userNumericHash, live, metadata, + userSessionsCount, }, timezone, onUserClick = () => null, hasUserFilter = false, - disableUser = false + disableUser = false, + metaList = [], } = this.props; const formattedDuration = durationFormatted(duration); const hasUserId = userId || userAnonymousId; const isAssist = isRoute(ASSIST_ROUTE, this.props.location.pathname); - console.log('metadata', metadata); - const _metaList = Object.keys(metadata).map(key => { + const _metaList = Object.keys(metadata).filter(i => metaList.includes(i)).map(key => { const value = metadata[key]; return { label: key, value }; }); - console.log('SessionItem', _metaList); - return (
@@ -84,15 +83,15 @@ export default class SessionItem extends React.PureComponent {
(!disableUser && !hasUserFilter && hasUserId) && onUserClick(userId, userAnonymousId)} + onClick={() => (!disableUser && !hasUserFilter) && onUserClick(userId, userAnonymousId)} > {userDisplayName}
(!disableUser && !hasUserFilter && hasUserId) && onUserClick(userId, userAnonymousId)} + onClick={() => (!disableUser && !hasUserFilter) && onUserClick(userId, userAnonymousId)} > - 30 Sessions + {userSessionsCount} Sessions
@@ -111,23 +110,21 @@ export default class SessionItem extends React.PureComponent {
{ live ? : formattedDuration }
-
- {/*
*/} - -
- - - -
·
- - - -
·
- - - -
- {/*
*/} +
+ +
+ + + +
·
+ + + +
·
+ + + +
{ !isAssist && (
@@ -139,14 +136,12 @@ export default class SessionItem extends React.PureComponent {
- +
- { isAssist && ( - - )} +
); } diff --git a/frontend/app/components/shared/SortOrderButton/SortOrderButton.tsx b/frontend/app/components/shared/SortOrderButton/SortOrderButton.tsx new file mode 100644 index 000000000..6c730b4e8 --- /dev/null +++ b/frontend/app/components/shared/SortOrderButton/SortOrderButton.tsx @@ -0,0 +1,44 @@ +import React from 'react' +import { Icon, Popup } from 'UI' +import cn from 'classnames' + +interface Props { + sortOrder: string, + onChange?: (sortOrder: string) => void, +} +export default React.memo(function SortOrderButton(props: Props) { + const { sortOrder, onChange = () => null } = props + const isAscending = sortOrder === 'asc' + + return ( +
+ onChange('asc')} + > + +
+ } + content={'Ascending'} + /> + + onChange('desc')} + > + +
+ } + content={'Descending'} + /> +
+ ) +}) diff --git a/frontend/app/components/shared/SortOrderButton/index.ts b/frontend/app/components/shared/SortOrderButton/index.ts new file mode 100644 index 000000000..dedc48a43 --- /dev/null +++ b/frontend/app/components/shared/SortOrderButton/index.ts @@ -0,0 +1 @@ +export { default } from './SortOrderButton'; \ No newline at end of file diff --git a/frontend/app/components/ui/CountryFlag/CountryFlag.js b/frontend/app/components/ui/CountryFlag/CountryFlag.js index 6bcc6672b..f817cf65f 100644 --- a/frontend/app/components/ui/CountryFlag/CountryFlag.js +++ b/frontend/app/components/ui/CountryFlag/CountryFlag.js @@ -13,7 +13,7 @@ const CountryFlag = React.memo(({ country, className, style = {}, label = false - : + : // :
{ "N/A" }
} content={ countryName } diff --git a/frontend/app/duck/liveSearch.js b/frontend/app/duck/liveSearch.js index bebdc9a35..5c9364e96 100644 --- a/frontend/app/duck/liveSearch.js +++ b/frontend/app/duck/liveSearch.js @@ -15,21 +15,24 @@ const EDIT = editType(name); const CLEAR_SEARCH = `${name}/CLEAR_SEARCH`; const APPLY = `${name}/APPLY`; const UPDATE_CURRENT_PAGE = `${name}/UPDATE_CURRENT_PAGE`; +const TOGGLE_SORT_ORDER = `${name}/TOGGLE_SORT_ORDER`; const initialState = Map({ list: List(), instance: new Filter({ filters: [] }), filterSearchList: {}, currentPage: 1, + sortOrder: 'asc', }); - function reducer(state = initialState, action = {}) { switch (action.type) { case EDIT: return state.mergeIn(['instance'], action.instance); case UPDATE_CURRENT_PAGE: return state.set('currentPage', action.page); + case TOGGLE_SORT_ORDER: + return state.set('sortOrder', action.order); } return state; } @@ -98,4 +101,11 @@ export function updateCurrentPage(page) { type: UPDATE_CURRENT_PAGE, page, }; +} + +export function toggleSortOrder (order) { + return { + type: TOGGLE_SORT_ORDER, + order, + }; } \ No newline at end of file diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index 8b4ab8e12..ad4ea944c 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -243,9 +243,12 @@ export const addFilter = (filter) => (dispatch, getState) => { } } -export const addFilterByKeyAndValue = (key, value) => (dispatch, getState) => { +export const addFilterByKeyAndValue = (key, value, operator = undefined) => (dispatch, getState) => { let defaultFilter = filtersMap[key]; defaultFilter.value = value; + if (operator) { + defaultFilter.operator = operator; + } dispatch(addFilter(defaultFilter)); } diff --git a/frontend/app/types/filter/filter.js b/frontend/app/types/filter/filter.js index df31f1d0e..6d3b177f9 100644 --- a/frontend/app/types/filter/filter.js +++ b/frontend/app/types/filter/filter.js @@ -34,7 +34,7 @@ export default Record({ rangeValue, startDate, endDate, - // groupByUser: true, + groupByUser: true, sort: 'startTs', order: 'desc', diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index d2125acdb..d4cb905a1 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -44,7 +44,7 @@ export const filtersMap = { [FilterKey.DURATION]: { key: FilterKey.DURATION, type: FilterType.DURATION, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'Duration', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is']), icon: 'filters/duration' }, [FilterKey.USER_COUNTRY]: { key: FilterKey.USER_COUNTRY, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'User Country', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/country', options: countryOptions }, // [FilterKey.CONSOLE]: { key: FilterKey.CONSOLE, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Console', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/console' }, - [FilterKey.USERID]: { key: FilterKey.USERID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User Id', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' }, + [FilterKey.USERID]: { key: FilterKey.USERID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User Id', operator: 'is', operatorOptions: filterOptions.stringOperators.concat([{ text: 'is undefined', value: 'isUndefined'}]), icon: 'filters/userid' }, [FilterKey.USERANONYMOUSID]: { key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User AnonymousId', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' }, // PERFORMANCE diff --git a/frontend/app/types/session/session.js b/frontend/app/types/session/session.js index 44dce3ab0..5eda0c987 100644 --- a/frontend/app/types/session/session.js +++ b/frontend/app/types/session/session.js @@ -36,7 +36,7 @@ export default Record({ stackEvents: List(), resources: List(), missedResources: List(), - metadata: List(), + metadata: Map(), favorite: false, filterId: '', messagesUrl: '', @@ -76,6 +76,7 @@ export default Record({ socket: null, isIOS: false, revId: '', + userSessionsCount: 0, }, { fromJS:({ startTs=0,