ui: tracked user profile and list

This commit is contained in:
nick-delirium 2025-01-30 15:21:07 +01:00
parent 60fa20d21d
commit de1e1ca44b
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
15 changed files with 676 additions and 46 deletions

View file

@ -38,6 +38,8 @@ const components: any = {
ScopeSetup: lazy(() => import('Components/ScopeForm')),
HighlightsPure: lazy(() => import('Components/Highlights/HighlightsList')),
ActivityPure: lazy(() => import('Components/DataManagement/Activity/Page')),
UserPage: lazy(() => import('Components/DataManagement/UsersEvents/UserPage')),
UsersEventsPage: lazy(() => import('Components/DataManagement/UsersEvents/ListPage')),
};
const enhancedComponents: any = {
@ -62,6 +64,8 @@ const enhancedComponents: any = {
ScopeSetup: components.ScopeSetup,
Highlights: components.HighlightsPure,
Activity: components.ActivityPure,
UserPage: components.UserPage,
UsersEventsPage: components.UsersEventsPage,
};
const withSiteId = routes.withSiteId;
@ -112,7 +116,9 @@ const SCOPE_SETUP = routes.scopeSetup();
const HIGHLIGHTS_PATH = routes.highlights();
const DATA_MANAGEMENT = {
ACTIVITY: routes.dataManagement.activity()
ACTIVITY: routes.dataManagement.activity(),
USER_PAGE: routes.dataManagement.userPage(),
USERS_EVENTS: routes.dataManagement.usersEvents(),
}
function PrivateRoutes() {
@ -302,6 +308,18 @@ function PrivateRoutes() {
path={withSiteId(DATA_MANAGEMENT.ACTIVITY, siteIdList)}
component={enhancedComponents.Activity}
/>
<Route
exact
strict
path={withSiteId(DATA_MANAGEMENT.USER_PAGE, siteIdList)}
component={enhancedComponents.UserPage}
/>
<Route
exact
strict
path={withSiteId(DATA_MANAGEMENT.USERS_EVENTS, siteIdList)}
component={enhancedComponents.UsersEventsPage}
/>
{Object.entries(routes.redirects).map(([fr, to]) => (
<Redirect key={fr} exact strict from={fr} to={to} />
))}

View file

@ -60,7 +60,7 @@ function EventDetailsModal({ ev, onClose }: { ev: EventData, onClose: () => void
return (
<div className={'h-screen w-full flex flex-col gap-4 p-4'}>
<div className={'flex justify-between items-center'}>
<div className={'font-semibold text-lg'}>Event</div>
<div className={'font-semibold text-xl'}>Event</div>
<div className={'p-2 cursor-pointer'} onClick={onClose}>
<X size={16} />
</div>
@ -112,7 +112,7 @@ function EventDetailsModal({ ev, onClose }: { ev: EventData, onClose: () => void
);
}
function Triangle({ size = 16, color = 'currentColor' }) {
export function Triangle({ size = 16, color = 'currentColor' }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"

View file

@ -11,45 +11,52 @@ import { useModal } from 'App/components/Modal';
import EventDetailsModal from './EventDetailsModal';
import { useQuery } from '@tanstack/react-query';
import Select from 'Shared/Select';
import { Link } from 'react-router-dom';
import { dataManagement, withSiteId } from 'App/routes'
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
const limit = 100;
const testEv = new Event({
name: 'test ev #',
time: Date.now(),
defaultFields: {
userId: '123',
userLocation: 'NY',
userEnvironment: 'Mac OS',
},
customFields: {},
isAutoCapture: false,
sessionId: '123123',
});
const testAutoEv = new Event({
name: 'auto test ev',
time: Date.now(),
defaultFields: {
userId: '123',
userLocation: 'NY',
userEnvironment: 'Mac OS',
},
customFields: {},
isAutoCapture: true,
sessionId: '123123',
});
export const list = [testEv.toData(), testAutoEv.toData()];
const fetcher = async (
page: number
): Promise<{ list: any[]; total: number }> => {
const total = 3000;
return new Promise((resolve) => {
const testEv = new Event({
name: 'test ev #' + page,
time: Date.now(),
defaultFields: {
userId: '123',
userCity: 'NY',
userEnvironment: 'Mac OS',
},
customFields: {},
isAutoCapture: false,
sessionId: '123123',
});
const testAutoEv = new Event({
name: 'auto test ev',
time: Date.now(),
defaultFields: {
userId: '123',
userCity: 'NY',
userEnvironment: 'Mac OS',
},
customFields: {},
isAutoCapture: true,
sessionId: '123123',
});
const list = [testEv.toData(), testAutoEv.toData()];
resolve({ list, total });
});
};
function ActivityPage() {
const { projectsStore } = useStore()
const siteId = projectsStore.activeSiteId;
const [page, setPage] = React.useState(1);
const [hiddenCols, setHiddenCols] = React.useState([]);
const { data, isPending } = useQuery({
@ -106,22 +113,23 @@ function ActivityPage() {
showSorterTooltip: { target: 'full-header' },
sorter: (a, b) => a.userId.localeCompare(b.userId),
render: (text) => (
<div
<Link
to={withSiteId(dataManagement.userPage(text), siteId)}
className={'link'}
onClick={(e) => {
e.stopPropagation();
}}
>
{text}
</div>
</Link>
),
},
{
title: 'City',
dataIndex: 'userCity',
key: 'userCity',
dataIndex: 'userLocation',
key: 'userLocation',
showSorterTooltip: { target: 'full-header' },
sorter: (a, b) => a.userCity.localeCompare(b.userCity),
sorter: (a, b) => a.userLocation.localeCompare(b.userLocation),
},
{
title: 'Environment',
@ -234,23 +242,23 @@ function ActivityPage() {
options={[
{ label: 'Past 24 Hours', value: 'DESC' },
{ label: 'Weekly', value: 'ASC' },
{ label: 'Other', value: 'Stuff' }
{ label: 'Other', value: 'Stuff' },
]}
defaultValue={'DESC'}
plain
onChange={({ value }) => {
console.log(value)
console.log(value);
}}
/>
<Select
options={[
{ label: 'Newest', value: 'DESC' },
{ label: 'Oldest', value: 'ASC' }
{ label: 'Oldest', value: 'ASC' },
]}
defaultValue={'DESC'}
plain
onChange={({ value }) => {
console.log(value)
console.log(value);
}}
/>
</div>
@ -289,4 +297,4 @@ function ActivityPage() {
);
}
export default ActivityPage;
export default observer(ActivityPage);

View file

@ -6,7 +6,7 @@ interface DefaultFields {
export interface EventData {
name: string;
time: string;
time: number;
$_isAutoCapture: boolean;
$_defaultFields: DefaultFields;
$_customFields?: Record<string, any>;
@ -14,10 +14,10 @@ export interface EventData {
export default class Event {
name: string;
time: string;
time: number;
defaultFields: DefaultFields = {
userId: '',
userCity: '',
userLocation: '',
userEnvironment: '',
}
customFields?: Record<string,any> = undefined;
@ -35,7 +35,7 @@ export default class Event {
isAutoCapture,
}: {
name: string;
time: string;
time: number;
defaultFields: DefaultFields;
customFields?: Record<string, any>;
sessionId: string;

View file

@ -0,0 +1,236 @@
import React from 'react';
import { numberWithCommas } from 'App/utils';
import FilterSelection from "Shared/Filters/FilterSelection/FilterSelection";
import User from './data/User';
import { Pagination } from 'UI';
import { Segmented, Input, Table, Button, Dropdown, Tabs } from 'antd';
import { MoreOutlined } from '@ant-design/icons';
import { TabsProps } from ".store/antd-virtual-7db13b4af6/package";
import { useHistory } from 'react-router-dom';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { withSiteId, dataManagement } from "App/routes";
import { Filter } from "lucide-react";
const customTabBar: TabsProps['renderTabBar'] = (props, DefaultTabBar) => (
<DefaultTabBar {...props} className="!mb-0" />
);
function ListPage() {
const { projectsStore } = useStore();
const siteId = projectsStore.activeSiteId;
const history = useHistory();
const toUser = (id: string) => history.push(withSiteId(dataManagement.userPage(id), siteId));
const [view, setView] = React.useState('users');
const views = [
{
key: 'users',
label: <div className={'text-lg font-medium'}>Users</div>,
content: <div>placeholder</div>,
},
{
key: 'events',
label: <div className={'text-lg font-medium'}>Events</div>,
content: <div>placeholder</div>,
},
];
return (
<div className="flex flex-col gap-4 p-4 pt-2 rounded-lg border bg-white">
<div className={'flex items-center justify-between border-b'}>
<Tabs
type={'line'}
defaultActiveKey={'users'}
activeKey={view}
style={{ borderBottom: 'none' }}
onChange={(key) => setView(key)}
items={views}
renderTabBar={customTabBar}
/>
<div className="flex items-center gap-2">
<Button type={'text'}>Docs</Button>
<Input.Search placeholder={'Name, email, ID'} />
</div>
</div>
{view === 'users' ? <UsersList toUser={toUser} /> : null}
</div>
);
}
function UsersList({ toUser }: { toUser: (id: string) => void }) {
const [editCols, setEditCols] = React.useState(false);
const testUsers = [
new User({
name: 'test123',
userId: 'email@id.com',
distinctId: ['123123123'],
userLocation: 'NY',
cohorts: ['test'],
properties: {
email: 'sad;jsadk',
},
updatedAt: Date.now(),
}),
new User({
name: 'test123',
userId: 'email@id.com',
distinctId: ['123123123'],
userLocation: 'NY',
cohorts: ['test'],
properties: {
email: 'sad;jsadk',
},
updatedAt: Date.now(),
}),
new User({
name: 'test123',
userId: 'email@id.com',
distinctId: ['123123123123'],
userLocation: 'NY',
cohorts: ['test'],
properties: {
email: 'sad;jsadk',
},
updatedAt: Date.now(),
}),
new User({
name: 'test123',
userId: 'email@id.com',
distinctId: ['1231214143123'],
userLocation: 'NY',
cohorts: ['test'],
properties: {
email: 'sad;jsadk',
},
updatedAt: Date.now(),
})
]
const dropdownItems = [
{
label: 'Show/Hide Columns',
key: 'edit-columns',
onClick: () => setTimeout(() => setEditCols(true), 1),
},
];
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
showSorterTooltip: { target: 'full-header' },
sorter: (a, b) => a.name.localeCompare(b.name),
},
{
title: 'Email',
dataIndex: 'userId',
key: 'userId',
showSorterTooltip: { target: 'full-header' },
sorter: (a, b) => a.userId.localeCompare(b.userId),
},
{
title: 'Distinct ID',
dataIndex: 'distinctId',
key: 'distinctId',
showSorterTooltip: { target: 'full-header' },
sorter: (a, b) => a.distinctId[0].localeCompare(b.distinctId[0]),
},
{
title: 'Updated',
dataIndex: 'updatedAt',
key: 'updatedAt',
showSorterTooltip: { target: 'full-header' },
sorter: (a, b) => a.updatedAt.localeCompare(b.updatedAt),
},
{
title: (
<Dropdown
menu={{ items: dropdownItems }}
trigger={'click'}
placement={'bottomRight'}
>
<div className={'cursor-pointer'}>
<MoreOutlined />
</div>
</Dropdown>
),
dataIndex: '$__opts__$',
key: '$__opts__$',
width: 50,
},
];
const page = 1;
const total = 10;
const onPageChange = (page: number) => {};
const limit = 10;
const list = [];
const onAddFilter = () => console.log('add filter');
const excludeFilterKeys = []
const excludeCategory = []
return (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
{/* 1.23 -- <span>Show by</span>*/}
{/*<Segmented*/}
{/* size={'small'}*/}
{/* options={[*/}
{/* { label: 'Profiles', value: 'profiles' },*/}
{/* { label: 'Company', value: 'company' },*/}
{/* ]}*/}
{/*/>*/}
<FilterSelection
mode={'filters'}
filter={undefined}
onFilterClick={onAddFilter}
disabled={false}
excludeFilterKeys={excludeFilterKeys}
excludeCategory={excludeCategory}
isLive={false}
>
<Button
icon={<Filter size={16} strokeWidth={1} />}
type="default"
size={'small'}
className='btn-add-filter'
>
Filters
</Button>
</FilterSelection>
</div>
<Table
onRow={(record) => ({
onClick: () => toUser(record.userId),
})}
pagination={false}
rowClassName={'cursor-pointer'}
dataSource={testUsers}
columns={columns}
/>
<div className="flex items-center justify-between px-4 py-3 shadow-sm w-full bg-white rounded-lg mt-2">
<div>
{'Showing '}
<span className="font-medium">{(page - 1) * limit + 1}</span>
{' to '}
<span className="font-medium">
{(page - 1) * limit + list.length}
</span>
{' of '}
<span className="font-medium">{numberWithCommas(total)}</span>
{' users.'}
</div>
<Pagination
page={page}
total={total}
onPageChange={onPageChange}
limit={limit}
debounceRequest={500}
/>
</div>
</div>
);
}
export default observer(ListPage);

View file

@ -0,0 +1,210 @@
import React from 'react';
import EventDetailsModal, { Triangle } from '../Activity/EventDetailsModal';
import User from './data/User';
import { Dropdown, Popover } from 'antd';
import { MoreOutlined, DeleteOutlined } from '@ant-design/icons';
import Event from 'Components/DataManagement/Activity/data/Event';
import { Files, Users, Eye, EyeOff } from 'lucide-react';
import copy from 'copy-to-clipboard';
import { list } from '../Activity/Page';
import Select from 'Shared/Select';
import { tsToCheckRecent } from 'App/date';
import { useModal } from 'App/components/Modal';
import UserPropertiesModal from './components/UserPropertiesModal';
import Tag from './components/Tag';
import EventsByDay from './components/EventsByDay';
import Breadcrumb from 'Shared/Breadcrumb';
import { dataManagement } from 'App/routes'
const card = 'rounded-lg border bg-white';
function UserPage() {
return (
<div className={'flex flex-col gap-2 mx-auto'} style={{ maxWidth: 1360 }}>
<UserInfo />
<Activity />
</div>
);
}
function Activity() {
const testEvs = [...list, ...list, ...list];
const [show, setShow] = React.useState(true);
const { showModal, hideModal } = useModal();
const onItemClick = (ev: Event) => {
showModal(<EventDetailsModal ev={ev} onClose={hideModal} />, {
width: 420,
right: true,
});
};
const byDays: Record<string, Event[]> = testEvs.reduce((acc, ev) => {
const date = tsToCheckRecent(ev.time, 'LLL dd, yyyy');
if (!acc[date]) {
acc[date] = [];
}
acc[date].push(ev);
return acc;
}, {});
const toggleEvents = () => {
setShow((prev) => !prev);
};
return (
<div className={card}>
<div className={'px-4 py-2 flex items-center gap-2'}>
<div className={'text-lg font-semibold'}>Activity</div>
<div className={'link flex gap-1 items-center'}>
<span>Play Sessions</span>
<Triangle size={10} color={'blue'} />
</div>
<div className={'ml-auto'} />
<div
className={'flex items-center gap-2 cursor-pointer'}
onClick={toggleEvents}
>
{!show ? <Eye size={16} /> : <EyeOff size={16} />}
<span className={'font-medium'}>{show ? 'Hide' : 'Show'} Events</span>
</div>
<Select
options={[
{ label: 'Newest', value: 'DESC' },
{ label: 'Oldest', value: 'ASC' },
]}
defaultValue={'DESC'}
plain
onChange={({ value }) => {
console.log(value);
}}
/>
</div>
<div className={show ? 'block' : 'hidden'}>
<EventsByDay byDays={byDays} onItemClick={onItemClick} />
</div>
</div>
);
}
function UserInfo() {
const { showModal, hideModal } = useModal();
const testUser = new User({
name: 'test user',
userId: 'test@email.com',
distinctId: ['123123123123', '123123123123', '123123123123'],
userLocation: 'NY',
cohorts: ['test'],
properties: {
email: 'test@test.com',
},
updatedAt: Date.now(),
});
const dropdownItems = [
{
label: 'Delete User',
key: 'delete-user',
icon: <DeleteOutlined />,
onClick: () => console.log('confirm'),
},
];
const showAll = () => {
showModal(<UserPropertiesModal properties={testUser.properties} />, {
width: 420,
right: true,
});
};
return (
<>
<Breadcrumb items={[
{ label: 'Users', to: dataManagement.usersEvents(), withSiteId: true },
{ label: testUser.name },
]} />
<div className={card}>
<div className="flex items-center justify-between p-4 border-b">
<div className="flex items-center gap-2">
<div
className={
'bg-gray-lighter h-11 w-12 rounded-full flex items-center justify-center text-gray-medium border border-gray-medium'
}
>
{testUser.name.slice(0, 2)}
</div>
<div className="flex flex-col">
<div className="text-xl font-semibold">{testUser.name}</div>
<div>{testUser.userId}</div>
</div>
</div>
<div className="flex flex-col">
<div className={'font-semibold'}>Distinct ID</div>
<div>
{testUser.distinctId[0]}
{testUser.distinctId.length > 1 && (
<Popover
title={
<div className={'text-disabled-text'}>
Tracking IDs linked to this user
</div>
}
trigger={'click'}
placement={'bottom'}
arrow={false}
content={
<div className={'flex flex-col gap-2'}>
{testUser.distinctId.map((id) => (
<div className={'w-full group flex justify-between'}>
<span>{id}</span>
<div
className={
'hidden group-hover:block cursor-pointer active:text-blue'
}
onClick={() => copy(id)}
>
<Files size={14} />
</div>
</div>
))}
</div>
}
>
<div className={'w-fit cursor-pointer inline-block ml-2'}>
<Tag>+{testUser.distinctId.length - 1}</Tag>
</div>
</Popover>
)}
</div>
</div>
<div className="flex flex-col">
<div className={'font-semibold'}>Location</div>
<div>{testUser.userLocation}</div>
</div>
<div className={'flex items-center gap-4'}>
<div onClick={showAll} className={'link font-semibold'}>
+{Object.keys(testUser.properties).length} properties
</div>
<Dropdown
menu={{ items: dropdownItems }}
trigger={['click']}
placement={'bottomRight'}
>
<div className={'cursor-pointer'}>
<MoreOutlined />
</div>
</Dropdown>
</div>
</div>
<div className="flex items-center p-4">
<Users size={14} />
<div className={'mr-4 ml-2'}>Cohorts</div>
{testUser.cohorts.map((cohort) => (
<Tag>{cohort}</Tag>
))}
</div>
</div>
</>
);
}
export default UserPage;

View file

@ -0,0 +1,44 @@
import React from 'react';
import { formatTs } from 'App/date';
import { CalendarFold, ChevronRight } from 'lucide-react';
function EventsByDay({
byDays,
onItemClick,
}: {
byDays: Record<string, any[]>;
onItemClick: (ev: any) => void;
}) {
return (
<>
{Object.keys(byDays).map((date) => (
<div className={'flex flex-col'}>
<div
className={
'bg-gray-lightest px-4 py-2 border font-semibold flex items-center gap-2'
}
>
<CalendarFold size={16} />
<span>{date}</span>
</div>
{byDays[date].map((ev) => (
<div
onClick={() => onItemClick(ev)}
className={
'hover:bg-gray-lightest border-b cursor-pointer px-4 py-2 flex items-center group'
}
>
<div className={'w-56'}>{formatTs(ev.time, 'HH:mm:ss a')}</div>
<div>{ev.name}</div>
<div className={'hidden group-hover:block ml-auto'}>
<ChevronRight size={16} />
</div>
</div>
))}
</div>
))}
</>
);
}
export default EventsByDay;

View file

@ -0,0 +1,7 @@
import React from 'react';
function Tag({ children }: { children: React.ReactNode }) {
return <div className="px-2 bg-gray-lighter rounded-xl">{children}</div>;
}
export default Tag;

View file

@ -0,0 +1,51 @@
import React from 'react';
import { Input, Button } from 'antd'
import { Pencil } from 'lucide-react'
function UserPropertiesModal({
properties,
}: {
properties: Record<string, string>
}) {
return (
<div className="p-4 flex flex-col gap-4 h-screen w-full">
<div className="font-semibold text-xl">All User Properties</div>
<Input.Search size={'small'} />
{Object.entries(properties).map(([key, value]) => (
<Property pkey={key} value={value} />
))}
</div>
)
}
function Property({ pkey, value, onSave }: {
pkey: string,
value: string,
onSave?: (key: string, value: string) => void
}) {
const [isEdit, setIsEdit] = React.useState(false)
return (
<div className="p-4 flex items-start border-b group w-full hover:bg-gray-lightest">
<div className={'flex-1'}>{pkey}</div>
{isEdit ? (
<div className={'flex-1 flex flex-col gap-2'}>
<Input size={'small'} defaultValue={value} />
<div className={'flex items-center gap-2'}>
<Button type={'text'} onClick={() => setIsEdit(false)}>Cancel</Button>
<Button type={'primary'}>Save</Button>
</div>
</div>
) : (
<div className={'flex-1 text-disabled-text flex justify-between items-start'}>
<span>{value}</span>
<div className={'hidden group-hover:block cursor-pointer active:text-blue ml-auto'} onClick={() => setIsEdit(true)}>
<Pencil size={16} />
</div>
</div>
)}
</div>
)
}
export default UserPropertiesModal;

View file

@ -0,0 +1,35 @@
export default class User {
name: string;
userId: string;
distinctId: string[];
userLocation: string;
cohorts: string[];
properties: Record<string, any>;
updatedAt: number;
constructor({
name,
userId,
distinctId,
userLocation,
cohorts,
properties,
updatedAt
}: {
name: string;
userId: string;
distinctId: string[];
userLocation: string;
cohorts: string[];
properties: Record<string, any>;
updatedAt: number;
}) {
this.name = name;
this.userId = userId;
this.distinctId = distinctId;
this.userLocation = userLocation;
this.cohorts = cohorts;
this.properties = properties;
this.updatedAt = updatedAt;
}
}

View file

@ -1,6 +1,9 @@
import React from 'react';
import { Icon } from 'UI';
import { Link } from 'react-router-dom';
import { useStore } from 'App/mstore'
import { observer } from 'mobx-react-lite'
import { withSiteId } from "App/routes";
interface Props {
items: any;
@ -8,6 +11,9 @@ interface Props {
function Breadcrumb(props: Props) {
const { items } = props;
const { projectsStore } = useStore();
const siteId = projectsStore.activeSiteId;
return (
<div className="mb-3 flex items-center text-lg">
{items.map((item: any, index: any) => {
@ -28,7 +34,7 @@ function Breadcrumb(props: Props) {
}
return (
<div key={index} className="color-gray-darkest hover:text-teal group flex items-center">
<Link to={item.to} className="flex items-center default-hover">
<Link to={item.withSiteId ? withSiteId(item.to, siteId) : item.to} className="flex items-center default-hover">
{index === 0 && (
<Icon name="chevron-left" size={16} className="mr-1 group-hover:fill-teal" />
)}
@ -42,4 +48,4 @@ function Breadcrumb(props: Props) {
);
}
export default Breadcrumb;
export default observer(Breadcrumb);

View file

@ -9,6 +9,11 @@ export function getDateFromString(date: string, format = 'yyyy-MM-dd HH:mm:ss:SS
return DateTime.fromISO(date).toFormat(format);
}
export const formatTs = (ts: number, format: string): string => {
return DateTime.fromMillis(ts).toFormat(format);
}
/**
* Formats a given duration.
*
@ -164,6 +169,9 @@ export const checkForRecent = (date: DateTime, format: string): string => {
// Formatted
return date.toFormat(format);
};
export const tsToCheckRecent = (ts: number, format: string): string => {
return checkForRecent(DateTime.fromMillis(ts), format);
}
export const resentOrDate = (ts, short?: boolean) => {
const date = DateTime.fromMillis(ts);
const d = new Date();

View file

@ -153,6 +153,7 @@ function SideMenu(props: Props) {
[PREFERENCES_MENU.MODULES]: () => client(CLIENT_TABS.MODULES),
[MENU.HIGHLIGHTS]: () => withSiteId(routes.highlights(''), siteId),
[MENU.ACTIVITY]: () => withSiteId(routes.dataManagement.activity(), siteId),
[MENU.USERS_EVENTS]: () => withSiteId(routes.dataManagement.usersEvents(), siteId),
};
const handleClick = (item: any) => {

View file

@ -54,6 +54,8 @@ export const enum MENU {
EXIT = 'exit',
SPOTS = 'spots',
ACTIVITY = 'activity',
USER_PAGE = 'user-page',
USERS_EVENTS = 'users-events',
}
export const categories: Category[] = [
@ -104,7 +106,7 @@ export const categories: Category[] = [
{
title: 'Data Management',
key: 'data-management',
items: [{ label: 'Activity', key: MENU.ACTIVITY, icon: 'square-mouse-pointer' }],
items: [{ label: 'Activity', key: MENU.ACTIVITY, icon: 'square-mouse-pointer' }, { label: 'Users and Events', key: MENU.USERS_EVENTS, icon: 'square-mouse-pointer' }],
},
{
title: 'Product Optimization',

View file

@ -149,6 +149,8 @@ export const highlights = (): string => '/highlights';
export const dataManagement = {
activity: () => '/data-management/activity',
userPage: (id = ':userId', hash?: string | number) => hashed(`/data-management/user/${id}`, hash),
usersEvents: () => '/data-management/users-and-events',
}
const REQUIRED_SITE_ID_ROUTES = [
@ -197,6 +199,8 @@ const REQUIRED_SITE_ID_ROUTES = [
highlights(),
dataManagement.activity(),
dataManagement.userPage(''),
dataManagement.usersEvents(),
];
const routeNeedsSiteId = (path: string): boolean => REQUIRED_SITE_ID_ROUTES.some(r => path.startsWith(r));
const siteIdToUrl = (siteId = ':siteId'): string => {