Skip to content

Commit

Permalink
Merge branch 'master' into chore/snapshot-notebook-template
Browse files Browse the repository at this point in the history
  • Loading branch information
pauldambra committed Sep 13, 2023
2 parents 2a4595a + 45a206c commit 1da2d07
Show file tree
Hide file tree
Showing 21 changed files with 182 additions and 25 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1321,7 +1321,7 @@ const api = {
},
async update(
notebookId: NotebookType['short_id'],
data: Pick<NotebookType, 'version' | 'content' | 'title'>
data: Pick<NotebookType, 'version' | 'content' | 'text_content' | 'title'>
): Promise<NotebookType> {
return await new ApiRequest().notebook(notebookId).update({ data })
},
Expand Down Expand Up @@ -1352,7 +1352,7 @@ const api = {
}
return await apiRequest.withQueryString(q).get()
},
async create(data?: Pick<NotebookType, 'content' | 'title'>): Promise<NotebookType> {
async create(data?: Pick<NotebookType, 'content' | 'text_content' | 'title'>): Promise<NotebookType> {
return await new ApiRequest().notebooks().create({ data })
},
async delete(notebookId: NotebookType['short_id']): Promise<NotebookType> {
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/scenes/notebooks/Nodes/NodeWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ExtendedRegExpMatchArray,
Attribute,
NodeViewProps,
getExtensionField,
} from '@tiptap/react'
import { ReactNode, useCallback, useRef } from 'react'
import clsx from 'clsx'
Expand Down Expand Up @@ -212,12 +213,14 @@ export type CreatePostHogWidgetNodeOptions<T extends CustomNotebookNodeAttribute
}
attributes: Record<keyof T, Partial<Attribute>>
widgets?: NotebookNodeWidget[]
serializedText?: (attributes: NotebookNodeAttributes<T>) => string
}

export function createPostHogWidgetNode<T extends CustomNotebookNodeAttributes>({
Component,
pasteOptions,
attributes,
serializedText,
...wrapperProps
}: CreatePostHogWidgetNodeOptions<T>): Node {
// NOTE: We use NodeViewProps here as we convert them to NotebookNodeViewProps
Expand Down Expand Up @@ -252,6 +255,19 @@ export function createPostHogWidgetNode<T extends CustomNotebookNodeAttributes>(
atom: true,
draggable: true,

serializedText: serializedText,

extendNodeSchema(extension) {
const context = {
name: extension.name,
options: extension.options,
storage: extension.storage,
}
return {
serializedText: getExtensionField(extension, 'serializedText', context),
}
},

addAttributes() {
return {
height: {},
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/scenes/notebooks/Nodes/NotebookNodeImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ export const NotebookNodeImage = createPostHogWidgetNode<NotebookNodeImageAttrib
nodeType: NotebookNodeType.Image,
title: 'Image',
Component,
serializedText: (attrs) => {
// TODO file is null when this runs... should it be?
return attrs?.file?.name || ''
},
heightEstimate: 400,
minHeight: 100,
resizeable: true,
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/scenes/notebooks/Nodes/NotebookNodePerson.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,9 @@ export const NotebookNodePerson = createPostHogWidgetNode<NotebookNodePersonAttr
return { id: match[1] }
},
},
serializedText: (attrs) => {
const personTitle = attrs?.title || ''
const personId = attrs?.id || ''
return `${personTitle} ${personId}`.trim()
},
})
14 changes: 14 additions & 0 deletions frontend/src/scenes/notebooks/Nodes/NotebookNodeQuery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { urls } from 'scenes/urls'
import api from 'lib/api'

import './NotebookNodeQuery.scss'
import { containsHogQLQuery, isHogQLQuery, isNodeWithSource } from '~/queries/utils'

const DEFAULT_QUERY: QuerySchema = {
kind: NodeKind.DataTableNode,
Expand Down Expand Up @@ -161,4 +162,17 @@ export const NotebookNodeQuery = createPostHogWidgetNode<NotebookNodeQueryAttrib
}
},
},
serializedText: (attrs) => {
let text = ''
const q = attrs.query
if (containsHogQLQuery(q)) {
if (isHogQLQuery(q)) {
text = q.query
}
if (isNodeWithSource(q)) {
text = isHogQLQuery(q.source) ? q.source.query : ''
}
}
return text
},
})
3 changes: 3 additions & 0 deletions frontend/src/scenes/notebooks/Nodes/NotebookNodeRecording.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ export const NotebookNodeRecording = createPostHogWidgetNode<NotebookNodeRecordi
Component: Settings,
},
],
serializedText: (attrs) => {
return attrs.id
},
})

