diff --git a/frontend/app/IFrameRoutes.tsx b/frontend/app/IFrameRoutes.tsx index 434a6af56..b0a7d442f 100644 --- a/frontend/app/IFrameRoutes.tsx +++ b/frontend/app/IFrameRoutes.tsx @@ -10,6 +10,7 @@ import PublicRoutes from 'App/PublicRoutes'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; import * as routes from './routes'; +import Tracker from 'App/Tracker'; const components: any = { SessionPure: lazy(() => import('Components/Session/Session')), @@ -43,6 +44,7 @@ function IFrameRoutes(props: Props) { + }> = (props) => { + diff --git a/frontend/app/Tracker.ts b/frontend/app/Tracker.ts new file mode 100644 index 000000000..5c2c71147 --- /dev/null +++ b/frontend/app/Tracker.ts @@ -0,0 +1,3 @@ +export default function Tracker() { + return null; +} diff --git a/frontend/app/components/Assist/AssistSearchActions/AssistSearchActions.tsx b/frontend/app/components/Assist/AssistSearchActions/AssistSearchActions.tsx index 8069a96f3..deeb6cac6 100644 --- a/frontend/app/components/Assist/AssistSearchActions/AssistSearchActions.tsx +++ b/frontend/app/components/Assist/AssistSearchActions/AssistSearchActions.tsx @@ -6,8 +6,8 @@ import { MODULES } from 'Components/Client/Modules'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; import AssistStats from '../../AssistStats'; -import Recordings from '../RecordingsList/Recordings'; import { useTranslation } from 'react-i18next'; +import TrainingVideosBtn from './TrainingVideosBtn'; function AssistSearchActions() { const { t } = useTranslation(); @@ -23,12 +23,6 @@ function AssistSearchActions() { const showStats = () => { showModal(, { right: true, width: 960 }); }; - const showRecords = () => { - showModal(, { right: true, width: 960 }); - }; - - const originStr = window.env.ORIGIN || window.location.origin; - const isSaas = /app\.openreplay\.com/.test(originStr); return (

@@ -44,9 +38,7 @@ function AssistSearchActions() { {t('Clear')} - {!isSaas && isEnterprise && !modules.includes(MODULES.OFFLINE_RECORDINGS) - ? : null - } + {isEnterprise && userStore.account?.admin && ( : null + ) +} + +export default observer(TrainingVideosBtn); diff --git a/frontend/app/components/Client/Billing/Billing.tsx b/frontend/app/components/Client/Billing/Billing.tsx new file mode 100644 index 000000000..576038aff --- /dev/null +++ b/frontend/app/components/Client/Billing/Billing.tsx @@ -0,0 +1,3 @@ +export default function Billing() { + return null; +} \ No newline at end of file diff --git a/frontend/app/components/Client/Client.tsx b/frontend/app/components/Client/Client.tsx index e24cf9f91..f3a714f28 100644 --- a/frontend/app/components/Client/Client.tsx +++ b/frontend/app/components/Client/Client.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { withRouter } from 'react-router-dom'; import { Switch, Route, Redirect } from 'react-router'; -import { CLIENT_TABS, client as clientRoute } from 'App/routes'; +import { client as clientRoute } from 'App/routes'; +import { CLIENT_TABS } from 'App/utils/routeUtils'; import { PANEL_SIZES } from 'App/constants/panelSizes' import SessionsListingSettings from 'Components/Client/SessionsListingSettings'; @@ -10,7 +11,7 @@ import ProfileSettings from './ProfileSettings'; import Integrations from './Integrations'; import UserView from './Users/UsersView'; import AuditView from './Audit/AuditView'; -import Sites from './Sites'; +import Billing from './Billing/Billing'; import Projects from './Projects'; import CustomFields from './CustomFields'; import Webhooks from './Webhooks'; @@ -65,6 +66,12 @@ export default class Client extends React.PureComponent { path={clientRoute(CLIENT_TABS.CUSTOM_FIELDS)} component={CustomFields} /> + ([]); const onToggle = async (module: any) => { diff --git a/frontend/app/components/Client/Modules/extra.ts b/frontend/app/components/Client/Modules/extra.ts new file mode 100644 index 000000000..9dae85e76 --- /dev/null +++ b/frontend/app/components/Client/Modules/extra.ts @@ -0,0 +1,15 @@ +const extra = (t: any) => [] + +export const enum MODULES { + ASSIST = 'assist', + HIGHLIGHTS = 'notes', + BUG_REPORTS = 'bug-reports', + OFFLINE_RECORDINGS = 'offline-recordings', + ALERTS = 'alerts', + ASSIST_STATS = 'assist-stats', + FEATURE_FLAGS = 'feature-flags', + RECOMMENDATIONS = 'recommendations', + USABILITY_TESTS = 'usability-tests', +} + +export default extra; diff --git a/frontend/app/components/Client/Modules/index.ts b/frontend/app/components/Client/Modules/index.ts index 85425d5f2..5a88748f2 100644 --- a/frontend/app/components/Client/Modules/index.ts +++ b/frontend/app/components/Client/Modules/index.ts @@ -1,18 +1,9 @@ import { TFunction } from 'i18next'; +import extraModules, { MODULES } from './extra'; +export * from './extra'; export { default } from './Modules'; -export const enum MODULES { - ASSIST = 'assist', - HIGHLIGHTS = 'notes', - BUG_REPORTS = 'bug-reports', - OFFLINE_RECORDINGS = 'offline-recordings', - ALERTS = 'alerts', - ASSIST_STATS = 'assist-stats', - FEATURE_FLAGS = 'feature-flags', - RECOMMENDATIONS = 'recommendations', - USABILITY_TESTS = 'usability-tests', -} export interface Module { label: string; @@ -73,15 +64,6 @@ export const modules = (t: TFunction) => [ key: MODULES.FEATURE_FLAGS, icon: 'toggles', }, - { - label: t('Recommendations'), - description: t( - 'Get personalized recommendations for sessions to watch, based on your replay history and search preferences.', - ), - key: MODULES.RECOMMENDATIONS, - icon: 'magic', - hidden: true, - }, { label: t('Usability Tests'), description: t( @@ -90,4 +72,5 @@ export const modules = (t: TFunction) => [ key: MODULES.USABILITY_TESTS, icon: 'clipboard-check', }, + ...extraModules(t), ]; diff --git a/frontend/app/components/Clips/ClipsPage/index.ts b/frontend/app/components/Clips/ClipsPage/index.ts new file mode 100644 index 000000000..61790bafa --- /dev/null +++ b/frontend/app/components/Clips/ClipsPage/index.ts @@ -0,0 +1,3 @@ +function Clips() { + return null; +} diff --git a/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx b/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx index 4cccdb14b..95ade1977 100644 --- a/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx +++ b/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx @@ -11,7 +11,6 @@ import { AppWindow, Combine, Users, - Sparkles, Globe, MonitorSmartphone, } from 'lucide-react'; @@ -250,8 +249,6 @@ const AddCardSection = observer( { label: t('Web Analytics'), value: 'web_analytics' }, ]; - const originStr = window.env.ORIGIN || window.location.origin; - const isSaas = /api\.openreplay\.com/.test(originStr); const onExistingClick = () => { const dashboardId = dashboardStore.selectedDashboard?.dashboardId; const siteId = projectsStore.activeSiteId; @@ -273,12 +270,6 @@ const AddCardSection = observer(
{t('What do you want to visualize?')}
- {isSaas ? ( -
- -
{t('Ask AI')}
-
- ) : null}

{options.length > 1 ? ( diff --git a/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx b/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx index b7829ee37..5c9a1c699 100644 --- a/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx +++ b/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx @@ -1,12 +1,12 @@ -import { Card, Col, Modal, Row, Typography } from 'antd'; +import { Col, Modal, Row, Typography } from 'antd'; import { GalleryVertical, Plus } from 'lucide-react'; import React from 'react'; import { useStore } from 'App/mstore'; import NewDashboardModal from 'Components/Dashboard/components/DashboardList/NewDashModal'; -import AiQuery from './DashboardView/AiQuery'; import { useTranslation } from 'react-i18next'; +import AiQuerySection, { panelSize } from './AiQuerySection'; interface Props { open: boolean; @@ -32,8 +32,6 @@ function AddCardSelectionModal(props: Props) { setOpen(true); }; - const originStr = window.env.ORIGIN || window.location.origin; - const isSaas = /app\.openreplay\.com/.test(originStr); return ( <> +
+ + + +
+ {t('or')} +
+ + ) +} + +export const panelSize = 900 + */ + +function AiQuerySection() { + return null +} + +export const panelSize = undefined + +export default AiQuerySection diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index 64b67be76..f9da96601 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -4,7 +4,7 @@ import { useStore } from 'App/mstore'; import { Loader } from 'UI'; import { withSiteId } from 'App/routes'; import withModal from 'App/components/Modal/withModal'; -import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { RouteComponentProps } from 'react-router-dom'; import { useModal } from 'App/components/Modal'; import AlertFormModal from 'App/components/Alerts/AlertFormModal'; import withPageTitle from 'HOCs/withPageTitle'; @@ -89,9 +89,6 @@ function DashboardView(props: Props) { }, [dashboard]); if (!dashboard) return null; - - const originStr = window.env.ORIGIN || window.location.origin; - const isSaas = /app\.openreplay\.com/.test(originStr); return (
- {isSaas ? : null}
(PlayerContext); const screenWrapper = React.useRef(null); - const { time } = playerContext.store.get(); - const { range, isFull } = props; + const { time, error } = playerContext.store.get(); + const { range, isFull, isHighlight } = props; React.useEffect(() => { if (!playerContext.player) return; @@ -57,7 +58,16 @@ function ClipPlayerContent(props: Props) { const outerHeight = props.isHighlight ? 556 + 39 : 556; const innerHeight = props.isHighlight ? 504 + 39 : 504; - return ( + return error ? ( +
+
+ +
+
+ ) : (
- +
{props.message}
) : null} - +
); diff --git a/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerHeader.tsx b/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerHeader.tsx index b7fd37c9e..450b7f996 100644 --- a/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerHeader.tsx +++ b/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerHeader.tsx @@ -35,7 +35,7 @@ function ClipPlayerHeader(props: Props) { return (
{isHighlight && !isFull ? : null} - + {props.session ? : null} diff --git a/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerOverlay.tsx b/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerOverlay.tsx index d8ae4681e..e34db8b33 100644 --- a/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerOverlay.tsx +++ b/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerOverlay.tsx @@ -8,9 +8,10 @@ import AutoplayTimer from 'Components/Session/Player/ClipPlayer/AutoPlayTimer'; interface Props { autoplay: boolean; + isHighlight?: boolean; } -function Overlay({ autoplay }: Props) { +function Overlay({ autoplay, isHighlight }: Props) { const { player, store } = React.useContext(PlayerContext); const togglePlay = () => player.togglePlay(); @@ -19,9 +20,11 @@ function Overlay({ autoplay }: Props) { return ( <> {messagesLoading ? : null} - {/*
*/} - {/* */} - {/*
*/} + {isHighlight ? null : ( +
+ +
+ )} {completed && autoplay && } diff --git a/frontend/app/components/Session/Player/ClipPlayer/QueueControls.tsx b/frontend/app/components/Session/Player/ClipPlayer/QueueControls.tsx index 830ee23b6..d5ef44e2e 100644 --- a/frontend/app/components/Session/Player/ClipPlayer/QueueControls.tsx +++ b/frontend/app/components/Session/Player/ClipPlayer/QueueControls.tsx @@ -12,7 +12,7 @@ interface Props {} function QueueControls(props: Props) { const { t } = useTranslation(); - const { clipStore, projectsStore, sessionStore, searchStore } = useStore(); + const { clipStore } = useStore(); const previousId = clipStore.prevId; const { nextId } = clipStore; diff --git a/frontend/app/components/Session/Player/ClipPlayer/Timeline.tsx b/frontend/app/components/Session/Player/ClipPlayer/Timeline.tsx index c50d77bb0..751d999f2 100644 --- a/frontend/app/components/Session/Player/ClipPlayer/Timeline.tsx +++ b/frontend/app/components/Session/Player/ClipPlayer/Timeline.tsx @@ -132,17 +132,7 @@ function Timeline({ range }: any) { // }; return ( -
+
0; - // @ts-ignore - const originStr = window.env.ORIGIN || window.location.origin; - const isSaas = /app\.openreplay\.com/.test(originStr); const showSummary = () => { player.pause(); @@ -197,13 +194,11 @@ const DevtoolsButtons = observer( }; return ( <> - {isSaas ? ( - - ) : null} + diff --git a/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx b/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx index 3d9a8576e..971b9ebc8 100644 --- a/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx @@ -143,6 +143,7 @@ function Player(props: IProps) { isInspMode ? stl.solidBg : stl.checkers, )} ref={screenWrapper} + data-openreplay-obscured />
{!fullscreen && !!bottomBlock && ( diff --git a/frontend/app/components/Session/Player/ReplayPlayer/SummaryBlock/index.tsx b/frontend/app/components/Session/Player/ReplayPlayer/SummaryBlock/index.tsx index 1444790ff..deb72a8be 100644 --- a/frontend/app/components/Session/Player/ReplayPlayer/SummaryBlock/index.tsx +++ b/frontend/app/components/Session/Player/ReplayPlayer/SummaryBlock/index.tsx @@ -7,15 +7,10 @@ import { debounce } from 'App/utils'; import { IResourceRequest, IResourceTiming } from 'App/player'; import { WsChannel } from 'App/player/web/messages'; import { PlayerContext } from 'App/components/Session/playerContext'; +import MDRenderer from 'Shared/MDRenderer/MDRenderer'; let debounceUpdate: any = () => {}; -const boldLine = /\*\*(.*?)\*\*/i; - -function isTitleLine(line: string): boolean { - return boldLine.test(line); -} - function SummaryBlock({ sessionId }: { sessionId: string }) { const { store } = React.useContext(PlayerContext); const { tabStates } = store.get(); @@ -24,7 +19,7 @@ function SummaryBlock({ sessionId }: { sessionId: string }) { const zoomEnabled = uiPlayerStore.timelineZoom.enabled; const zoomStartTs = uiPlayerStore.timelineZoom.startTs; const zoomEndTs = uiPlayerStore.timelineZoom.endTs; - const { zoomTab } = uiPlayerStore; + const zoomTab = uiPlayerStore.zoomTab; React.useEffect(() => { debounceUpdate = debounce( @@ -76,31 +71,13 @@ function SummaryBlock({ sessionId }: { sessionId: string }) { } }, [zoomTab]); - const formattedText = aiSummaryStore.text.split('\n').map((line) => { - if (isTitleLine(line)) { - return ( -
{line.replace(/\*/g, '')}
- ); - } - if (line.startsWith('*')) { - return ( -
  • - -
  • - ); - } - return ( -
    - -
    - ); - }); - return (
    {aiSummaryStore.text ? ( -
    - {formattedText.map((v) => v)} +
    +
    ) : ( @@ -111,44 +88,31 @@ function SummaryBlock({ sessionId }: { sessionId: string }) { function TextPlaceholder() { return ( -
    -
    -
    -
    -
    -
    +
    +
    +
    +
    +
    +
    -
    -
    -
    -
    +
    +
    +
    +
    -
    -
    -
    +
    +
    +
    ); } -function CodeStringFormatter({ text }: { text: string }) { - const parts = text.split(/(`[^`]*`)/).map((part, index) => - part.startsWith('`') && part.endsWith('`') ? ( -
    - {part.substring(1, part.length - 1)} -
    - ) : ( - {part} - ), - ); - - return <>{parts}; -} - const summaryBlockStyle: React.CSSProperties = { background: 'linear-gradient(180deg, #E8EBFF -24.14%, rgba(236, 254, 255, 0.00) 100%)', diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index af0a2951e..3f1021260 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -10,7 +10,7 @@ import { } from 'App/components/Session/playerContext'; import { useStore } from 'App/mstore'; import SummaryBlock from 'Components/Session/Player/ReplayPlayer/SummaryBlock'; -import { SummaryButton } from 'Components/Session_/Player/Controls/Controls'; +import SummaryButton from 'Components/Session_/Player/Controls/SummaryButton'; import TimelineZoomButton from 'Components/Session_/Player/Controls/components/TimelineZoomButton'; import { Icon, NoContent } from 'UI'; import TabSelector from '../../shared/DevTools/TabSelector'; @@ -99,8 +99,6 @@ function MobileOverviewPanelCont() { player.scale(); }, [selectedFeatures]); - const originStr = window.env.ORIGIN || window.location.origin; - const isSaas = /app\.openreplay\.com/.test(originStr); return ( aiSummaryStore.setToggleSummary(!aiSummaryStore.toggleSummary) } @@ -229,8 +227,6 @@ function WebOverviewPanelCont() { ], ); - const originStr = window.env.ORIGIN || window.location.origin; - const isSaas = /app\.openreplay\.com/.test(originStr); return ( aiSummaryStore.setToggleSummary(!aiSummaryStore.toggleSummary) } diff --git a/frontend/app/components/Session_/Player/Controls/Controls.tsx b/frontend/app/components/Session_/Player/Controls/Controls.tsx index a7038da7b..dfe7b2ed9 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.tsx +++ b/frontend/app/components/Session_/Player/Controls/Controls.tsx @@ -18,6 +18,7 @@ import { LaunchStateShortcut, LaunchXRaShortcut, } from 'Components/Session_/Player/Controls/components/KeyboardHelp'; +import { signalService } from 'App/services'; import { CONSOLE, GRAPHQL, @@ -29,12 +30,15 @@ import { STACKEVENTS, STORAGE, BACKENDLOGS, - LONG_TASK -} from "App/mstore/uiPlayerStore"; +} from 'App/mstore/uiPlayerStore'; import { Icon } from 'UI'; import LogsButton from 'App/components/Session/Player/SharedComponents/BackendLogs/LogsButton'; -import { CodeOutlined, DashboardOutlined, ClusterOutlined } from '@ant-design/icons'; -import { ArrowDownUp, ListCollapse, Merge, Waypoints, Timer } from 'lucide-react' +import { + CodeOutlined, + DashboardOutlined, + ClusterOutlined, +} from '@ant-design/icons'; +import { ArrowDownUp, ListCollapse, Merge, Waypoints } from 'lucide-react'; import ControlButton from './ControlButton'; import Timeline from './Timeline'; @@ -55,15 +59,30 @@ export const SKIP_INTERVALS = { function getStorageName(type: any) { switch (type) { case STORAGE_TYPES.REDUX: - return { name: 'Redux', icon: }; + return { + name: 'Redux', + icon: , + }; case STORAGE_TYPES.MOBX: - return { name: 'Mobx', icon: }; + return { + name: 'Mobx', + icon: , + }; case STORAGE_TYPES.VUEX: - return { name: 'Vuex', icon: }; + return { + name: 'Vuex', + icon: , + }; case STORAGE_TYPES.NGRX: - return { name: 'NgRx', icon: }; + return { + name: 'NgRx', + icon: , + }; case STORAGE_TYPES.ZUSTAND: - return { name: 'Zustand', icon: }; + return { + name: 'Zustand', + icon: , + }; case STORAGE_TYPES.NONE: return { name: 'State', icon: }; default: @@ -80,6 +99,7 @@ function Controls({ setActiveTab, activeTab }: any) { sessionStore, userStore, } = useStore(); + const [mounted, setMounted] = React.useState(false); const permissions = userStore.account.permissions || []; const disableDevtools = userStore.isEnterprise && @@ -114,6 +134,7 @@ function Controls({ setActiveTab, activeTab }: any) { const disabled = disableDevtools || messagesLoading || inspectorMode || markedTargets; const sessionTz = session?.timezone; + const sessionId = session?.sessionId; const nextHandler = () => { history.push(withSiteId(sessionRoute(nextSessionId), siteId)); @@ -134,19 +155,63 @@ function Controls({ setActiveTab, activeTab }: any) { disableDevtools, }); + React.useEffect(() => { + setMounted(true); + }, []); + + React.useEffect(() => { + if (mounted) { + signalService.send( + { + source: 'speed', + value: speed, + }, + sessionId, + ); + } + }, [speed]); + const forthTenSeconds = () => { // @ts-ignore player.jumpInterval(SKIP_INTERVALS[skipInterval]); + signalService.send( + { + source: 'fast_forward', + }, + sessionId, + ); }; const backTenSeconds = () => { // @ts-ignore player.jumpInterval(-SKIP_INTERVALS[skipInterval]); + signalService.send( + { + source: 'rewind', + }, + sessionId, + ); }; const toggleBottomTools = (blockName: number) => { player.toggleInspectorMode(false); toggleBottomBlock(blockName); + signalService.send( + { + source: getBlockLabel(blockName)!, + }, + sessionId, + ); + }; + + const togglePlay = () => { + player.togglePlay(); + signalService.send( + { + source: playing ? 'pause' : 'play', + }, + sessionId, + ); }; const state = completed @@ -174,7 +239,7 @@ function Controls({ setActiveTab, activeTab }: any) { playButton={ } @@ -231,11 +296,6 @@ const DevtoolsButtons = observer( const { t } = useTranslation(); const { aiSummaryStore, integrationsStore } = useStore(); const { store, player } = React.useContext(PlayerContext); - - // @ts-ignore - const originStr = window.env.ORIGIN || window.location.origin; - const isSaas = /app\.openreplay\.com/.test(originStr); - const { inspectorMode, currentTab, tabStates } = store.get(); const disableButtons = disabled; @@ -299,12 +359,13 @@ const DevtoolsButtons = observer( icon: , label: t('Long Tasks'), }, - } + }; // @ts-ignore - const getLabel = (block: string) => labels[block][showIcons ? 'icon' : 'label'] + const getLabel = (block: string) => + labels[block][showIcons ? 'icon' : 'label']; return ( <> - {isSaas ? : null} + @@ -364,14 +425,6 @@ const DevtoolsButtons = observer( label={getLabel('performance')} /> - toggleBottomTools(LONG_TASK)} - active={bottomBlock === LONG_TASK && !inspectorMode} - label={getLabel('longTask')} - /> - {showGraphql && ( void; + withToggle?: boolean; + onToggle?: () => void; + toggleValue?: boolean; +}) { + const { t } = useTranslation(); + const [isHovered, setHovered] = React.useState(false); + + return ( +
    +
    setHovered(true)} + onMouseLeave={() => setHovered(false)} + > + {withToggle ? ( + + ) : null} + +
    {t('Summary AI')}
    +
    +
    + ); +} +*/ + +function SummaryButton(props: any) { + return null; +} + +export default SummaryButton; diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.tsx b/frontend/app/components/Session_/Player/Controls/Timeline.tsx index 81cddcee7..985035f91 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.tsx +++ b/frontend/app/components/Session_/Player/Controls/Timeline.tsx @@ -16,6 +16,7 @@ import { import stl from './timeline.module.css' import TooltipContainer from './components/TooltipContainer'; import CustomDragLayer, { OnDragCallback } from './components/CustomDragLayer'; +import { signalService } from 'App/services'; function Timeline({ isMobile }: { isMobile: boolean }) { const { player, store } = useContext(PlayerContext); @@ -32,6 +33,7 @@ function Timeline({ isMobile }: { isMobile: boolean }) { const highlightEnabled = uiPlayerStore.highlightSelection.enabled; const { playing, skipToIssue, ready, endTime, devtoolsLoading, domLoading } = store.get(); + const sessionId = sessionStore.current.sessionId; const progressRef = useRef(null); const timelineRef = useRef(null); @@ -67,6 +69,10 @@ function Timeline({ isMobile }: { isMobile: boolean }) { const time = Math.max(Math.round(p * endTime), 0); debouncedJump(time); hideTimeTooltip(); + signalService.send({ + source: 'jump', + value: time, + }, sessionId) if (playing) { setWasPlaying(true); player.pause(); diff --git a/frontend/app/components/Session_/Player/Overlay.tsx b/frontend/app/components/Session_/Player/Overlay.tsx index 015b7af1b..37a6e549f 100644 --- a/frontend/app/components/Session_/Player/Overlay.tsx +++ b/frontend/app/components/Session_/Player/Overlay.tsx @@ -5,7 +5,6 @@ import React from 'react'; import { Link2 } from 'lucide-react'; import copy from 'copy-to-clipboard'; import { toast } from 'react-toastify'; - import { PlayerContext } from 'App/components/Session/playerContext'; import { CONSOLE, diff --git a/frontend/app/components/Session_/SimilarSessions/SimilarSessionsButton.tsx b/frontend/app/components/Session_/SimilarSessions/SimilarSessionsButton.tsx new file mode 100644 index 000000000..c33cc133b --- /dev/null +++ b/frontend/app/components/Session_/SimilarSessions/SimilarSessionsButton.tsx @@ -0,0 +1,5 @@ +function SimilarSessionsButton() { + return null +} + +export default SimilarSessionsButton; diff --git a/frontend/app/components/Session_/Subheader.tsx b/frontend/app/components/Session_/Subheader.tsx index 313b346ca..107b7bf0e 100644 --- a/frontend/app/components/Session_/Subheader.tsx +++ b/frontend/app/components/Session_/Subheader.tsx @@ -27,6 +27,7 @@ import QueueControls from './QueueControls'; import HighlightButton from './Highlight/HighlightButton'; import ShareModal from '../shared/SharePopup/SharePopup'; import { useTranslation } from 'react-i18next'; +import SimilarSessionsButton from './SimilarSessions/SimilarSessionsButton'; const disableDevtools = 'or_devtools_uxt_toggle'; @@ -167,6 +168,7 @@ function SubHeader(props) { )} style={{ width: 'max-content' }} > + { toast.success(isFavorite ? REMOVED_MESSAGE : ADDED_MESSAGE); setIsFavorite(!isFavorite); + + signalService.send( + { + source: 'vault', + value: !isFavorite, + }, + sessionId, + ); }); }; diff --git a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx index 138501c49..6d50700ab 100644 --- a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx +++ b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import cn from 'classnames'; +import ExplainButton from 'Shared/DevTools/ExplainButton'; import { Icon } from 'UI'; import JumpButton from 'Shared/DevTools/JumpButton'; import TabTag from '../TabTag'; @@ -13,6 +14,7 @@ interface Props { onClick?: () => void; getTabNum?: (tab: string) => number; showSingleTab: boolean; + sessionId: string; } function ConsoleRow(props: Props) { const { log, iconProps, jump, renderWithNL, style } = props; @@ -106,7 +108,21 @@ function ConsoleRow(props: Props) {
    ))}
    - jump(log.time)} /> + + } + time={log.time} + onClick={() => { + jump(log.time); + }} + />
    ); } diff --git a/frontend/app/components/shared/DevTools/ExplainButton.tsx b/frontend/app/components/shared/DevTools/ExplainButton.tsx new file mode 100644 index 000000000..c7b8d7404 --- /dev/null +++ b/frontend/app/components/shared/DevTools/ExplainButton.tsx @@ -0,0 +1,3 @@ +export default function ExplainButton(props: any) { + return null; +} diff --git a/frontend/app/components/shared/DevTools/JumpButton/JumpButton.tsx b/frontend/app/components/shared/DevTools/JumpButton/JumpButton.tsx index 99b87dd9e..723d26925 100644 --- a/frontend/app/components/shared/DevTools/JumpButton/JumpButton.tsx +++ b/frontend/app/components/shared/DevTools/JumpButton/JumpButton.tsx @@ -8,15 +8,14 @@ interface Props { onClick: any; time?: number; tooltip?: string; + extra?: React.ReactNode; } function JumpButton(props: Props) { const { tooltip } = props; return ( -
    - {props.time ? ( -
    - {shortDurationFromMs(props.time)} -
    +
    + {props.extra ? ( +
    {props.extra}
    ) : null} + {props.time ? ( +
    + {shortDurationFromMs(props.time)} +
    + ) : null}
    ); diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index aee9a21ae..e4a8469dc 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -35,6 +35,8 @@ import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; import WSPanel from './WSPanel'; import { useTranslation } from 'react-i18next'; import { mergeListsWithZoom, processInChunks } from './utils'; +import check from './hasExplainAi'; +import ExplainButton from '../ExplainButton'; // Constants remain the same const INDEX_KEY = 'network'; @@ -734,6 +736,10 @@ export const NetworkPanelComp = observer( return cols; }, [showSingleTab, activeTab, t, getTabName, getTabNum, isSpot]); + const hasExplainAi = (reqType: string) => { + // @ts-ignore + return check && [ResourceType.XHR, ResourceType.FETCH].includes(reqType); + }; return ( + hasExplainAi(row.type) ? ( + + ) : null + } > {tableCols} diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/hasExplainAi.ts b/frontend/app/components/shared/DevTools/NetworkPanel/hasExplainAi.ts new file mode 100644 index 000000000..33136544d --- /dev/null +++ b/frontend/app/components/shared/DevTools/NetworkPanel/hasExplainAi.ts @@ -0,0 +1 @@ +export default false; diff --git a/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx index 263325502..2751ce1f8 100644 --- a/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx +++ b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx @@ -70,6 +70,7 @@ type Props = { hoverable?: boolean; onRowClick?: (row: any, index: number) => void; onJump?: (obj: { time: number }) => void; + extra?: (row: Record) => React.ReactNode; }; type TimeLineInfo = { @@ -145,7 +146,10 @@ function TimeTable(props: Props) { ]); React.useEffect(() => { if (props.activeIndex && props.activeIndex >= 0 && scroller.current) { - scroller.current.scrollToIndex(props.activeIndex, { align: 'center', smooth: false }); + scroller.current.scrollToIndex(props.activeIndex, { + align: 'center', + smooth: false, + }); setFirstVisibleRowIndex(props.activeIndex ?? 0); } }, [props.activeIndex]); @@ -296,6 +300,7 @@ function TimeTable(props: Props) { onRowClick={onRowClick} activeIndex={activeIndex} onJump={onJump} + extra={props.extra} /> )} @@ -316,6 +321,7 @@ function RowRenderer({ onRowClick, activeIndex, onJump, + extra, }: any) { if (!row) return; return ( @@ -350,7 +356,10 @@ function RowRenderer({ popup={renderPopup} />
    - onJump(index)} /> + onJump(index)} + />
    ); } diff --git a/frontend/app/components/shared/MDRenderer/MDRenderer.tsx b/frontend/app/components/shared/MDRenderer/MDRenderer.tsx new file mode 100644 index 000000000..339153196 --- /dev/null +++ b/frontend/app/components/shared/MDRenderer/MDRenderer.tsx @@ -0,0 +1,4 @@ +export default function MDRenderer(props: any) { + console.warn('saas comp') + return null; +} diff --git a/frontend/app/components/shared/ProjectDropdown/ProjectDropdown.tsx b/frontend/app/components/shared/ProjectDropdown/ProjectDropdown.tsx index c14b90be2..8222a82cb 100644 --- a/frontend/app/components/shared/ProjectDropdown/ProjectDropdown.tsx +++ b/frontend/app/components/shared/ProjectDropdown/ProjectDropdown.tsx @@ -17,7 +17,7 @@ const { Text } = Typography; function ProjectDropdown(props: { location: any }) { const mstore = useStore(); const { t } = useTranslation(); - const { projectsStore, searchStore, searchStoreLive, userStore } = mstore; + const { projectsStore, searchStore, searchStoreLive, userStore, aiFiltersStore } = mstore; const { account } = userStore; const sites = projectsStore.list; const { siteId } = projectsStore; @@ -32,6 +32,7 @@ function ProjectDropdown(props: { location: any }) { const handleSiteChange = async (newSiteId: string) => { mstore.initClient(); + aiFiltersStore.clearFilters(); setSiteId(newSiteId); searchStore.clearSearch(); searchStore.clearList(); diff --git a/frontend/app/components/shared/SearchActions/AiSearchSection.tsx b/frontend/app/components/shared/SearchActions/AiSearchSection.tsx new file mode 100644 index 000000000..4f8d21a13 --- /dev/null +++ b/frontend/app/components/shared/SearchActions/AiSearchSection.tsx @@ -0,0 +1,14 @@ +/** Saas +import React from 'react' +import AiSessionSearchField from 'Shared/SessionFilters/AiSessionSearchField'; + +function AiSearchSection() { + return +} +*/ + +function AiSearchSection() { + return null +} + +export default AiSearchSection diff --git a/frontend/app/components/shared/SearchActions/SearchActions.tsx b/frontend/app/components/shared/SearchActions/SearchActions.tsx index 5f88ac4fd..199446a34 100644 --- a/frontend/app/components/shared/SearchActions/SearchActions.tsx +++ b/frontend/app/components/shared/SearchActions/SearchActions.tsx @@ -3,7 +3,7 @@ import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import { observer } from 'mobx-react-lite'; import { useStore } from 'App/mstore'; import { Button, Tooltip } from 'antd'; -import AiSessionSearchField from 'Shared/SessionFilters/AiSessionSearchField'; +import AiSearchSection from './AiSearchSection'; import { useTranslation } from 'react-i18next'; import SavedSearch from '../SavedSearch/SavedSearch'; @@ -30,13 +30,11 @@ function SearchActions() { return t('Sessions'); }, [activeTab?.type, isEnterprise, i18n.language]); - // @ts-ignore - const originStr = window.env.ORIGIN || window.location.origin; - const isSaas = /app\.openreplay\.com/.test(originStr); - const showAiField = isSaas && activeTab?.type === 'sessions'; + const showAiField = activeTab?.type === 'sessions'; const showPanel = hasEvents || hasFilters || aiFiltersStore.isLoading; return !metaLoading ? (
    + {/* mobile */}

    {title}

    @@ -54,12 +52,13 @@ function SearchActions() {
    - {isSaas ? : null} +
    + {/* desktop */}

    {title}

    - {isSaas && showAiField ? : null} + {showAiField ? : null}
    diff --git a/frontend/app/components/shared/SessionFilters/AiSessionSearchField.tsx b/frontend/app/components/shared/SessionFilters/AiSessionSearchField.tsx index 247cd42b5..96cc8e0a8 100644 --- a/frontend/app/components/shared/SessionFilters/AiSessionSearchField.tsx +++ b/frontend/app/components/shared/SessionFilters/AiSessionSearchField.tsx @@ -1,100 +1,5 @@ -import { CloseOutlined, EnterOutlined } from '@ant-design/icons'; -import { observer } from 'mobx-react-lite'; -import React, { useState } from 'react'; -import { useStore } from 'App/mstore'; -import { Input } from 'UI'; - -const AiSearchField = observer(() => { - const { searchStore } = useStore(); - const appliedFilter = searchStore.instance; - const hasFilters = - appliedFilter && appliedFilter.filters && appliedFilter.filters.length > 0; - const { aiFiltersStore } = useStore(); - const [searchQuery, setSearchQuery] = useState(''); - - const onSearchChange = ({ target: { value } }: any) => { - setSearchQuery(value); - }; - - const fetchResults = () => { - if (searchQuery) { - void aiFiltersStore.getSearchFilters(searchQuery); - } - }; - - const handleKeyDown = (event: any) => { - if (event.key === 'Enter') { - fetchResults(); - } - }; - - const clearAll = () => { - searchStore.clearSearch(); - setSearchQuery(''); - }; - - React.useEffect(() => { - if (aiFiltersStore.filtersSetKey !== 0) { - searchStore.edit(aiFiltersStore.filters); - } - }, [aiFiltersStore.filters, aiFiltersStore.filtersSetKey]); - - return ( -
    - -
    - {hasFilters ? : } -
    -
    - ) : null - } - /> -
    - ); -}); - function AiSessionSearchField() { - const { aiFiltersStore } = useStore(); - - return ( -
    -
    - -
    -
    - ); + return null; } -export const gradientBox = { - border: 'double 1.5px transparent', - borderRadius: '100px', - background: - 'linear-gradient(#ffffff, #ffffff), linear-gradient(-45deg, #394eff, #3eaaaf, #3ccf65)', - backgroundOrigin: 'border-box', - backgroundSize: '200% 200%', - backgroundClip: 'content-box, border-box', - display: 'flex', - gap: '0.25rem', - alignItems: 'center', - width: '100%', - overflow: 'hidden', -}; - -export default observer(AiSessionSearchField); +export default AiSessionSearchField; diff --git a/frontend/app/components/shared/SharePopup/SharePopup.tsx b/frontend/app/components/shared/SharePopup/SharePopup.tsx index f34d99488..93ca7a94c 100644 --- a/frontend/app/components/shared/SharePopup/SharePopup.tsx +++ b/frontend/app/components/shared/SharePopup/SharePopup.tsx @@ -7,6 +7,7 @@ import { useStore } from 'App/mstore'; import SessionCopyLink from './SessionCopyLink'; import IntegrateSlackButton from '../IntegrateSlackButton/IntegrateSlackButton'; import { useTranslation } from 'react-i18next'; +import { signalService } from 'App/services'; interface Channel { webhookId: string; @@ -126,7 +127,19 @@ const ShareModalComp: React.FC = ({ showCopyLink, hideModal, time }) => { ); const sendMsg = async () => { - shareTo === 'slack' ? await shareToSlack() : await shareToMSTeams(); + if (shareTo === 'slack') { + await shareToSlack(); + } else { + await shareToMSTeams(); + } + + signalService.send( + { + source: 'share', + value: shareTo, + }, + sessionId, + ); }; const hasBoth = slackOptions.length > 0 && msTeamsOptions.length > 0; diff --git a/frontend/app/dateRange.js b/frontend/app/dateRange.js index 7c0104690..ee8318c20 100644 --- a/frontend/app/dateRange.js +++ b/frontend/app/dateRange.js @@ -17,6 +17,12 @@ const DATE_RANGE_LABELS = { [CUSTOM_RANGE]: 'Custom Range', }; +const LONG_RANGE_LABELS = { + LAST_3_MONTHS: 'Last 3 Months', + LAST_YEAR: 'Last Year', + CUSTOM_RANGE: 'Custom Range', +} + const COMPARISON_DATE_RANGE_LABELS = { PREV_24_HOURS: 'Previous Day', PREV_7_DAYS: 'Previous Week', @@ -26,18 +32,28 @@ const COMPARISON_DATE_RANGE_LABELS = { }; const DATE_RANGE_VALUES = {}; +const LONG_RANGE_VALUES = {}; Object.keys(DATE_RANGE_LABELS).forEach((key) => { DATE_RANGE_VALUES[key] = key; }); +Object.keys(LONG_RANGE_LABELS).forEach((key) => { + LONG_RANGE_VALUES[key] = key; +}); -export { DATE_RANGE_VALUES }; +export { DATE_RANGE_VALUES, LONG_RANGE_LABELS }; export const dateRangeValues = Object.keys(DATE_RANGE_VALUES); -export const DATE_RANGE_OPTIONS = +export const DATE_RANGE_OPTIONS = Object.keys(DATE_RANGE_LABELS).map((key) => ({ label: DATE_RANGE_LABELS[key], value: key, })); +export const LONG_DATE_RANGE_OPTIONS = + Object.keys(LONG_RANGE_LABELS).map((key) => ({ + label: LONG_RANGE_LABELS[key], + value: key, + })); + export const DATE_RANGE_COMPARISON_OPTIONS = Object.keys(COMPARISON_DATE_RANGE_LABELS).map((key) => ({ label: COMPARISON_DATE_RANGE_LABELS[key], @@ -45,9 +61,11 @@ export const DATE_RANGE_COMPARISON_OPTIONS = })); export function getDateRangeLabel(value, t) { - return t(DATE_RANGE_LABELS[value]); + const string = DATE_RANGE_LABELS[value] ?? LONG_RANGE_LABELS[value]; + return t(string); } + export function getDateRangeFromValue(value) { const tz = JSON.parse(localStorage.getItem(TIMEZONE)); const offset = tz ? tz.value : undefined; @@ -106,6 +124,14 @@ export function getDateRangeFromValue(value) { // return Interval.fromDateTimes(now.startOf('year'), now.endOf('year')); // case DATE_RANGE_VALUES.CUSTOM_RANGE: // return Interval.fromDateTimes(now, now); + case LONG_RANGE_VALUES.LAST_YEAR: + const lastYear = now.minus({ years: 1 }); + return Interval.fromDateTimes(lastYear.startOf('year'), lastYear.endOf('year')); + case LONG_RANGE_VALUES.LAST_3_MONTHS: + return Interval.fromDateTimes( + now.minus({ months: 3 }).startOf('month'), + now.endOf('month'), + ); default: throw new Error('Invalid date range value'); } diff --git a/frontend/app/extraRoutes.ts b/frontend/app/extraRoutes.ts new file mode 100644 index 000000000..d381719f6 --- /dev/null +++ b/frontend/app/extraRoutes.ts @@ -0,0 +1,2 @@ +export const routeIdRequired = []; +export const changeAvailable = []; diff --git a/frontend/app/layout/CrispIframe.tsx b/frontend/app/layout/CrispIframe.tsx new file mode 100644 index 000000000..4b4b5baf3 --- /dev/null +++ b/frontend/app/layout/CrispIframe.tsx @@ -0,0 +1,3 @@ +export default function CrispIframe({ WEBSITE_ID }: { WEBSITE_ID?: string }) { + return null; +} diff --git a/frontend/app/layout/SupportModal.tsx b/frontend/app/layout/SupportModal.tsx index c6013e3dd..76c419381 100644 --- a/frontend/app/layout/SupportModal.tsx +++ b/frontend/app/layout/SupportModal.tsx @@ -2,7 +2,7 @@ import { ArrowRightOutlined } from '@ant-design/icons'; import { Button, Drawer, Space, Typography } from 'antd'; import React from 'react'; import { useTranslation } from 'react-i18next'; - +import CrispIframe from './CrispIframe'; import { Icon } from 'UI'; const { Text } = Typography; @@ -126,19 +126,7 @@ function SupportModal(props: Props) {
    - {!!WEBSITE_ID && ( -
    -