+
{createdAt}
+
@@ -88,6 +89,7 @@ const SpotsListHeader = observer(
onChange={handleInputChange}
onSearch={onSearch}
className="rounded-lg"
+ size="small"
/>
diff --git a/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteItem.tsx b/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteItem.tsx
index a1be32388..83ca85698 100644
--- a/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteItem.tsx
+++ b/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteItem.tsx
@@ -1,6 +1,5 @@
import React from 'react';
import { Link, confirm } from 'UI';
-import PlayLink from 'Shared/SessionItem/PlayLink';
import { tagProps, Note } from 'App/services/NotesService';
import { formatTimeOrDate } from 'App/date';
import { useStore } from 'App/mstore';
diff --git a/frontend/app/components/ui/Icons/index.ts b/frontend/app/components/ui/Icons/index.ts
index 570e74c12..be6c2757f 100644
--- a/frontend/app/components/ui/Icons/index.ts
+++ b/frontend/app/components/ui/Icons/index.ts
@@ -449,6 +449,7 @@ export { default as Social_trello } from './social_trello';
export { default as Sparkles } from './sparkles';
export { default as Speedometer2 } from './speedometer2';
export { default as Spinner } from './spinner';
+export { default as Square_mouse_pointer } from './square_mouse_pointer';
export { default as Star } from './star';
export { default as Step_forward } from './step_forward';
export { default as Stickies } from './stickies';
diff --git a/frontend/app/components/ui/Icons/square_mouse_pointer.tsx b/frontend/app/components/ui/Icons/square_mouse_pointer.tsx
new file mode 100644
index 000000000..7e84e1a05
--- /dev/null
+++ b/frontend/app/components/ui/Icons/square_mouse_pointer.tsx
@@ -0,0 +1,19 @@
+
+/* Auto-generated, do not edit */
+import React from 'react';
+
+interface Props {
+ size?: number | string;
+ width?: number | string;
+ height?: number | string;
+ fill?: string;
+}
+
+function Square_mouse_pointer(props: Props) {
+ const { size = 14, width = size, height = size, fill = '' } = props;
+ return (
+
+ );
+}
+
+export default Square_mouse_pointer;
diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx
index 24e8b8b02..7baf551d1 100644
--- a/frontend/app/components/ui/SVG.tsx
+++ b/frontend/app/components/ui/SVG.tsx
@@ -451,6 +451,7 @@ import {
Sparkles,
Speedometer2,
Spinner,
+ Square_mouse_pointer,
Star,
Step_forward,
Stickies,
@@ -480,7 +481,7 @@ import {
Zoom_in
} from './Icons'
-export type IconNames = 'activity' | 'analytics' | 'anchor' | 'arrow-bar-left' | 'arrow-clockwise' | 'arrow-counterclockwise' | 'arrow-down-short' | 'arrow-down-up' | 'arrow-down' | 'arrow-repeat' | 'arrow-right-short' | 'arrow-up-short' | 'arrow-up' | 'avatar/icn_avatar1' | 'avatar/icn_avatar10' | 'avatar/icn_avatar11' | 'avatar/icn_avatar12' | 'avatar/icn_avatar13' | 'avatar/icn_avatar14' | 'avatar/icn_avatar15' | 'avatar/icn_avatar16' | 'avatar/icn_avatar17' | 'avatar/icn_avatar18' | 'avatar/icn_avatar19' | 'avatar/icn_avatar2' | 'avatar/icn_avatar20' | 'avatar/icn_avatar21' | 'avatar/icn_avatar22' | 'avatar/icn_avatar23' | 'avatar/icn_avatar3' | 'avatar/icn_avatar4' | 'avatar/icn_avatar5' | 'avatar/icn_avatar6' | 'avatar/icn_avatar7' | 'avatar/icn_avatar8' | 'avatar/icn_avatar9' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'battery-charging' | 'battery' | 'bell-plus' | 'bell-slash' | 'bell' | 'binoculars' | 'book' | 'bookmark' | 'broadcast' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'buildings' | 'bullhorn' | 'calendar' | 'call' | 'camera-video-off' | 'camera-video' | 'camera' | 'card-list' | 'card-text' | 'caret-down-fill' | 'caret-right-fill' | 'chat-dots' | 'chat-left-text' | 'chat-square-quote' | 'check-circle-fill' | 'check-circle' | 'check' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'click-hesitation' | 'click-rage' | 'clipboard-check' | 'clock-history' | 'clock' | 'close' | 'code' | 'cog' | 'cogs' | 'collection-play' | 'collection' | 'color/apple' | 'color/browser/chrome' | 'color/browser/edge' | 'color/browser/facebook' | 'color/browser/firefox' | 'color/browser/google' | 'color/browser/opera' | 'color/browser/safari' | 'color/browser/unknown' | 'color/browser/whale' | 'color/chrome' | 'color/country/de' | 'color/country/fr' | 'color/country/gb' | 'color/country/in' | 'color/country/us' | 'color/de' | 'color/device/desktop' | 'color/device/mobile' | 'color/device/tablet' | 'color/device/unkown' | 'color/edge' | 'color/fedora' | 'color/firefox' | 'color/fr' | 'color/gb' | 'color/in' | 'color/issues/bad_request' | 'color/issues/click_rage' | 'color/issues/cpu' | 'color/issues/crash' | 'color/issues/custom' | 'color/issues/dead_click' | 'color/issues/errors' | 'color/issues/excessive_scrolling' | 'color/issues/js_exception' | 'color/issues/memory' | 'color/issues/missing_resource' | 'color/issues/mouse_thrashing' | 'color/issues/slow_page_load' | 'color/microsoft' | 'color/opera' | 'color/os/android' | 'color/os/apple' | 'color/os/elementary' | 'color/os/fedora' | 'color/os/ios' | 'color/os/linux' | 'color/os/macos' | 'color/os/microsoft' | 'color/os/ubuntu' | 'color/os/unkown' | 'color/safari' | 'color/ubuntu' | 'color/us' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-2-back' | 'cross' | 'cubes' | 'cursor-trash' | 'dash' | 'dashboard-icn' | 'dashboards/circle-alert' | 'dashboards/cohort-chart' | 'dashboards/heatmap-2' | 'dashboards/user-journey' | 'db-icons/icn-card-clickMap' | 'db-icons/icn-card-errors' | 'db-icons/icn-card-funnel' | 'db-icons/icn-card-funnels' | 'db-icons/icn-card-insights' | 'db-icons/icn-card-library' | 'db-icons/icn-card-mapchart' | 'db-icons/icn-card-pathAnalysis' | 'db-icons/icn-card-performance' | 'db-icons/icn-card-resources' | 'db-icons/icn-card-table' | 'db-icons/icn-card-timeseries' | 'db-icons/icn-card-webVitals' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'door-closed' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'emoji-dizzy' | 'enter' | 'envelope-check' | 'envelope-paper' | 'envelope-x' | 'envelope' | 'errors-icon' | 'event/click' | 'event/click_hesitation' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/input_hesitation' | 'event/link' | 'event/location' | 'event/mouse_thrashing' | 'event/resize' | 'event/view' | 'exclamation-circle-fill' | 'exclamation-circle' | 'exclamation-triangle' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch-request' | 'fetch' | 'fflag-multi' | 'fflag-single' | 'file-bar-graph' | 'file-code' | 'file-medical-alt' | 'file-pdf' | 'file' | 'files' | 'filetype-js' | 'filetype-pdf' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/chevrons-up-down' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/screen' | 'filters/state-action' | 'filters/tag-element' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'folder-plus' | 'folder2' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel' | 'gear' | 'github' | 'graph-up' | 'grid-3x3' | 'grid-check' | 'grid' | 'hash' | 'headset' | 'history' | 'ic-errors' | 'ic-network' | 'ic-rage' | 'ic-resources' | 'icn_fetch-request' | 'icn_referrer' | 'icn_url' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'input-hesitation' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/dynatrace' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/teams-white' | 'integrations/teams' | 'integrations/vuejs' | 'integrations/zustand' | 'journal-code' | 'key' | 'keyboard' | 'layers-half' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-ul' | 'list' | 'low-disc-space' | 'magic' | 'map-marker-alt' | 'memory-ios' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'mouse-pointer-click' | 'network' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'no-recordings' | 'orIcn' | 'orSpot' | 'orspotOutline' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-circle-fill' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'people' | 'percent' | 'performance-icon' | 'person-border' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-bold' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plug' | 'plus-circle' | 'plus' | 'prev1' | 'pulse' | 'puzzle-piece' | 'puzzle' | 'question-circle' | 'question-lg' | 'quotes' | 'record-circle-fill' | 'record-circle' | 'record2' | 'redo' | 'redux' | 'referrer' | 'remote-control' | 'resources-icon' | 'safe' | 'sandglass' | 'search' | 'server' | 'share-alt' | 'shield-lock' | 'side_menu_closed' | 'side_menu_open' | 'signpost-split' | 'signup' | 'slack' | 'slash-circle' | 'sleep' | 'sliders' | 'social/slack' | 'social/trello' | 'sparkles' | 'speedometer2' | 'spinner' | 'star' | 'step-forward' | 'stickies' | 'stop-record-circle' | 'stopwatch' | 'store' | 'sync-alt' | 'table' | 'tags' | 'terminal' | 'thermometer-sun' | 'toggles' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'user-journey' | 'user-switch' | 'users' | 'vendors/graphql' | 'web-vitals' | 'wifi' | 'window-x' | 'window' | 'zoom-in';
+export type IconNames = 'activity' | 'analytics' | 'anchor' | 'arrow-bar-left' | 'arrow-clockwise' | 'arrow-counterclockwise' | 'arrow-down-short' | 'arrow-down-up' | 'arrow-down' | 'arrow-repeat' | 'arrow-right-short' | 'arrow-up-short' | 'arrow-up' | 'avatar/icn_avatar1' | 'avatar/icn_avatar10' | 'avatar/icn_avatar11' | 'avatar/icn_avatar12' | 'avatar/icn_avatar13' | 'avatar/icn_avatar14' | 'avatar/icn_avatar15' | 'avatar/icn_avatar16' | 'avatar/icn_avatar17' | 'avatar/icn_avatar18' | 'avatar/icn_avatar19' | 'avatar/icn_avatar2' | 'avatar/icn_avatar20' | 'avatar/icn_avatar21' | 'avatar/icn_avatar22' | 'avatar/icn_avatar23' | 'avatar/icn_avatar3' | 'avatar/icn_avatar4' | 'avatar/icn_avatar5' | 'avatar/icn_avatar6' | 'avatar/icn_avatar7' | 'avatar/icn_avatar8' | 'avatar/icn_avatar9' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'battery-charging' | 'battery' | 'bell-plus' | 'bell-slash' | 'bell' | 'binoculars' | 'book' | 'bookmark' | 'broadcast' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'buildings' | 'bullhorn' | 'calendar' | 'call' | 'camera-video-off' | 'camera-video' | 'camera' | 'card-list' | 'card-text' | 'caret-down-fill' | 'caret-right-fill' | 'chat-dots' | 'chat-left-text' | 'chat-square-quote' | 'check-circle-fill' | 'check-circle' | 'check' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'click-hesitation' | 'click-rage' | 'clipboard-check' | 'clock-history' | 'clock' | 'close' | 'code' | 'cog' | 'cogs' | 'collection-play' | 'collection' | 'color/apple' | 'color/browser/chrome' | 'color/browser/edge' | 'color/browser/facebook' | 'color/browser/firefox' | 'color/browser/google' | 'color/browser/opera' | 'color/browser/safari' | 'color/browser/unknown' | 'color/browser/whale' | 'color/chrome' | 'color/country/de' | 'color/country/fr' | 'color/country/gb' | 'color/country/in' | 'color/country/us' | 'color/de' | 'color/device/desktop' | 'color/device/mobile' | 'color/device/tablet' | 'color/device/unkown' | 'color/edge' | 'color/fedora' | 'color/firefox' | 'color/fr' | 'color/gb' | 'color/in' | 'color/issues/bad_request' | 'color/issues/click_rage' | 'color/issues/cpu' | 'color/issues/crash' | 'color/issues/custom' | 'color/issues/dead_click' | 'color/issues/errors' | 'color/issues/excessive_scrolling' | 'color/issues/js_exception' | 'color/issues/memory' | 'color/issues/missing_resource' | 'color/issues/mouse_thrashing' | 'color/issues/slow_page_load' | 'color/microsoft' | 'color/opera' | 'color/os/android' | 'color/os/apple' | 'color/os/elementary' | 'color/os/fedora' | 'color/os/ios' | 'color/os/linux' | 'color/os/macos' | 'color/os/microsoft' | 'color/os/ubuntu' | 'color/os/unkown' | 'color/safari' | 'color/ubuntu' | 'color/us' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-2-back' | 'cross' | 'cubes' | 'cursor-trash' | 'dash' | 'dashboard-icn' | 'dashboards/circle-alert' | 'dashboards/cohort-chart' | 'dashboards/heatmap-2' | 'dashboards/user-journey' | 'db-icons/icn-card-clickMap' | 'db-icons/icn-card-errors' | 'db-icons/icn-card-funnel' | 'db-icons/icn-card-funnels' | 'db-icons/icn-card-insights' | 'db-icons/icn-card-library' | 'db-icons/icn-card-mapchart' | 'db-icons/icn-card-pathAnalysis' | 'db-icons/icn-card-performance' | 'db-icons/icn-card-resources' | 'db-icons/icn-card-table' | 'db-icons/icn-card-timeseries' | 'db-icons/icn-card-webVitals' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'door-closed' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'emoji-dizzy' | 'enter' | 'envelope-check' | 'envelope-paper' | 'envelope-x' | 'envelope' | 'errors-icon' | 'event/click' | 'event/click_hesitation' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/input_hesitation' | 'event/link' | 'event/location' | 'event/mouse_thrashing' | 'event/resize' | 'event/view' | 'exclamation-circle-fill' | 'exclamation-circle' | 'exclamation-triangle' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch-request' | 'fetch' | 'fflag-multi' | 'fflag-single' | 'file-bar-graph' | 'file-code' | 'file-medical-alt' | 'file-pdf' | 'file' | 'files' | 'filetype-js' | 'filetype-pdf' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/chevrons-up-down' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/screen' | 'filters/state-action' | 'filters/tag-element' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'folder-plus' | 'folder2' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel' | 'gear' | 'github' | 'graph-up' | 'grid-3x3' | 'grid-check' | 'grid' | 'hash' | 'headset' | 'history' | 'ic-errors' | 'ic-network' | 'ic-rage' | 'ic-resources' | 'icn_fetch-request' | 'icn_referrer' | 'icn_url' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'input-hesitation' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/dynatrace' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/teams-white' | 'integrations/teams' | 'integrations/vuejs' | 'integrations/zustand' | 'journal-code' | 'key' | 'keyboard' | 'layers-half' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-ul' | 'list' | 'low-disc-space' | 'magic' | 'map-marker-alt' | 'memory-ios' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'mouse-pointer-click' | 'network' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'no-recordings' | 'orIcn' | 'orSpot' | 'orspotOutline' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-circle-fill' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'people' | 'percent' | 'performance-icon' | 'person-border' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-bold' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plug' | 'plus-circle' | 'plus' | 'prev1' | 'pulse' | 'puzzle-piece' | 'puzzle' | 'question-circle' | 'question-lg' | 'quotes' | 'record-circle-fill' | 'record-circle' | 'record2' | 'redo' | 'redux' | 'referrer' | 'remote-control' | 'resources-icon' | 'safe' | 'sandglass' | 'search' | 'server' | 'share-alt' | 'shield-lock' | 'side_menu_closed' | 'side_menu_open' | 'signpost-split' | 'signup' | 'slack' | 'slash-circle' | 'sleep' | 'sliders' | 'social/slack' | 'social/trello' | 'sparkles' | 'speedometer2' | 'spinner' | 'square-mouse-pointer' | 'star' | 'step-forward' | 'stickies' | 'stop-record-circle' | 'stopwatch' | 'store' | 'sync-alt' | 'table' | 'tags' | 'terminal' | 'thermometer-sun' | 'toggles' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'user-journey' | 'user-switch' | 'users' | 'vendors/graphql' | 'web-vitals' | 'wifi' | 'window-x' | 'window' | 'zoom-in';
interface Props {
name: IconNames;
@@ -1842,6 +1843,9 @@ const SVG = (props: Props) => {
case 'spinner': return
;
+ // case 'square-mouse-pointer':
+ case 'square-mouse-pointer': return
;
+
case 'star': return
;
diff --git a/frontend/app/date.ts b/frontend/app/date.ts
index 96713ee77..e9a5982ec 100644
--- a/frontend/app/date.ts
+++ b/frontend/app/date.ts
@@ -12,7 +12,8 @@ export function getDateFromString(date: string, format = 'yyyy-MM-dd HH:mm:ss:SS
/**
* Formats a given duration.
*
- * @param {Duration | number} inputDuration - The duration to format. Can be a Duration object or a number representing milliseconds.
+ * @param {Duration | number} inputDuration - The duration to format. Can be a Duration object or a number representing
+ * milliseconds.
* @returns {string} - Formatted duration string.
*
* @example
@@ -163,7 +164,7 @@ export const checkForRecent = (date: DateTime, format: string): string => {
// Formatted
return date.toFormat(format);
};
-export const resentOrDate = (ts) => {
+export const resentOrDate = (ts, short?: boolean) => {
const date = DateTime.fromMillis(ts);
const d = new Date();
// Today
@@ -171,7 +172,7 @@ export const resentOrDate = (ts) => {
// Yesterday
if (date.hasSame(d.setDate(d.getDate() - 1), 'day')) return 'Yesterday at ' + date.toFormat('hh:mm a');
- return date.toFormat('LLL dd, yyyy, hh:mm a');
+ return date.toFormat(`LLL dd, yyyy${short ? '' : ', hh:mm a'}`);
}
export const checkRecentTime = (date, format) => {
diff --git a/frontend/app/layout/SideMenu.tsx b/frontend/app/layout/SideMenu.tsx
index 901da31f1..7c3345b26 100644
--- a/frontend/app/layout/SideMenu.tsx
+++ b/frontend/app/layout/SideMenu.tsx
@@ -150,7 +150,8 @@ function SideMenu(props: Props) {
[PREFERENCES_MENU.TEAM]: () => client(CLIENT_TABS.MANAGE_USERS),
[PREFERENCES_MENU.NOTIFICATIONS]: () => client(CLIENT_TABS.NOTIFICATIONS),
[PREFERENCES_MENU.BILLING]: () => client(CLIENT_TABS.BILLING),
- [PREFERENCES_MENU.MODULES]: () => client(CLIENT_TABS.MODULES)
+ [PREFERENCES_MENU.MODULES]: () => client(CLIENT_TABS.MODULES),
+ [MENU.HIGHLIGHTS]: () => withSiteId(routes.highlights(''), siteId),
};
const handleClick = (item: any) => {
diff --git a/frontend/app/layout/data.ts b/frontend/app/layout/data.ts
index 7a1c92302..611ae148f 100644
--- a/frontend/app/layout/data.ts
+++ b/frontend/app/layout/data.ts
@@ -40,6 +40,7 @@ export const enum MENU {
VAULT = 'vault',
BOOKMARKS = 'bookmarks',
NOTES = 'notes',
+ HIGHLIGHTS = 'highlights',
LIVE_SESSIONS = 'live-sessions',
DASHBOARDS = 'dashboards',
CARDS = 'cards',
@@ -63,7 +64,8 @@ export const categories: Category[] = [
{ 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: 'Notes', key: MENU.NOTES, icon: 'stickies' },
+ { label: 'Highlights', key: MENU.HIGHLIGHTS, icon: 'chat-square-quote' }
]
},
{
diff --git a/frontend/app/mstore/notesStore.ts b/frontend/app/mstore/notesStore.ts
index bdea01bab..2ef728ded 100644
--- a/frontend/app/mstore/notesStore.ts
+++ b/frontend/app/mstore/notesStore.ts
@@ -9,17 +9,32 @@ export default class NotesStore {
sessionNotes: Note[] = []
loading: boolean
page = 1
- pageSize = 10
+ pageSize = 9
activeTags: iTag[] = []
sort = 'createdAt'
order: 'DESC' | 'ASC' = 'DESC'
ownOnly = false
total = 0
+ isSaving = false;
+ query = ''
+ editNote: Note | null = null
constructor() {
makeAutoObservable(this)
}
+ setEditNote = (note: Note | null) => {
+ this.editNote = note
+ }
+
+ setQuery = (query: string) => {
+ this.query = query
+ }
+
+ setSaving = (saving: boolean) => {
+ this.isSaving = saving
+ }
+
setLoading(loading: boolean) {
this.loading = loading
}
@@ -40,7 +55,8 @@ export default class NotesStore {
order: this.order,
tags: this.activeTags,
mineOnly: this.ownOnly,
- sharedOnly: false
+ sharedOnly: false,
+ search: this.query,
}
this.setLoading(true)
@@ -48,7 +64,18 @@ export default class NotesStore {
const { notes, count } = await notesService.fetchNotes(filter);
this.setNotes(notes);
this.setTotal(count)
- return notes;
+ return { notes, total: count };
+ } catch (e) {
+ console.error(e)
+ } finally {
+ this.setLoading(false)
+ }
+ }
+
+ fetchNoteById = async (noteId: string)=> {
+ this.setLoading(true)
+ try {
+ return await notesService.fetchNoteById(noteId);
} catch (e) {
console.error(e)
} finally {
@@ -115,7 +142,7 @@ export default class NotesStore {
}
}
- getNoteById(noteId: number, notes?: Note[]) {
+ getNoteById(noteId: any, notes?: Note[]) {
const notesSource = notes ? notes : this.notes
return notesSource.find(note => note.noteId === noteId)
@@ -128,24 +155,19 @@ export default class NotesStore {
toggleTag(tag?: iTag) {
if (!tag) {
this.activeTags = []
- this.fetchNotes()
} else {
this.activeTags = [tag]
- this.fetchNotes()
}
}
toggleShared(ownOnly: boolean) {
this.ownOnly = ownOnly
- this.fetchNotes()
}
toggleSort(sort: string) {
const sortOrder = sort.split('-')[1]
// @ts-ignore
this.order = sortOrder
-
- this.fetchNotes()
}
async sendSlackNotification(noteId: string, webhook: string) {
diff --git a/frontend/app/mstore/sessionStore.ts b/frontend/app/mstore/sessionStore.ts
index f48efcd2b..d628edc62 100644
--- a/frontend/app/mstore/sessionStore.ts
+++ b/frontend/app/mstore/sessionStore.ts
@@ -394,21 +394,23 @@ export default class SessionStore {
const wasInFavorite =
this.favoriteList.findIndex(({ sessionId }) => sessionId === id) > -1;
- if (session) {
- session.favorite = !wasInFavorite;
- this.list[sessionIdx] = session;
- }
- if (current.sessionId === id) {
- this.current.favorite = !wasInFavorite;
- }
+ runInAction(() => {
+ if (session) {
+ session.favorite = !wasInFavorite;
+ this.list[sessionIdx] = session;
+ }
+ if (current.sessionId === id) {
+ this.current.favorite = !wasInFavorite;
+ }
- if (wasInFavorite) {
- this.favoriteList = this.favoriteList.filter(
- ({ sessionId }) => sessionId !== id
- );
- } else {
- this.favoriteList.push(session);
- }
+ if (wasInFavorite) {
+ this.favoriteList = this.favoriteList.filter(
+ ({ sessionId }) => sessionId !== id
+ );
+ } else {
+ this.favoriteList.push(session);
+ }
+ })
} else {
console.error(r);
}
diff --git a/frontend/app/mstore/types/spot.ts b/frontend/app/mstore/types/spot.ts
index 0b0ce5975..c0ee25306 100644
--- a/frontend/app/mstore/types/spot.ts
+++ b/frontend/app/mstore/types/spot.ts
@@ -22,7 +22,7 @@ export class Spot {
this.comments = data.comments ?? [];
this.thumbnail = data.previewURL
this.title = data.name;
- this.createdAt = resentOrDate(new Date(data.createdAt).getTime());
+ this.createdAt = resentOrDate(new Date(data.createdAt).getTime(), true);
this.user = data.userEmail;
this.duration = shortDurationFromMs(data.duration);
this.spotId = data.id
diff --git a/frontend/app/mstore/uiPlayerStore.ts b/frontend/app/mstore/uiPlayerStore.ts
index d79e13c45..c4b880d4b 100644
--- a/frontend/app/mstore/uiPlayerStore.ts
+++ b/frontend/app/mstore/uiPlayerStore.ts
@@ -65,6 +65,11 @@ export default class UiPlayerStore {
startTs: 0,
endTs: 0,
}
+ highlightSelection = {
+ enabled: false,
+ startTs: 0,
+ endTs: 0,
+ }
zoomTab: 'overview' | 'journey' | 'issues' | 'errors' = 'overview'
dataSource: 'all' | 'current' = 'all'
@@ -113,6 +118,12 @@ export default class UiPlayerStore {
this.timelineZoom.endTs = payload.range?.[1] ?? 0;
}
+ toggleHighlightSelection = (payload: ToggleZoomPayload) => {
+ this.highlightSelection.enabled = payload.enabled;
+ this.highlightSelection.startTs = payload.range?.[0] ?? 0;
+ this.highlightSelection.endTs = payload.range?.[1] ?? 0;
+ }
+
setZoomTab = (tab: 'overview' | 'journey' | 'issues' | 'errors') => {
this.zoomTab = tab;
}
diff --git a/frontend/app/player/create.ts b/frontend/app/player/create.ts
index c9cd10472..8d04f777a 100644
--- a/frontend/app/player/create.ts
+++ b/frontend/app/player/create.ts
@@ -88,3 +88,23 @@ export function createLiveWebPlayer(
const player = new WebLivePlayer(store, session, config, agentId, projectId, uiErrorHandler)
return [player, store]
}
+
+export function createClipPlayer(
+ session: SessionFilesInfo,
+ wrapStore?: (s: IWebPlayerStore) => IWebPlayerStore,
+ uiErrorHandler?: { error: (msg: string) => void },
+ range?: [number, number]
+): [IWebPlayer, IWebPlayerStore] {
+ let store: WebPlayerStore = new SimpleStore
({
+ ...WebPlayer.INITIAL_STATE,
+ });
+ if (wrapStore) {
+ store = wrapStore(store);
+ }
+
+ const player = new WebPlayer(store, session, false, false, uiErrorHandler);
+ if (range && range[0] !== range[1]) {
+ player.toggleRange(range[0], range[1]);
+ }
+ return [player, store];
+}
\ No newline at end of file
diff --git a/frontend/app/player/player/Animator.ts b/frontend/app/player/player/Animator.ts
index 17ddfc93e..41c70b08c 100644
--- a/frontend/app/player/player/Animator.ts
+++ b/frontend/app/player/player/Animator.ts
@@ -195,11 +195,13 @@ export default class Animator {
}
// jump by index?
- jump = (time: number) => {
+ jump = (time: number, silent?: boolean) => {
if (this.store.get().playing && this.store.get().ready) {
cancelAnimationFrame(this.animationFrameRequestId)
this.setTime(time)
- this.startAnimation()
+ if (!silent) {
+ this.startAnimation()
+ }
this.store.update({ livePlay: time === this.store.get().endTime })
} else {
this.setTime(time)
diff --git a/frontend/app/player/player/Player.ts b/frontend/app/player/player/Player.ts
index d80e679db..eaa44ca6b 100644
--- a/frontend/app/player/player/Player.ts
+++ b/frontend/app/player/player/Player.ts
@@ -32,6 +32,7 @@ export default class Player extends Animator {
autoplay: initialAutoplay,
skip: initialSkip,
speed: initialSpeed,
+ range: [0, 0] as [number, number],
} as const
constructor(private pState: Store, private manager: IMessageManager) {
@@ -105,8 +106,11 @@ export default class Player extends Animator {
const { speed } = this.pState.get()
this.updateSpeed(Math.max(1, speed / 2))
}
- /* === === */
+ // toggle range (start, end)
+ toggleRange(start: number, end: number) {
+ this.pState.update({ range: [start, end] })
+ }
clean() {
this.pause()
diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts
index 1276a3208..a503ebdac 100644
--- a/frontend/app/player/web/WebPlayer.ts
+++ b/frontend/app/player/web/WebPlayer.ts
@@ -195,6 +195,11 @@ export default class WebPlayer extends Player {
this.screen.cursor.showTag(name)
}
+ // toggle range -> from super
+ toggleRange = (start: number, end: number) => {
+ super.toggleRange(start, end)
+ }
+
changeTab = (tab: string) => {
const playing = this.wpState.get().playing
this.pause()
diff --git a/frontend/app/routes.ts b/frontend/app/routes.ts
index 3104428b4..f19e45424 100644
--- a/frontend/app/routes.ts
+++ b/frontend/app/routes.ts
@@ -146,6 +146,8 @@ export const spotsList = (): string => '/spots';
export const spot = (id = ':spotId', hash?: string | number): string => hashed(`/view-spot/${id}`, hash);
export const scopeSetup = (): string => '/scope-setup';
+export const highlights = (): string => '/highlights';
+
const REQUIRED_SITE_ID_ROUTES = [
liveSession(''),
session(''),
@@ -188,6 +190,8 @@ const REQUIRED_SITE_ID_ROUTES = [
usabilityTestingCreate(),
usabilityTestingEdit(''),
usabilityTestingView(''),
+
+ highlights(),
];
const routeNeedsSiteId = (path: string): boolean => REQUIRED_SITE_ID_ROUTES.some(r => path.startsWith(r));
const siteIdToUrl = (siteId = ':siteId'): string => {
diff --git a/frontend/app/services/NotesService.ts b/frontend/app/services/NotesService.ts
index 18d307892..9ec5ba134 100644
--- a/frontend/app/services/NotesService.ts
+++ b/frontend/app/services/NotesService.ts
@@ -3,9 +3,8 @@ import APIClient from 'App/api_client';
export const tagProps = {
'ISSUE': 'red',
- 'QUERY': 'geekblue',
- 'TASK': 'purple',
- 'OTHER': '',
+ 'DESIGN': 'geekblue',
+ 'NOTE': 'purple',
}
export type iTag = keyof typeof tagProps | "ALL"
@@ -14,11 +13,12 @@ export const TAGS = Object.keys(tagProps) as unknown as (keyof typeof tagProps)[
export interface WriteNote {
message: string
- tag: iTag
+ tag: string
isPublic: boolean
- timestamp: number
- noteId?: string
- author?: string
+ timestamp?: number
+ startAt: number
+ endAt: number
+ thumbnail: string
}
export interface Note {
@@ -33,6 +33,9 @@ export interface Note {
timestamp: number
userId: number
userName: string
+ startAt: number
+ endAt: number
+ thumbnail: string
}
export interface NotesFilter {
@@ -43,6 +46,7 @@ export interface NotesFilter {
tags: iTag[]
sharedOnly: boolean
mineOnly: boolean
+ search: string
}
export default class NotesService {
@@ -62,6 +66,12 @@ export default class NotesService {
})
}
+ fetchNoteById(noteId: string): Promise {
+ return this.client.get(`/notes/${noteId}`).then(r => {
+ return r.json().then(r => r.data)
+ })
+ }
+
getNotesBySessionId(sessionID: string): Promise {
return this.client.get(`/sessions/${sessionID}/notes`)
.then(r => {
diff --git a/frontend/app/svg/icons/square-mouse-pointer.svg b/frontend/app/svg/icons/square-mouse-pointer.svg
new file mode 100644
index 000000000..027112256
--- /dev/null
+++ b/frontend/app/svg/icons/square-mouse-pointer.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/app/utils/index.ts b/frontend/app/utils/index.ts
index 79c492d20..8ba6cdcb5 100644
--- a/frontend/app/utils/index.ts
+++ b/frontend/app/utils/index.ts
@@ -401,37 +401,41 @@ export function simpleThrottle(func: (...args: any[]) => void, limit: number): (
};
}
-export function throttle(func, wait, options) {
- var context, args, result;
- var timeout = null;
- var previous = 0;
- if (!options) options = {};
- var later = function () {
- previous = options.leading === false ? 0 : Date.now();
- timeout = null;
- result = func.apply(context, args);
- if (!timeout) context = args = null;
- };
- return function () {
- var now = Date.now();
- if (!previous && options.leading === false) previous = now;
- var remaining = wait - (now - previous);
- context = this;
- args = arguments;
- if (remaining <= 0 || remaining > wait) {
- if (timeout) {
- clearTimeout(timeout);
- timeout = null;
- }
- previous = now;
- result = func.apply(context, args);
- if (!timeout) context = args = null;
- } else if (!timeout && options.trailing !== false) {
- timeout = setTimeout(later, remaining);
- }
- return result;
- };
-}
+export const throttle = (
+ fn: (...args: A) => R,
+ delay: number
+): [(...args: A) => R | undefined, () => void, () => void] => {
+ let wait = false;
+ let timeout: undefined | number;
+ let cancelled = false;
+
+ function resetWait() {
+ wait = false;
+ }
+
+ return [
+ (...args: A) => {
+ if (cancelled) return undefined;
+ if (wait) return undefined;
+
+ const val = fn(...args);
+
+ wait = true;
+
+ timeout = window.setTimeout(resetWait, delay);
+
+ return val;
+ },
+ () => {
+ cancelled = true;
+ clearTimeout(timeout);
+ },
+ () => {
+ clearTimeout(timeout);
+ resetWait();
+ },
+ ];
+};
export function deleteCookie(name: string, path: string, domain: string) {
document.cookie =
diff --git a/spot/utils/networkTrackingUtils.ts b/spot/utils/networkTrackingUtils.ts
index 0d5ea273c..b56e48ef8 100644
--- a/spot/utils/networkTrackingUtils.ts
+++ b/spot/utils/networkTrackingUtils.ts
@@ -167,3 +167,48 @@ export function tryFilterUrl(url: string) {
return url;
}
}
+
+export function mergeRequests(
+ webTrackedRequests: SpotNetworkRequest[],
+ proxyNetworkRequests: SpotNetworkRequest[],
+): SpotNetworkRequest[] {
+ const map = new Map();
+ const webReqClone = webTrackedRequests.map((r) => ({ ...r }));
+ const makeKey = (r: SpotNetworkRequest) =>
+ `${r.statusCode}::${r.method}::${r.url}::${Math.round(r.timestamp).toString().slice(0, -2) + "00"}`;
+
+ for (const proxyReq of proxyNetworkRequests) {
+ map.set(makeKey(proxyReq), proxyReq);
+ }
+
+ const merged: SpotNetworkRequest[] = [];
+ for (const webReq of webReqClone) {
+ if (webReq.url.includes("ingest/v1/web/i")) {
+ continue;
+ }
+ const key = makeKey(webReq);
+ const found = map.get(key);
+ if (found) {
+ if (
+ found.responseBody &&
+ found.responseBody.length > 0 &&
+ found.responseBody !== "{}"
+ ) {
+ webReq.responseBody = found.responseBody;
+ webReq.responseBodySize = found.responseBodySize;
+ if (webReq.encodedBodySize < found.encodedBodySize) {
+ webReq.encodedBodySize = found.encodedBodySize;
+ }
+ }
+ merged.push(webReq);
+ map.delete(key);
+ } else {
+ webReq.responseBody = JSON.stringify({
+ message: "Spot was unable to track this request's data",
+ });
+ merged.push(webReq);
+ }
+ }
+
+ return merged;
+}