export function sessionRecordingPlayerProps(id: SessionRecordingId): SessionRecordingPlayerProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ export const NotebookNodeReplayTimestamp = Node.create({
group: 'inline',
atom: true,

serializedText: (attrs: NotebookNodeReplayTimestampAttrs): string => {
// timestamp is not a block so `getText` does not add a separator.
// we need to add it manually
return `${attrs.playbackTime ? formatTimestamp(attrs.playbackTime) : '00:00'}:\n`
},

addAttributes() {
return {
playbackTime: { default: null, keepOnSplit: false },
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/scenes/notebooks/Notebook/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useActions } from 'kea'
import { useCallback, useRef } from 'react'

import { Editor as TTEditor } from '@tiptap/core'
import { useEditor, EditorContent } from '@tiptap/react'
import { EditorContent, useEditor } from '@tiptap/react'
import { FloatingMenu } from '@tiptap/extension-floating-menu'
import StarterKit from '@tiptap/starter-kit'
import ExtensionPlaceholder from '@tiptap/extension-placeholder'
Expand All @@ -25,7 +25,7 @@ import { lemonToast } from '@posthog/lemon-ui'
import { NotebookNodeType } from '~/types'
import { NotebookNodeImage } from '../Nodes/NotebookNodeImage'

import { JSONContent, NotebookEditor, EditorFocusPosition, EditorRange, Node } from './utils'
import { EditorFocusPosition, EditorRange, JSONContent, Node, NotebookEditor, textContent } from './utils'
import { SlashCommandsExtension } from './SlashCommands'
import { BacklinkCommandsExtension } from './BacklinkCommands'
import { NotebookNodeEarlyAccessFeature } from '../Nodes/NotebookNodeEarlyAccessFeature'
Expand Down Expand Up @@ -182,6 +182,7 @@ export function Editor({

onCreate({
getJSON: () => editor.getJSON(),
getText: () => textContent(editor.state.doc),
getEndPosition: () => editor.state.doc.content.size,
getSelectedNode: () => editor.state.doc.nodeAt(editor.state.selection.$anchor.pos),
getAdjacentNodes: (pos: number) => getAdjacentNodes(editor, pos),
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/scenes/notebooks/Notebook/notebookLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export const notebookLogic = kea<notebookLogicType>([
response = {
...values.scratchpadNotebook,
content: {},
text_content: null,
version: 0,
}
} else if (props.shortId.startsWith('template-')) {
Expand Down Expand Up @@ -210,6 +211,7 @@ export const notebookLogic = kea<notebookLogicType>([
const response = await api.notebooks.update(values.notebook.short_id, {
version: values.notebook.version,
content: notebook.content,
text_content: values.editor?.getText() || '',
title: notebook.title,
})

Expand Down Expand Up @@ -242,6 +244,7 @@ export const notebookLogic = kea<notebookLogicType>([
// We use the local content if set otherwise the notebook content. That way it supports templates, scratchpad etc.
const response = await api.notebooks.create({
content: values.content || values.notebook.content,
text_content: values.editor?.getText() || '',
title: values.title || values.notebook.title,
})

Expand Down Expand Up @@ -430,6 +433,7 @@ export const notebookLogic = kea<notebookLogicType>([
return
}
const jsonContent = values.editor.getJSON()

actions.setLocalContent(jsonContent)
actions.onUpdateEditor()
},
Expand Down
41 changes: 35 additions & 6 deletions frontend/src/scenes/notebooks/Notebook/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
getText,
JSONContent as TTJSONContent,
Range as EditorRange,
TextSerializer,
} from '@tiptap/core'
import { Node as PMNode } from '@tiptap/pm/model'
import { NodeViewProps } from '@tiptap/react'
Expand Down Expand Up @@ -48,12 +49,13 @@ export type NotebookNodeWidget = {
key: string
label: string
icon: JSX.Element
// using 'any' here shouldn't be necessary but I couldn't figure out how to set a generic on the notebookNodeLogic props
// using 'any' here shouldn't be necessary but, I couldn't figure out how to set a generic on the notebookNodeLogic props
Component: ({ attributes, updateAttributes }: NotebookNodeAttributeProperties<any>) => JSX.Element
}

export interface NotebookEditor {
getJSON: () => JSONContent
getText: () => string
getEndPosition: () => number
getSelectedNode: () => Node | null
getAdjacentNodes: (pos: number) => { previous: Node | null; next: Node | null }
Expand Down Expand Up @@ -88,12 +90,39 @@ export const isCurrentNodeEmpty = (editor: TTEditor): boolean => {
return false
}

const textContent = (node: any): string => {
export const textContent = (node: any): string => {
// we've extended the node schema to support a custom serializedText function
// each custom node type needs to implement this function, or have an alternative in the map below
const customOrTitleSerializer: TextSerializer = (props): string => {
// TipTap chooses whether to add a separator based on a couple of factors
// but, we always want a separator since this text is for search purposes
const serializedText = props.node.type.spec.serializedText(props.node.attrs) || props.node.attrs?.title || ''
if (serializedText.length > 0 && serializedText[serializedText.length - 1] !== '\n') {
return serializedText + '\n'
}
return serializedText
}

// we want the type system to complain if we forget to add a custom serializer
const customNodeTextSerializers: Record<NotebookNodeType, TextSerializer> = {
'ph-backlink': customOrTitleSerializer,
'ph-early-access-feature': customOrTitleSerializer,
'ph-experiment': customOrTitleSerializer,
'ph-feature-flag': customOrTitleSerializer,
'ph-feature-flag-code-example': customOrTitleSerializer,
'ph-image': customOrTitleSerializer,
'ph-insight': customOrTitleSerializer,
'ph-person': customOrTitleSerializer,
'ph-query': customOrTitleSerializer,
'ph-recording': customOrTitleSerializer,
'ph-recording-playlist': customOrTitleSerializer,
'ph-replay-timestamp': customOrTitleSerializer,
'ph-survey': customOrTitleSerializer,
}

return getText(node, {
blockSeparator: ' ',
textSerializers: {
[NotebookNodeType.ReplayTimestamp]: ({ node }) => `${node.attrs.playbackTime || '00:00'}: `,
},
blockSeparator: '\n',
textSerializers: customNodeTextSerializers,
})
}

Expand Down
2 changes: 2 additions & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3020,6 +3020,8 @@ export type NotebookListItemType = {
export type NotebookType = NotebookListItemType & {
content: JSONContent // TODO: Type this better
version: number
// used to power text-based search
text_content?: string | null
}

export enum NotebookNodeType {
Expand Down
2 changes: 1 addition & 1 deletion latest_migrations.manifest
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ contenttypes: 0002_remove_content_type_name
ee: 0015_add_verified_properties
otp_static: 0002_throttling
otp_totp: 0002_auto_20190420_0723
posthog: 0349_update_survey_query_name
posthog: 0350_add_notebook_text_content
sessions: 0001_initial
social_django: 0010_uid_db_index
two_factor: 0007_auto_20201201_1019
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 8 additions & 2 deletions posthog/api/notebook.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Dict, List, Optional, Any

from django.db.models import Q
import structlog
from django.db import transaction
from django.db.models import QuerySet
Expand Down Expand Up @@ -74,6 +74,7 @@ class Meta:
"short_id",
"title",
"content",
"text_content",
"version",
"deleted",
"created_at",
Expand Down Expand Up @@ -251,7 +252,12 @@ def _filter_request(self, request: request.Request, queryset: QuerySet) -> Query
last_modified_at__lt=relative_date_parse(request.GET["date_to"], self.team.timezone_info)
)
elif key == "search":
queryset = queryset.filter(title__icontains=request.GET["search"])
queryset = queryset.filter(
# some notebooks have no text_content until next saved, so we need to check the title too
# TODO this can be removed once all/most notebooks have text_content
Q(title__search=request.GET["search"])
| Q(text_content__search=request.GET["search"])
)
elif key == "contains":
contains = request.GET["contains"]
match_pairs = contains.replace(",", " ").split(" ")
Expand Down
Loading

0 comments on commit 1da2d07

Please sign in to comment.