feat(ui) - assist ui - wip
This commit is contained in:
parent
df240bc7c4
commit
40b88446d1
16 changed files with 318 additions and 71 deletions
|
|
@ -11,6 +11,7 @@ import UpdatePassword from 'Components/UpdatePassword/UpdatePassword';
|
|||
import ClientPure from 'Components/Client/Client';
|
||||
import OnboardingPure from 'Components/Onboarding/Onboarding';
|
||||
import SessionPure from 'Components/Session/Session';
|
||||
import AssistPure from 'Components/Assist';
|
||||
import BugFinderPure from 'Components/BugFinder/BugFinder';
|
||||
import DashboardPure from 'Components/Dashboard/Dashboard';
|
||||
import ErrorsPure from 'Components/Errors/Errors';
|
||||
|
|
@ -29,6 +30,7 @@ import { setSessionPath } from 'Duck/sessions';
|
|||
const BugFinder = withSiteIdUpdater(BugFinderPure);
|
||||
const Dashboard = withSiteIdUpdater(DashboardPure);
|
||||
const Session = withSiteIdUpdater(SessionPure);
|
||||
const Assist = withSiteIdUpdater(AssistPure);
|
||||
const Client = withSiteIdUpdater(ClientPure);
|
||||
const Onboarding = withSiteIdUpdater(OnboardingPure);
|
||||
const Errors = withSiteIdUpdater(ErrorsPure);
|
||||
|
|
@ -39,6 +41,7 @@ const withObTab = routes.withObTab;
|
|||
|
||||
const DASHBOARD_PATH = routes.dashboard();
|
||||
const SESSIONS_PATH = routes.sessions();
|
||||
const ASSIST_PATH = routes.assist();
|
||||
const ERRORS_PATH = routes.errors();
|
||||
const ERROR_PATH = routes.error();
|
||||
const FUNNEL_PATH = routes.funnel();
|
||||
|
|
@ -145,6 +148,7 @@ class Router extends React.Component {
|
|||
<Redirect to={ routes.client(routes.CLIENT_TABS.SITES) } />
|
||||
}
|
||||
<Route exact strict path={ withSiteId(DASHBOARD_PATH, siteIdList) } component={ Dashboard } />
|
||||
<Route exact strict path={ withSiteId(ASSIST_PATH, siteIdList) } component={ Assist } />
|
||||
<Route exact strict path={ withSiteId(ERRORS_PATH, siteIdList) } component={ Errors } />
|
||||
<Route exact strict path={ withSiteId(ERROR_PATH, siteIdList) } component={ Errors } />
|
||||
<Route exact strict path={ withSiteId(FUNNEL_PATH, siteIdList) } component={ Funnels } />
|
||||
|
|
|
|||
|
|
@ -1,11 +1,20 @@
|
|||
import React from 'react';
|
||||
import ChatWindow from './ChatWindow';
|
||||
|
||||
import LiveSessionList from 'Shared/LiveSessionList';
|
||||
import LiveSessionSearch from 'Shared/LiveSessionSearch';
|
||||
import cn from 'classnames'
|
||||
|
||||
export default function Assist() {
|
||||
return (
|
||||
<div className="absolute">
|
||||
{/* <ChatWindow /> */}
|
||||
<div className="page-margin container-90 flex relative">
|
||||
<div className="flex-1 flex">
|
||||
{/* <div className="side-menu">
|
||||
</div> */}
|
||||
<div className={cn("w-full mx-auto")} style={{ maxWidth: '1300px'}}>
|
||||
<LiveSessionSearch />
|
||||
<div className="my-4" />
|
||||
<LiveSessionList />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ function SessionsMenu(props) {
|
|||
/>
|
||||
))}
|
||||
|
||||
<div className={stl.divider} />
|
||||
{/* <div className={stl.divider} />
|
||||
<div className="my-3">
|
||||
<SideMenuitem
|
||||
title={
|
||||
|
|
@ -94,7 +94,7 @@ function SessionsMenu(props) {
|
|||
onClick={() => onMenuItemClick({ name: 'Assist', type: 'live' })}
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className={stl.divider} />
|
||||
<div className="my-3">
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { NavLink, withRouter } from 'react-router-dom';
|
|||
import cn from 'classnames';
|
||||
import {
|
||||
sessions,
|
||||
assist,
|
||||
client,
|
||||
errors,
|
||||
dashboard,
|
||||
|
|
@ -27,6 +28,7 @@ import Alerts from '../Alerts/Alerts';
|
|||
|
||||
const DASHBOARD_PATH = dashboard();
|
||||
const SESSIONS_PATH = sessions();
|
||||
const ASSIST_PATH = assist();
|
||||
const ERRORS_PATH = errors();
|
||||
const CLIENT_PATH = client(CLIENT_DEFAULT_TAB);
|
||||
const AUTOREFRESH_INTERVAL = 30 * 1000;
|
||||
|
|
@ -86,6 +88,13 @@ const Header = (props) => {
|
|||
>
|
||||
{ 'Sessions' }
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to={ withSiteId(ASSIST_PATH, siteId) }
|
||||
className={ styles.nav }
|
||||
activeClassName={ styles.active }
|
||||
>
|
||||
{ 'Assist' }
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to={ withSiteId(ERRORS_PATH, siteId) }
|
||||
className={ styles.nav }
|
||||
|
|
|
|||
|
|
@ -9,9 +9,7 @@ import {
|
|||
init as initPlayer,
|
||||
clean as cleanPlayer,
|
||||
} from 'Player';
|
||||
import withPermissions from 'HOCs/withPermissions'
|
||||
import Assist from 'Components/Assist'
|
||||
|
||||
import withPermissions from 'HOCs/withPermissions';
|
||||
|
||||
import PlayerBlockHeader from '../Session_/PlayerBlockHeader';
|
||||
import EventsBlock from '../Session_/EventsBlock';
|
||||
|
|
@ -54,7 +52,6 @@ function WebPlayer ({ showAssist, session, toggleFullscreen, closeBottomBlock, l
|
|||
return (
|
||||
<PlayerProvider>
|
||||
<InitLoader className="flex-1 p-3">
|
||||
{ showAssist && <Assist session={session} /> }
|
||||
<PlayerBlockHeader fullscreen={fullscreen}/>
|
||||
<div className={ styles.session } data-fullscreen={fullscreen}>
|
||||
<PlayerBlock />
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import LiveFilterModal from '../LiveFilterModal';
|
|||
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
|
||||
import { Icon } from 'UI';
|
||||
import { connect } from 'react-redux';
|
||||
import { dashboard as dashboardRoute, isRoute } from "App/routes";
|
||||
import { assist as assistRoute, isRoute } from "App/routes";
|
||||
|
||||
const DASHBOARD_ROUTE = dashboardRoute();
|
||||
const ASSIST_ROUTE = assistRoute();
|
||||
|
||||
interface Props {
|
||||
filter?: any; // event/filter
|
||||
|
|
@ -43,7 +43,7 @@ function FilterSelection(props: Props) {
|
|||
</OutsideClickDetectingDiv>
|
||||
{showModal && (
|
||||
<div className="absolute left-0 top-20 border shadow rounded bg-white z-50">
|
||||
{ (isLive && !isRoute(DASHBOARD_ROUTE, window.location.pathname)) ? <LiveFilterModal onFilterClick={onFilterClick} /> : <FilterModal onFilterClick={onFilterClick} /> }
|
||||
{ isRoute(ASSIST_ROUTE, window.location.pathname) ? <LiveFilterModal onFilterClick={onFilterClick} /> : <FilterModal onFilterClick={onFilterClick} /> }
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { fetchLiveList } from 'Duck/sessions';
|
||||
import { connect } from 'react-redux';
|
||||
import { NoContent, Loader, LoadMoreButton } from 'UI';
|
||||
import { List, Map } from 'immutable';
|
||||
import SessionItem from 'Shared/SessionItem';
|
||||
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';
|
||||
|
||||
const AUTOREFRESH_INTERVAL = .5 * 60 * 1000
|
||||
const PER_PAGE = 20;
|
||||
|
||||
interface Props {
|
||||
loading: Boolean,
|
||||
list: List<any>,
|
||||
fetchLiveList: () => Promise<void>,
|
||||
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 (
|
||||
<div>
|
||||
<NoContent
|
||||
title={"No live sessions."}
|
||||
subtext={
|
||||
<span>
|
||||
See how to <a target="_blank" className="link" href="https://docs.openreplay.com/plugins/assist">{'enable Assist'}</a> and ensure you're using tracker-assist <span className="font-medium">v3.5.0</span> or higher.
|
||||
</span>
|
||||
}
|
||||
image={<img src="/img/live-sessions.png"
|
||||
style={{ width: '70%', marginBottom: '30px' }}/>}
|
||||
show={ !loading && sessions && sessions.size === 0}
|
||||
>
|
||||
<Loader loading={ loading }>
|
||||
{sessions && sessions.take(displayedCount).map(session => (
|
||||
<SessionItem
|
||||
key={ session.sessionId }
|
||||
session={ session }
|
||||
live
|
||||
hasUserFilter={hasUserFilter}
|
||||
onUserClick={onUserClick}
|
||||
/>
|
||||
))}
|
||||
|
||||
<LoadMoreButton
|
||||
className="mt-3"
|
||||
displayedCount={displayedCount}
|
||||
totalCount={sessions.size}
|
||||
onClick={addPage}
|
||||
/>
|
||||
</Loader>
|
||||
</NoContent>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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));
|
||||
1
frontend/app/components/shared/LiveSessionList/index.js
Normal file
1
frontend/app/components/shared/LiveSessionList/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from './LiveSessionList'
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react'
|
||||
import cn from 'classnames'
|
||||
|
||||
const GOOD = 'Good'
|
||||
const LESS_CRITICAL = 'Less Critical'
|
||||
const CRITICAL = 'Critical'
|
||||
const getErrorState = (count: number) => {
|
||||
if (count === 0) { return GOOD }
|
||||
if (count < 2) { return LESS_CRITICAL }
|
||||
return CRITICAL
|
||||
}
|
||||
|
||||
|
||||
interface Props {
|
||||
count?: number
|
||||
}
|
||||
export default function ErrorBars(props: Props) {
|
||||
const { count = 2 } = props
|
||||
const state = React.useCallback(() => getErrorState(count), [count])()
|
||||
const bgColor = { 'bg-red' : state === CRITICAL, 'bg-green' : state === GOOD, 'bg-red2' : state === LESS_CRITICAL }
|
||||
return (
|
||||
<div className="relative" style={{ width: '80px' }}>
|
||||
<div className="grid grid-cols-3 gap-1 absolute inset-0" style={{ opacity: '1'}}>
|
||||
<div className={cn("h-1 rounded-tl rounded-bl", bgColor)}></div>
|
||||
{ (state === GOOD || state === LESS_CRITICAL || state === CRITICAL) && <div className={cn("h-1 rounded-tl rounded-bl", bgColor)}></div> }
|
||||
{ (state === GOOD || state === CRITICAL) && <div className={cn("h-1 rounded-tl rounded-bl", bgColor)}></div> }
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-1" style={{ opacity: '0.3'}}>
|
||||
<div className={cn("h-1 rounded-tl rounded-bl", bgColor)}></div>
|
||||
<div className={cn("h-1", bgColor)}></div>
|
||||
<div className={cn("h-1 rounded-tr rounded-br", bgColor)}></div>
|
||||
</div>
|
||||
<div className="mt-2">{state}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './ErrorBars';
|
||||
|
|
@ -18,6 +18,8 @@ import LiveTag from 'Shared/LiveTag';
|
|||
import Bookmark from 'Shared/Bookmark';
|
||||
import Counter from './Counter'
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import SessionMetaList from './SessionMetaList';
|
||||
import ErrorBars from './ErrorBars';
|
||||
|
||||
const Label = ({ label = '', color = 'color-gray-medium'}) => (
|
||||
<div className={ cn('font-light text-sm', color)}>{label}</div>
|
||||
|
|
@ -61,64 +63,69 @@ export default class SessionItem extends React.PureComponent {
|
|||
const hasUserId = userId || userAnonymousId;
|
||||
|
||||
return (
|
||||
<div className={ stl.sessionItem } id="session-item" >
|
||||
<div className={ cn('flex items-center mr-auto')}>
|
||||
<div className="flex items-center mr-6" style={{ width: '200px' }}>
|
||||
<Avatar seed={ userNumericHash } />
|
||||
<div className="flex flex-col ml-3 overflow-hidden">
|
||||
<div
|
||||
className={cn({'color-teal cursor-pointer': !disableUser && hasUserId, 'color-gray-medium' : disableUser || !hasUserId})}
|
||||
onClick={() => (!disableUser && !hasUserFilter && hasUserId) && onUserClick(userId, userAnonymousId)}
|
||||
>
|
||||
<TextEllipsis text={ userDisplayName } noHint />
|
||||
<div className={ cn(stl.sessionItem, "flex flex-col bg-white p-3 mb-3") } id="session-item" >
|
||||
<div className="flex items-start">
|
||||
<div className={ cn('flex items-center w-full')}>
|
||||
<div className="flex items-center" style={{ width: "40%"}}>
|
||||
<div><Avatar seed={ userNumericHash } /></div>
|
||||
<div className="flex flex-col overflow-hidden color-gray-medium ml-3">
|
||||
<div
|
||||
className={cn({'color-teal cursor-pointer': !disableUser && hasUserId, 'color-gray-medium' : disableUser || !hasUserId})}
|
||||
onClick={() => (!disableUser && !hasUserFilter && hasUserId) && onUserClick(userId, userAnonymousId)}
|
||||
>
|
||||
{userDisplayName}
|
||||
</div>
|
||||
<div className="color-gray-medium">30 Sessions</div>
|
||||
</div>
|
||||
<Label label={ formatTimeOrDate(startedAt, timezone) } />
|
||||
</div>
|
||||
<div style={{ width: "20%"}}>
|
||||
<div>{formatTimeOrDate(startedAt, timezone) }</div>
|
||||
<div className="flex items-center color-gray-medium">
|
||||
{!live && (
|
||||
<div className="color-gray-medium">
|
||||
<span className="mr-1">{ eventsCount }</span>
|
||||
<span>{ eventsCount === 0 || eventsCount > 1 ? 'Events' : 'Event' }</span>
|
||||
</div>
|
||||
)}
|
||||
<span className="mx-1">-</span>
|
||||
<div>{ live ? <Counter startTime={startedAt} /> : formattedDuration }</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ width: "20%"}}>
|
||||
<div className="">
|
||||
<CountryFlag country={ userCountry } className="mr-6" />
|
||||
<div className="color-gray-medium">
|
||||
<span>{userBrowser}</span> -
|
||||
<span>{userOs}</span> -
|
||||
<span>{userDeviceType}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ width: "10%"}} className="self-center">
|
||||
<ErrorBars count={errorsCount} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={ cn(stl.iconStack, 'flex-1') }>
|
||||
<div className={ stl.icons }>
|
||||
<CountryFlag country={ userCountry } className="mr-6" />
|
||||
<BrowserIcon browser={ userBrowser } size="16" className="mr-6" />
|
||||
<OsIcon os={ userOs } size="16" className="mr-6" />
|
||||
<Icon name={ deviceTypeIcon(userDeviceType) } size="16" className="mr-6" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center px-4" style={{ width: '150px'}}>
|
||||
<div className="text-xl">
|
||||
{ live ? <Counter startTime={startedAt} /> : formattedDuration }
|
||||
</div>
|
||||
<Label label="Duration" />
|
||||
</div>
|
||||
|
||||
{!live && (
|
||||
<div className="flex flex-col items-center px-4">
|
||||
<div className={ stl.count }>{ eventsCount }</div>
|
||||
<Label label={ eventsCount === 0 || eventsCount > 1 ? 'Events' : 'Event' } />
|
||||
<div className="flex items-center">
|
||||
{/* { live && <LiveTag isLive={true} /> } */}
|
||||
<div className={ cn(stl.iconDetails, stl.favorite, 'px-4') } data-favourite={favorite} >
|
||||
<Bookmark sessionId={sessionId} favorite={favorite} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
{!live && (
|
||||
<div className="flex flex-col items-center px-4">
|
||||
<div className={ cn(stl.count, { "color-gray-medium": errorsCount === 0 }) } >{ errorsCount }</div>
|
||||
<Label label="Errors" color={errorsCount > 0 ? '' : 'color-gray-medium'} />
|
||||
|
||||
<div className={ stl.playLink } id="play-button" data-viewed={ viewed }>
|
||||
<Link to={ sessionRoute(sessionId) }>
|
||||
<Icon name={ viewed ? 'play-fill' : 'play-circle-light' } size="30" color="teal" />
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{ live && <LiveTag isLive={true} /> }
|
||||
|
||||
<div className={ cn(stl.iconDetails, stl.favorite, 'px-4') } data-favourite={favorite} >
|
||||
<Bookmark sessionId={sessionId} favorite={favorite} />
|
||||
</div>
|
||||
|
||||
<div className={ stl.playLink } id="play-button" data-viewed={ viewed }>
|
||||
<Link to={ sessionRoute(sessionId) }>
|
||||
<Icon name={ viewed ? 'play-fill' : 'play-circle-light' } size="30" color="teal" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<SessionMetaList className="pt-3" metaList={[
|
||||
{ label: 'Pages', value: pagesCount },
|
||||
{ label: 'Errors', value: errorsCount },
|
||||
{ label: 'Events', value: eventsCount },
|
||||
{ label: 'Events', value: eventsCount },
|
||||
{ label: 'Events', value: eventsCount },
|
||||
]} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
import React from 'react'
|
||||
import { Popup } from 'UI'
|
||||
import cn from 'classnames'
|
||||
|
||||
interface Props {
|
||||
className?: string,
|
||||
metaList: []
|
||||
}
|
||||
const MAX_LENGTH = 3;
|
||||
export default function SessionMetaList(props: Props) {
|
||||
const { className = '', metaList } = props
|
||||
return (
|
||||
<div className={cn("text-sm flex items-start", className)}>
|
||||
{metaList.slice(0, MAX_LENGTH).map(({ label, value }, index) => (
|
||||
<div key={index} className="flex items-center rounded mr-3">
|
||||
<span className="rounded-tl rounded-bl bg-gray-light-shade px-2 color-gray-medium">{label}</span>
|
||||
<span className="rounded-tr rounded-br bg-gray-lightest px-2 color-gray-dark">{value}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{metaList.length > MAX_LENGTH && (
|
||||
<Popup
|
||||
trigger={ (
|
||||
<div className="flex items-center">
|
||||
<span className="rounded bg-active-blue color-teal px-2 color-gray-dark cursor-pointer">
|
||||
+{metaList.length - MAX_LENGTH} More
|
||||
</span>
|
||||
</div>
|
||||
) }
|
||||
content={
|
||||
<div className="flex flex-col">
|
||||
{metaList.slice(MAX_LENGTH).map(({ label, value }, index) => (
|
||||
<div key={index} className="flex items-center rounded mb-2">
|
||||
<span className="rounded-tl rounded-bl bg-gray-light-shade px-2 color-gray-medium">{label}</span>
|
||||
<span className="rounded-tr rounded-br bg-gray-lightest px-2 color-gray-dark">{value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
on="click"
|
||||
position="top right"
|
||||
// className={ styles.popup }
|
||||
hideOnScroll
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './SessionMetaList';
|
||||
|
|
@ -12,12 +12,12 @@
|
|||
user-select: none;
|
||||
@mixin defaultHover;
|
||||
border-radius: 3px;
|
||||
padding: 10px 10px;
|
||||
padding-right: 15px;
|
||||
margin-bottom: 15px;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* padding: 10px 10px; */
|
||||
/* padding-right: 15px; */
|
||||
/* margin-bottom: 15px; */
|
||||
/* background-color: white; */
|
||||
/* display: flex; */
|
||||
/* align-items: center; */
|
||||
border: solid thin #EEEEEE;
|
||||
|
||||
& .favorite {
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ const routerOBTabString = `:activeTab(${ Object.values(OB_TABS).join('|') })`;
|
|||
export const onboarding = (tab = routerOBTabString) => `/onboarding/${ tab }`;
|
||||
|
||||
export const sessions = params => queried('/sessions', params);
|
||||
export const assist = params => queried('/assist', params);
|
||||
|
||||
export const session = (sessionId = ':sessionId', hash) => hashed(`/session/${ sessionId }`, hash);
|
||||
export const liveSession = (sessionId = ':sessionId', hash) => hashed(`/live/session/${ sessionId }`, hash);
|
||||
|
|
@ -105,7 +106,7 @@ export const METRICS_QUERY_KEY = 'metrics';
|
|||
export const SOURCE_QUERY_KEY = 'source';
|
||||
export const WIDGET_QUERY_KEY = 'widget';
|
||||
|
||||
const REQUIRED_SITE_ID_ROUTES = [ liveSession(''), session(''), sessions(), dashboard(''), error(''), errors(), onboarding(''), funnel(''), funnelIssue(''), ];
|
||||
const REQUIRED_SITE_ID_ROUTES = [ liveSession(''), session(''), sessions(), assist(), dashboard(''), error(''), errors(), onboarding(''), funnel(''), funnelIssue(''), ];
|
||||
const routeNeedsSiteId = path => REQUIRED_SITE_ID_ROUTES.some(r => path.startsWith(r));
|
||||
const siteIdToUrl = (siteId = ':siteId') => {
|
||||
if (Array.isArray(siteId)) {
|
||||
|
|
@ -128,7 +129,7 @@ export function isRoute(route, path){
|
|||
routeParts.every((p, i) => p.startsWith(':') || p === pathParts[ i ]);
|
||||
}
|
||||
|
||||
const SITE_CHANGE_AVALIABLE_ROUTES = [ sessions(), dashboard(), errors(), onboarding('')];
|
||||
const SITE_CHANGE_AVALIABLE_ROUTES = [ sessions(), assist(), dashboard(), errors(), onboarding('')];
|
||||
export const siteChangeAvaliable = path => SITE_CHANGE_AVALIABLE_ROUTES.some(r => isRoute(r, path));
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export default Record({
|
|||
rangeValue,
|
||||
startDate,
|
||||
endDate,
|
||||
// groupByUser: true,
|
||||
|
||||
sort: 'startTs',
|
||||
order: 'desc',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue