From 7f2ca0e841b603cfee5db634436720c3e785d44f Mon Sep 17 00:00:00 2001 From: pauldambra Date: Mon, 29 Sep 2025 15:21:56 +0100 Subject: [PATCH 1/4] a little button fiddling --- .../player/controller/PlayerController.tsx | 7 +++++-- .../player/sessionRecordingPlayerLogic.ts | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx b/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx index b356722d02461..011350d5a34fc 100644 --- a/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx +++ b/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx @@ -12,6 +12,7 @@ import { EmojiCommentOnRecordingButton, } from 'scenes/session-recordings/player/commenting/CommentOnRecordingButton' import { + ModesThatCanHavePlayerControllerButtons, SessionRecordingPlayerMode, sessionRecordingPlayerLogic, } from 'scenes/session-recordings/player/sessionRecordingPlayerLogic' @@ -155,15 +156,17 @@ export function PlayerController(): JSX.Element {
- {!isCinemaMode && playerMode === SessionRecordingPlayerMode.Standard && ( + {!isCinemaMode && ModesThatCanHavePlayerControllerButtons.includes(playerMode) && ( <> - {playlistLogic ? : undefined} )} + {playlistLogic && ModesThatCanHavePlayerControllerButtons.includes(playerMode) ? ( + + ) : undefined} {playerMode === SessionRecordingPlayerMode.Standard && }
diff --git a/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts b/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts index adfac058e39e9..fe6c62e22f837 100644 --- a/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts +++ b/frontend/src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts @@ -113,6 +113,7 @@ export enum SessionRecordingPlayerMode { } const ModesThatCanBeMarkedViewed = [SessionRecordingPlayerMode.Standard, SessionRecordingPlayerMode.Notebook] +export const ModesThatCanHavePlayerControllerButtons = ModesThatCanBeMarkedViewed export interface SessionRecordingPlayerLogicProps extends SessionRecordingDataLogicProps { playerKey: string From b93c1ec8ac439c882e4f7a0cb3dee4fb05cf6886 Mon Sep 17 00:00:00 2001 From: pauldambra Date: Mon, 29 Sep 2025 15:51:46 +0100 Subject: [PATCH 2/4] feat: notebooks can just use normal comments --- .../notebooks/Nodes/NotebookNodePlaylist.tsx | 15 +------ .../notebooks/Nodes/NotebookNodeRecording.tsx | 31 +------------- .../Nodes/NotebookNodeReplayTimestamp.tsx | 14 ------- .../notebooks/Nodes/notebookNodeLogic.ts | 17 -------- .../notebooks/Notebook/notebookLogic.ts | 40 ------------------- .../player/player-meta/PlayerMetaLinks.tsx | 22 +--------- 6 files changed, 5 insertions(+), 134 deletions(-) diff --git a/frontend/src/scenes/notebooks/Nodes/NotebookNodePlaylist.tsx b/frontend/src/scenes/notebooks/Nodes/NotebookNodePlaylist.tsx index ea48e9178f642..57862e81524f9 100644 --- a/frontend/src/scenes/notebooks/Nodes/NotebookNodePlaylist.tsx +++ b/frontend/src/scenes/notebooks/Nodes/NotebookNodePlaylist.tsx @@ -2,8 +2,6 @@ import { BuiltLogic, useActions, useValues } from 'kea' import { PostHogErrorBoundary } from 'posthog-js/react' import { useEffect, useMemo } from 'react' -import { IconComment } from '@posthog/icons' - import { JSONContent } from 'lib/components/RichContentEditor/types' import { useOnMountEffect } from 'lib/hooks/useOnMountEffect' import { createPostHogWidgetNode } from 'scenes/notebooks/Nodes/NodeWrapper' @@ -51,8 +49,7 @@ const Component = ({ [playerKey, universalFilters, pinned] ) - const { setActions, insertAfter, insertReplayCommentByTimestamp, setMessageListeners, scrollIntoView } = - useActions(notebookNodeLogic) + const { setActions, insertAfter, setMessageListeners, scrollIntoView } = useActions(notebookNodeLogic) const logic = sessionRecordingsPlaylistLogic(recordingPlaylistLogicProps) const { activeSessionRecording } = useValues(logic) @@ -83,16 +80,6 @@ const Component = ({ }) }, }, - { - text: 'Comment', - icon: , - onClick: () => { - if (activeSessionRecording.id) { - const time = getReplayLogic(activeSessionRecording.id)?.values.currentPlayerTime - insertReplayCommentByTimestamp(time ?? 0, activeSessionRecording.id) - } - }, - }, ] : [] ) diff --git a/frontend/src/scenes/notebooks/Nodes/NotebookNodeRecording.tsx b/frontend/src/scenes/notebooks/Nodes/NotebookNodeRecording.tsx index 2ab055514f9f9..da0571af06ca2 100644 --- a/frontend/src/scenes/notebooks/Nodes/NotebookNodeRecording.tsx +++ b/frontend/src/scenes/notebooks/Nodes/NotebookNodeRecording.tsx @@ -1,11 +1,10 @@ import { useActions, useValues } from 'kea' import { useEffect } from 'react' -import { IconComment, IconPerson } from '@posthog/icons' +import { IconPerson } from '@posthog/icons' import { LemonInput, LemonSwitch } from '@posthog/lemon-ui' import { NotFound } from 'lib/components/NotFound' -import { JSONContent } from 'lib/components/RichContentEditor/types' import { useOnMountEffect } from 'lib/hooks/useOnMountEffect' import { colonDelimitedDuration } from 'lib/utils' import { parseTimestampToMs } from 'lib/utils/timestamps' @@ -18,7 +17,6 @@ import { import { sessionRecordingDataLogic } from 'scenes/session-recordings/player/sessionRecordingDataLogic' import { SessionRecordingPlayerMode, - getCurrentPlayerTime, sessionRecordingPlayerLogic, } from 'scenes/session-recordings/player/sessionRecordingPlayerLogic' import { @@ -48,14 +46,7 @@ const Component = ({ attributes }: NotebookNodeProps { const person = sessionPlayerMetaData?.person setActions([ - { - text: 'Comment', - icon: , - onClick: () => { - const time = getCurrentPlayerTime(recordingLogicProps) * 1000 - - insertReplayCommentByTimestamp(time, id) - }, - }, person ? { text: `View ${asDisplay(person)}`, @@ -212,12 +194,3 @@ export function sessionRecordingPlayerProps(id: SessionRecordingId): SessionReco playerKey: `notebook-${id}`, } } - -export function buildRecordingContent(sessionRecordingId: string): JSONContent { - return { - type: 'ph-recording', - attrs: { - id: sessionRecordingId, - }, - } -} diff --git a/frontend/src/scenes/notebooks/Nodes/NotebookNodeReplayTimestamp.tsx b/frontend/src/scenes/notebooks/Nodes/NotebookNodeReplayTimestamp.tsx index bf0b9b9af83b2..9b8c6299e645e 100644 --- a/frontend/src/scenes/notebooks/Nodes/NotebookNodeReplayTimestamp.tsx +++ b/frontend/src/scenes/notebooks/Nodes/NotebookNodeReplayTimestamp.tsx @@ -6,7 +6,6 @@ import { useMemo } from 'react' import { LemonButton } from '@posthog/lemon-ui' -import { JSONContent } from 'lib/components/RichContentEditor/types' import { dayjs } from 'lib/dayjs' import { urls } from 'scenes/urls' @@ -101,16 +100,3 @@ export const NotebookNodeReplayTimestamp = Node.create({ export function formatTimestamp(time: number): string { return dayjs.duration(time, 'milliseconds').format('HH:mm:ss').replace(/^00:/, '').trim() } - -export function buildTimestampCommentContent(attrs: NotebookNodeReplayTimestampAttrs): JSONContent { - return { - type: 'paragraph', - content: [ - { - type: NotebookNodeType.ReplayTimestamp, - attrs, - }, - { type: 'text', text: ' ' }, - ], - } -} diff --git a/frontend/src/scenes/notebooks/Nodes/notebookNodeLogic.ts b/frontend/src/scenes/notebooks/Nodes/notebookNodeLogic.ts index 51e9d3cea19d3..b8f537137d5bc 100644 --- a/frontend/src/scenes/notebooks/Nodes/notebookNodeLogic.ts +++ b/frontend/src/scenes/notebooks/Nodes/notebookNodeLogic.ts @@ -51,10 +51,6 @@ export const notebookNodeLogic = kea([ insertAfter: (content: JSONContent) => ({ content }), insertAfterLastNodeOfType: (nodeType: string, content: JSONContent) => ({ content, nodeType }), updateAttributes: (attributes: Partial>) => ({ attributes }), - insertReplayCommentByTimestamp: (timestamp: number, sessionRecordingId: string) => ({ - timestamp, - sessionRecordingId, - }), insertOrSelectNextLine: true, setPreviousNode: (node: RichContentNode | null) => ({ node }), setNextNode: (node: RichContentNode | null) => ({ node }), @@ -240,19 +236,6 @@ export const notebookNodeLogic = kea([ } values.notebookLogic.actions.insertAfterLastNodeOfType(nodeType, content, insertionPosition) }, - - insertReplayCommentByTimestamp: ({ timestamp, sessionRecordingId }) => { - if (!props.getPos) { - return - } - const insertionPosition = props.getPos() - values.notebookLogic.actions.insertReplayCommentByTimestamp({ - timestamp, - sessionRecordingId, - knownStartingPosition: insertionPosition, - nodeId: values.nodeId, - }) - }, insertOrSelectNextLine: () => { const pos = props.getPos?.() if (!pos || !values.isEditable) { diff --git a/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts b/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts index 810d02fe57e9f..a22768820f556 100644 --- a/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts +++ b/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts @@ -11,10 +11,6 @@ import { accessLevelSatisfied } from 'lib/components/AccessControlAction' import { EditorRange, JSONContent } from 'lib/components/RichContentEditor/types' import { base64Decode, base64Encode, downloadFile, slugify } from 'lib/utils' import { commentsLogic } from 'scenes/comments/commentsLogic' -import { - NotebookNodeReplayTimestampAttrs, - buildTimestampCommentContent, -} from 'scenes/notebooks/Nodes/NotebookNodeReplayTimestamp' import { urls } from 'scenes/urls' import { sidePanelStateLogic } from '~/layout/navigation-3000/sidepanel/sidePanelStateLogic' @@ -133,12 +129,6 @@ export const notebookLogic = kea([ nodeType, knownStartingPosition, }), - insertReplayCommentByTimestamp: (options: { - timestamp: number - sessionRecordingId: string - knownStartingPosition?: number - nodeId?: string - }) => options, setShowHistory: (showHistory: boolean) => ({ showHistory }), setTableOfContents: (tableOfContents: TableOfContentData) => ({ tableOfContents }), setTextSelection: (selection: number | EditorRange) => ({ selection }), @@ -523,36 +513,6 @@ export const notebookLogic = kea([ } ) }, - insertReplayCommentByTimestamp: async ({ timestamp, sessionRecordingId, knownStartingPosition, nodeId }) => { - await runWhenEditorIsReady( - () => !!values.editor, - () => { - let insertionPosition = - knownStartingPosition || values.editor?.findNodePositionByAttrs({ id: sessionRecordingId }) - let nextNode = values.editor?.nextNode(insertionPosition) - while (nextNode && values.editor?.hasChildOfType(nextNode.node, NotebookNodeType.ReplayTimestamp)) { - const candidateTimestampAttributes = nextNode.node.content.firstChild - ?.attrs as NotebookNodeReplayTimestampAttrs - const nextNodePlaybackTime = candidateTimestampAttributes.playbackTime || -1 - if (nextNodePlaybackTime <= timestamp) { - insertionPosition = nextNode.position - nextNode = values.editor?.nextNode(insertionPosition) - } else { - nextNode = null - } - } - - values.editor?.insertContentAfterNode( - insertionPosition, - buildTimestampCommentContent({ - playbackTime: timestamp, - sessionRecordingId, - sourceNodeId: nodeId, - }) - ) - } - ) - }, setLocalContent: async ({ updateEditor, jsonContent, skipCapture }, breakpoint) => { if ( values.mode !== 'canvas' && diff --git a/frontend/src/scenes/session-recordings/player/player-meta/PlayerMetaLinks.tsx b/frontend/src/scenes/session-recordings/player/player-meta/PlayerMetaLinks.tsx index bddf977a15d59..3d00900fad5d1 100644 --- a/frontend/src/scenes/session-recordings/player/player-meta/PlayerMetaLinks.tsx +++ b/frontend/src/scenes/session-recordings/player/player-meta/PlayerMetaLinks.tsx @@ -115,17 +115,11 @@ export function PlayerMetaLinks({ size }: { size: PlayerMetaBreakpoints }): JSX. } const AddToNotebookButton = ({ fullWidth = false }: Pick): JSX.Element => { - const { sessionRecordingId, logicProps } = useValues(sessionRecordingPlayerLogic) + const { sessionRecordingId } = useValues(sessionRecordingPlayerLogic) const { setPause } = useActions(sessionRecordingPlayerLogic) const { closeSessionPlayer } = useActions(sessionPlayerModalLogic()) - const getCurrentPlayerTime = (): number => { - // NOTE: We pull this value at call time as otherwise it would trigger re-renders if pulled from the hook - const playerTime = sessionRecordingPlayerLogic.findMounted(logicProps)?.values.currentPlayerTime || 0 - return Math.floor(playerTime / 1000) - } - return ( setPause()} - onNotebookOpened={(theNotebookLogic, theNodeLogic) => { - const time = getCurrentPlayerTime() * 1000 - - if (theNodeLogic) { - // Node already exists, we just add a comment - theNodeLogic.actions.insertReplayCommentByTimestamp(time, sessionRecordingId) - return - } - theNotebookLogic.actions.insertReplayCommentByTimestamp({ - timestamp: time, - sessionRecordingId, - }) - + onNotebookOpened={() => { closeSessionPlayer() personsModalLogic.findMounted()?.actions.closeModal() }} From dd749eaa78e3dfcdfc3599f080fdddf81b041931 Mon Sep 17 00:00:00 2001 From: pauldambra Date: Mon, 29 Sep 2025 16:06:59 +0100 Subject: [PATCH 3/4] fix --- .../notebooks/Suggestions/ReplayTimestamp.tsx | 67 ------------------- 1 file changed, 67 deletions(-) delete mode 100644 frontend/src/scenes/notebooks/Suggestions/ReplayTimestamp.tsx diff --git a/frontend/src/scenes/notebooks/Suggestions/ReplayTimestamp.tsx b/frontend/src/scenes/notebooks/Suggestions/ReplayTimestamp.tsx deleted file mode 100644 index cb216febe7a0e..0000000000000 --- a/frontend/src/scenes/notebooks/Suggestions/ReplayTimestamp.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { useValues } from 'kea' - -import { LemonButton } from '@posthog/lemon-ui' - -import { RichContentEditorType, RichContentNode } from 'lib/components/RichContentEditor/types' -import { firstChildOfType, hasChildOfType } from 'lib/components/RichContentEditor/utils' -import { sessionRecordingPlayerLogic } from 'scenes/session-recordings/player/sessionRecordingPlayerLogic' - -import { sessionRecordingPlayerProps } from '../Nodes/NotebookNodeRecording' -import { buildTimestampCommentContent, formatTimestamp } from '../Nodes/NotebookNodeReplayTimestamp' -import { NotebookNodeType } from '../types' -import { InsertionSuggestion, InsertionSuggestionViewProps } from './InsertionSuggestion' - -const insertTimestamp = ({ - editor, - previousNode, -}: { - editor: RichContentEditorType | null - previousNode: RichContentNode | null -}): void => { - if (!!previousNode && !!editor) { - const sessionRecordingId = getSessionRecordingId(previousNode) - - const currentPlayerTime = - sessionRecordingPlayerLogic.findMounted(sessionRecordingPlayerProps(sessionRecordingId))?.values - .currentPlayerTime || 0 - - editor.insertContent([buildTimestampCommentContent({ playbackTime: currentPlayerTime, sessionRecordingId })]) - } -} - -const Component = ({ previousNode, editor }: InsertionSuggestionViewProps): JSX.Element => { - const { currentPlayerTime } = useValues( - sessionRecordingPlayerLogic(sessionRecordingPlayerProps(getSessionRecordingId(previousNode))) - ) - - return ( -
- insertTimestamp({ previousNode, editor })}> - {formatTimestamp(currentPlayerTime)} - -
- ) -} - -export default InsertionSuggestion.create({ - shouldShow: ({ previousNode }) => { - return previousNode - ? previousNode.type.name === NotebookNodeType.Recording || - hasChildOfType(previousNode, NotebookNodeType.ReplayTimestamp) - : false - }, - - onTab: insertTimestamp, - - Component, -}) - -function getSessionRecordingId(node: RichContentNode | null): string { - return node?.type.name === NotebookNodeType.Recording - ? node.attrs.id - : getTimestampChildNode(node).attrs.sessionRecordingId -} - -function getTimestampChildNode(node: RichContentNode | null): RichContentNode { - return firstChildOfType(node as RichContentNode, NotebookNodeType.ReplayTimestamp) as RichContentNode -} From cb05ff12af0d722e4935c50edcd9f3bdb60a3bb1 Mon Sep 17 00:00:00 2001 From: pauldambra Date: Mon, 29 Sep 2025 17:23:40 +0100 Subject: [PATCH 4/4] fix --- .../scenes/notebooks/Suggestions/insertionSuggestionsLogic.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/scenes/notebooks/Suggestions/insertionSuggestionsLogic.ts b/frontend/src/scenes/notebooks/Suggestions/insertionSuggestionsLogic.ts index 1ec4836ddf930..9362b467903cb 100644 --- a/frontend/src/scenes/notebooks/Suggestions/insertionSuggestionsLogic.ts +++ b/frontend/src/scenes/notebooks/Suggestions/insertionSuggestionsLogic.ts @@ -3,7 +3,6 @@ import { actions, events, kea, listeners, path, reducers, selectors } from 'kea' import { RichContentEditorType, RichContentNode } from 'lib/components/RichContentEditor/types' import { InsertionSuggestion } from './InsertionSuggestion' -import ReplayTimestampSuggestion from './ReplayTimestamp' import SlashCommands from './SlashCommands' import type { insertionSuggestionsLogicType } from './insertionSuggestionsLogicType' @@ -19,7 +18,7 @@ export const insertionSuggestionsLogic = kea([ }), reducers({ suggestions: [ - [ReplayTimestampSuggestion] as InsertionSuggestion[], + [] as InsertionSuggestion[], { setSuggestions: (_, { suggestions }) => suggestions, },