diff --git a/frontend/app/PrivateRoutes.tsx b/frontend/app/PrivateRoutes.tsx index 7e4a85955..aba0340f0 100644 --- a/frontend/app/PrivateRoutes.tsx +++ b/frontend/app/PrivateRoutes.tsx @@ -37,6 +37,7 @@ const components: any = { SpotPure: lazy(() => import('Components/Spots/SpotPlayer')), ScopeSetup: lazy(() => import('Components/ScopeForm')), HighlightsPure: lazy(() => import('Components/Highlights/HighlightsList')), + ActivityPure: lazy(() => import('Components/DataManagement/Activity/Page')), }; const enhancedComponents: any = { @@ -60,6 +61,7 @@ const enhancedComponents: any = { Spot: components.SpotPure, ScopeSetup: components.ScopeSetup, Highlights: components.HighlightsPure, + Activity: components.ActivityPure, }; const withSiteId = routes.withSiteId; @@ -109,6 +111,10 @@ const SCOPE_SETUP = routes.scopeSetup(); const HIGHLIGHTS_PATH = routes.highlights(); +const DATA_MANAGEMENT = { + ACTIVITY: routes.dataManagement.activity() +} + function PrivateRoutes() { const { projectsStore, userStore, integrationsStore } = useStore(); const onboarding = userStore.onboarding; @@ -290,6 +296,12 @@ function PrivateRoutes() { path={withSiteId(LIVE_SESSION_PATH, siteIdList)} component={enhancedComponents.LiveSession} /> + {Object.entries(routes.redirects).map(([fr, to]) => ( ))} diff --git a/frontend/app/components/DataManagement/Activity/Page.tsx b/frontend/app/components/DataManagement/Activity/Page.tsx new file mode 100644 index 000000000..b2cdde3de --- /dev/null +++ b/frontend/app/components/DataManagement/Activity/Page.tsx @@ -0,0 +1,182 @@ +import React from 'react'; +import { EventsList, FilterList } from 'Shared/Filters/FilterList'; +import { Table, Dropdown } from 'antd'; +import { MoreOutlined } from '@ant-design/icons'; +import { numberWithCommas } from 'App/utils'; +import { Pagination } from 'UI'; +import Event from './data/Event'; + +function ActivityPage() { + const [hiddenCols, setHiddenCols] = React.useState([]); + const appliedFilter = { filters: [] }; + const onAddFilter = () => {}; + const onUpdateFilter = () => {}; + const onRemoveFilter = () => {}; + const onChangeEventsOrder = () => {}; + const saveRequestPayloads = () => {}; + const onFilterMove = () => {}; + const [editCols, setEditCols] = React.useState(false); + + const dropdownItems = [ + { + label: 'Show/Hide Columns', + key: 'edit-columns', + onClick: () => setEditCols(true), + } + ] + + const columns = [ + { + title: 'Event Name', + dataIndex: 'name', + key: 'name', + showSorterTooltip: { target: 'full-header' }, + sorter: (a, b) => a.name.localeCompare(b.name), + render: (text, row) => ( +
+ {row.$_isAutoCapture && ( + [auto] + )} + {row.name} +
+ ), + }, + { + title: 'Time', + dataIndex: 'time', + key: 'time', + showSorterTooltip: { target: 'full-header' }, + sorter: (a, b) => a.time - b.time, + }, + { + title: 'Distinct ID', + dataIndex: 'userId', + key: 'userId', + showSorterTooltip: { target: 'full-header' }, + sorter: (a, b) => a.userId.localeCompare(b.userId), + render: (text) =>
{text}
, + }, + { + title: 'City', + dataIndex: 'userCity', + key: 'userCity', + showSorterTooltip: { target: 'full-header' }, + sorter: (a, b) => a.userCiry.localeCompare(b.userCity), + }, + { + title: 'Environment', + dataIndex: 'userEnvironment', + key: 'userEnvironment', + showSorterTooltip: { target: 'full-header' }, + sorter: (a, b) => a.userEnvironment.localeCompare(b.userEnvironment), + }, + { + title: ( + +
+ +
+
+ ), + dataIndex: '$__opts__$', + key: '$__opts__$', + width: 50, + }, + ]; + + const shownCols = columns.map((col) => ({ + ...col, + hidden: hiddenCols.includes(col.key), + })); + + const page = 1; + const limit = 100; + const total = 3000; + const testEv = new Event( + 'test ev', + Date.now(), + { userId: '123', userCity: 'NY', userEnvironment: 'Mac OS' }, + {}, + false + ); + const testAutoEv = new Event( + 'test auto ev', + Date.now(), + { userId: '123', userCity: 'NY', userEnvironment: 'Mac OS' }, + {}, + true + ); + const list = [testEv.toData(), testAutoEv.toData()]; + const onPageChange = () => {}; + return ( +
+
+ + Activity +
+ } + /> + +
+
+
+ All users activity +
+ +
+
+ {'Showing '} + {(page - 1) * limit + 1} + {' to '} + + {(page - 1) * limit + list.length} + + {' of '} + {numberWithCommas(total)} + {' events.'} +
+ +
+ + + ); +} + +export default ActivityPage; diff --git a/frontend/app/components/DataManagement/Activity/data/Event.ts b/frontend/app/components/DataManagement/Activity/data/Event.ts new file mode 100644 index 000000000..5bda41437 --- /dev/null +++ b/frontend/app/components/DataManagement/Activity/data/Event.ts @@ -0,0 +1,48 @@ +interface DefaultFields { + userId: string; + userCity: string; + userEnvironment: string; +} + +export default class Event { + name: string; + time: string; + defaultFields: DefaultFields = { + userId: '', + userCity: '', + userEnvironment: '', + } + customFields?: Record = undefined; + + readonly $_isAutoCapture; + + constructor(name: string, time: string, defaultFields: DefaultFields, customFields?: Record, isAutoCapture = false) { + this.name = name; + this.time = time; + this.defaultFields = defaultFields; + this.customFields = customFields; + this.$_isAutoCapture = isAutoCapture; + } + + toJSON() { + const obj = this.toData(); + return JSON.stringify(obj, 4); + } + + toData() { + const obj: any = { + name: this.name, + time: this.time, + $_isAutoCapture: this.$_isAutoCapture, + } + Object.entries(this.defaultFields).forEach(([key, value]) => { + obj[key] = value; + }); + if (this.customFields) { + Object.entries(this.customFields).forEach(([key, value]) => { + obj[key] = value; + }); + } + return obj; + } +} diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelTable.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelTable.tsx index 97f850b91..70fb56af7 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelTable.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelTable.tsx @@ -53,7 +53,7 @@ function FunnelTable(props: Props) { if (props.compData) { tableData.push({ conversion: props.compData.funnel.totalConversionsPercentage, - }) + }); const compFunnel = props.compData.funnel; compFunnel.stages.forEach((st, ind) => { tableData[1]['st_' + ind] = st.count; @@ -71,9 +71,7 @@ function FunnelTable(props: Props) { pagination={false} size={'middle'} scroll={{ x: 'max-content' }} - rowClassName={(_, index) => ( - index > 0 ? 'opacity-70' : '' - )} + rowClassName={(_, index) => (index > 0 ? 'opacity-70' : '')} /> exportAntCsv(tableColumns, tableData, filename); return ( -
+
} />
-
); } diff --git a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx index f65729781..cccc14aca 100644 --- a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx +++ b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx @@ -1,4 +1,4 @@ -import { GripVertical, Plus, Filter } from 'lucide-react'; +import { GripVertical, Plus, Filter, SquareDashedMousePointer } from 'lucide-react'; import { observer } from 'mobx-react-lite'; import React, { useEffect } from 'react'; import { Button } from 'antd'; @@ -27,6 +27,8 @@ interface Props { mergeUp?: boolean; borderless?: boolean; cannotAdd?: boolean; + heading?: React.ReactNode; + isLive?: boolean; } export const FilterList = observer((props: Props) => { @@ -39,7 +41,8 @@ export const FilterList = observer((props: Props) => { onAddFilter, readonly, borderless, - excludeCategory + excludeCategory, + isLive } = props; const filters = filter.filters; @@ -70,6 +73,7 @@ export const FilterList = observer((props: Props) => { disabled={readonly} excludeFilterKeys={excludeFilterKeys} excludeCategory={excludeCategory} + isLive={isLive} > )} @@ -243,6 +249,28 @@ export const EventsList = observer((props: Props) => {
+ {filters.length === 0 ? ( +
+
+
1
+
+ + + +
+ ) : null} {filters.map((filter: any, filterIndex: number) => filter.isEvent ? (
+ ); } diff --git a/frontend/app/layout/SideMenu.tsx b/frontend/app/layout/SideMenu.tsx index 7c3345b26..750046087 100644 --- a/frontend/app/layout/SideMenu.tsx +++ b/frontend/app/layout/SideMenu.tsx @@ -152,6 +152,7 @@ function SideMenu(props: Props) { [PREFERENCES_MENU.BILLING]: () => client(CLIENT_TABS.BILLING), [PREFERENCES_MENU.MODULES]: () => client(CLIENT_TABS.MODULES), [MENU.HIGHLIGHTS]: () => withSiteId(routes.highlights(''), siteId), + [MENU.ACTIVITY]: () => withSiteId(routes.dataManagement.activity(), siteId), }; const handleClick = (item: any) => { diff --git a/frontend/app/layout/data.ts b/frontend/app/layout/data.ts index 611ae148f..fe7d9a7ae 100644 --- a/frontend/app/layout/data.ts +++ b/frontend/app/layout/data.ts @@ -53,6 +53,7 @@ export const enum MENU { SUPPORT = 'support', EXIT = 'exit', SPOTS = 'spots', + ACTIVITY = 'activity', } export const categories: Category[] = [ @@ -61,26 +62,27 @@ export const categories: Category[] = [ key: 'replays', items: [ { label: 'Sessions', key: MENU.SESSIONS, icon: 'collection-play' }, - { label: 'Recommendations', key: MENU.RECOMMENDATIONS, icon: 'magic', hidden: true }, + { + label: 'Recommendations', + key: MENU.RECOMMENDATIONS, + icon: 'magic', + hidden: true, + }, { label: 'Vault', key: MENU.VAULT, icon: 'safe', hidden: true }, { label: 'Bookmarks', key: MENU.BOOKMARKS, icon: 'bookmark' }, //{ label: 'Notes', key: MENU.NOTES, icon: 'stickies' }, - { label: 'Highlights', key: MENU.HIGHLIGHTS, icon: 'chat-square-quote' } - ] + { label: 'Highlights', key: MENU.HIGHLIGHTS, icon: 'chat-square-quote' }, + ], }, { title: '', key: 'spot', - items: [ - { label: 'Spots', key: MENU.SPOTS, icon: 'orspotOutline' }, - ] + items: [{ label: 'Spots', key: MENU.SPOTS, icon: 'orspotOutline' }], }, { title: '', key: 'assist', - items: [ - { label: 'Co-Browse', key: MENU.LIVE_SESSIONS, icon: 'broadcast' }, - ] + items: [{ label: 'Co-Browse', key: MENU.LIVE_SESSIONS, icon: 'broadcast' }], }, { title: 'Analytics', @@ -96,25 +98,39 @@ export const categories: Category[] = [ // { label: 'Resource Monitoring', key: MENU.RESOURCE_MONITORING } // ] // }, - { label: 'Alerts', key: MENU.ALERTS, icon: 'bell' } - ] + { label: 'Alerts', key: MENU.ALERTS, icon: 'bell' }, + ], + }, + { + title: 'Data Management', + key: 'data-management', + items: [{ label: 'Activity', key: MENU.ACTIVITY, icon: 'square-mouse-pointer' }], }, { title: 'Product Optimization', key: 'product-optimization', items: [ { label: 'Feature Flags', key: MENU.FEATURE_FLAGS, icon: 'toggles' }, - { label: 'Usability Tests', key: MENU.USABILITY_TESTS, icon: 'clipboard-check' }, - ] + { + label: 'Usability Tests', + key: MENU.USABILITY_TESTS, + icon: 'clipboard-check', + }, + ], }, { title: '', key: 'other', items: [ - { label: 'Preferences', key: MENU.PREFERENCES, icon: 'sliders', leading: 'chevron-right' }, - { label: 'Support', key: MENU.SUPPORT, icon: 'question-circle' } - ] - } + { + label: 'Preferences', + key: MENU.PREFERENCES, + icon: 'sliders', + leading: 'chevron-right', + }, + { label: 'Support', key: MENU.SUPPORT, icon: 'question-circle' }, + ], + }, ]; export const preferences: Category[] = [ diff --git a/frontend/app/routes.ts b/frontend/app/routes.ts index f19e45424..a3b399e35 100644 --- a/frontend/app/routes.ts +++ b/frontend/app/routes.ts @@ -148,6 +148,10 @@ export const scopeSetup = (): string => '/scope-setup'; export const highlights = (): string => '/highlights'; +export const dataManagement = { + activity: () => '/data-management/activity', +} + const REQUIRED_SITE_ID_ROUTES = [ liveSession(''), session(''), diff --git a/frontend/app/styles/main.css b/frontend/app/styles/main.css index e7d2b388f..ebe39fc39 100644 --- a/frontend/app/styles/main.css +++ b/frontend/app/styles/main.css @@ -403,4 +403,8 @@ svg { background-color: #c6c6c6; border-radius: 4px; cursor: grab; +} + +.ant-table-column-title { + font-weight: 500; } \ No newline at end of file diff --git a/frontend/app/svg/icons/square-mouse-pointer.svg b/frontend/app/svg/icons/square-mouse-pointer.svg index 027112256..30bc70d79 100644 --- a/frontend/app/svg/icons/square-mouse-pointer.svg +++ b/frontend/app/svg/icons/square-mouse-pointer.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + + diff --git a/frontend/scripts/icons.js b/frontend/scripts/icons.js index c47845761..790c9517e 100644 --- a/frontend/scripts/icons.js +++ b/frontend/scripts/icons.js @@ -116,6 +116,7 @@ function ${titleCase(fileName)}(props: Props) { /clipRule="evenoddCustomFill"/g, 'clipRule="evenodd" fillRule="evenodd"' ) + .replaceAll(`stroke="no-fill"`, 'fill="none"') .replaceAll(/fill-rule/g, 'fillRule') .replaceAll(/fill-opacity/g, 'fillOpacity') .replaceAll(/stop-color/g, 'stopColor')