From d38e754917744f1516b823c0e9b4411df95df6c0 Mon Sep 17 00:00:00 2001 From: David Newell Date: Fri, 30 Jun 2023 15:45:54 +0100 Subject: [PATCH 1/3] Display conflict errors to a user --- .../scenes/notebooks/Notebook/Notebook.tsx | 6 ++++-- .../Notebook/NotebookConflictOverlay.scss | 15 +++++++++++++ .../Notebook/NotebookConflictOverlay.tsx | 20 ++++++++++++++++++ .../notebooks/Notebook/notebookLogic.ts | 15 ++++++++++++- posthog/api/notebook.py | 21 ++++++++++++------- 5 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 frontend/src/scenes/notebooks/Notebook/NotebookConflictOverlay.scss create mode 100644 frontend/src/scenes/notebooks/Notebook/NotebookConflictOverlay.tsx diff --git a/frontend/src/scenes/notebooks/Notebook/Notebook.tsx b/frontend/src/scenes/notebooks/Notebook/Notebook.tsx index 44d2fdc97fb5c..5bf8750ede377 100644 --- a/frontend/src/scenes/notebooks/Notebook/Notebook.tsx +++ b/frontend/src/scenes/notebooks/Notebook/Notebook.tsx @@ -23,6 +23,7 @@ import posthog from 'posthog-js' import { LemonBanner } from 'lib/lemon-ui/LemonBanner' import { SCRATCHPAD_NOTEBOOK } from './notebooksListLogic' import { FloatingSlashCommands, SlashCommandsExtension } from './SlashCommands' +import { NotebookConflictOverlay } from './NotebookConflictOverlay' export type NotebookProps = { shortId: string @@ -37,7 +38,7 @@ const PLACEHOLDER_TITLES = ['Release notes', 'Product roadmap', 'Meeting notes', export function Notebook({ shortId, editable = false }: NotebookProps): JSX.Element { const logic = notebookLogic({ shortId }) - const { notebook, content, notebookLoading, isEmpty } = useValues(logic) + const { notebook, content, notebookLoading, isEmpty, conflictOverlayVisible } = useValues(logic) const { setEditorRef, onEditorUpdate, duplicateNotebook, loadNotebook } = useActions(logic) const { isExpanded } = useValues(notebookSettingsLogic) @@ -167,7 +168,7 @@ export function Notebook({ shortId, editable = false }: NotebookProps): JSX.Elem return ( -
+
{!notebook && notebookLoading ? (
@@ -212,6 +213,7 @@ export function Notebook({ shortId, editable = false }: NotebookProps): JSX.Elem ) : null} + {conflictOverlayVisible && } )}
diff --git a/frontend/src/scenes/notebooks/Notebook/NotebookConflictOverlay.scss b/frontend/src/scenes/notebooks/Notebook/NotebookConflictOverlay.scss new file mode 100644 index 0000000000000..8d3f06f4f5360 --- /dev/null +++ b/frontend/src/scenes/notebooks/Notebook/NotebookConflictOverlay.scss @@ -0,0 +1,15 @@ +@import '../../../styles/mixins'; + +.NotebookConflictOverlay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: rgb(255, 255, 255, 0.7); + backdrop-filter: blur(2px); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} diff --git a/frontend/src/scenes/notebooks/Notebook/NotebookConflictOverlay.tsx b/frontend/src/scenes/notebooks/Notebook/NotebookConflictOverlay.tsx new file mode 100644 index 0000000000000..8f56abe267bef --- /dev/null +++ b/frontend/src/scenes/notebooks/Notebook/NotebookConflictOverlay.tsx @@ -0,0 +1,20 @@ +import { useActions } from 'kea' +import './NotebookConflictOverlay.scss' +import { LemonButton } from 'lib/lemon-ui/LemonButton' +import { notebookLogic } from './notebookLogic' + +export function NotebookConflictOverlay(): JSX.Element { + const { loadNotebook } = useActions(notebookLogic) + + return ( +
+
+ It looks like someone else has been editing this content too. You will need to reload the notebook to + see the latest version. +
+ + Reload + +
+ ) +} diff --git a/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts b/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts index ae76445a10116..9172f309811c6 100644 --- a/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts +++ b/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts @@ -34,6 +34,7 @@ export const notebookLogic = kea([ loadNotebook: true, saveNotebook: (notebook: Pick) => ({ notebook }), exportJSON: true, + showConflictOverlay: true, }), reducers({ localContent: [ @@ -50,13 +51,19 @@ export const notebookLogic = kea([ setEditorRef: (_, { editor }) => editor, }, ], - ready: [ false, { setReady: () => true, }, ], + conflictOverlayVisible: [ + false, + { + showConflictOverlay: () => true, + loadNotebook: () => false, + }, + ], }), loaders(({ values, props, actions }) => ({ notebook: [ @@ -226,6 +233,12 @@ export const notebookLogic = kea([ saveNotebookSuccess: sharedListeners.onNotebookChange, loadNotebookSuccess: sharedListeners.onNotebookChange, + saveNotebookFailure: ({ errorObject }) => { + if (errorObject.code === 'conflict') { + actions.showConflictOverlay() + } + }, + exportJSON: () => { const file = new File( [JSON.stringify(values.editor?.getJSON())], diff --git a/posthog/api/notebook.py b/posthog/api/notebook.py index 5144a2592d041..9251ac80d4c94 100644 --- a/posthog/api/notebook.py +++ b/posthog/api/notebook.py @@ -2,6 +2,7 @@ import structlog from django.db.models import QuerySet +from django.db import transaction from django.utils.timezone import now from django_filters.rest_framework import DjangoFilterBackend from rest_framework import request, serializers, viewsets @@ -101,16 +102,22 @@ def update(self, instance: Notebook, validated_data: Dict, **kwargs) -> Notebook instance.last_modified_at = now() instance.last_modified_by = self.context["request"].user + raise serializers.ValidationError( + "Notebook was modified by someone else. Please refresh and try again.", "conflict" + ) + # TODO: This is not atomic meaning we could still end up with race conditions - if validated_data.get("content"): - if validated_data.get("version") != instance.version: - raise serializers.ValidationError( - "Notebook was modified by someone else. Please refresh and try again." - ) + with transaction.atomic(): + if validated_data.get("content"): + if validated_data.get("version") != instance.version: + raise serializers.ValidationError( + "Notebook was modified by someone else. Please refresh and try again." + ) + + validated_data["version"] = instance.version + 1 - validated_data["version"] = instance.version + 1 + updated_notebook = super().update(instance, validated_data) - updated_notebook = super().update(instance, validated_data) changes = changes_between("Notebook", previous=before_update, current=updated_notebook) log_notebook_activity( From 9d7a72658db89698141606d5fb9560efc01c7cfb Mon Sep 17 00:00:00 2001 From: David Newell Date: Mon, 3 Jul 2023 12:13:47 +0100 Subject: [PATCH 2/3] Add atomic notebook update --- frontend/src/initKea.ts | 2 +- .../scenes/notebooks/Notebook/Notebook.tsx | 13 +++++---- .../Notebook/NotebookConflictOverlay.scss | 15 ----------- ...verlay.tsx => NotebookConflictWarning.tsx} | 14 +++++----- .../notebooks/Notebook/notebookLogic.ts | 13 ++++----- posthog/api/notebook.py | 27 +++++++++---------- posthog/exceptions.py | 5 ++++ 7 files changed, 39 insertions(+), 50 deletions(-) delete mode 100644 frontend/src/scenes/notebooks/Notebook/NotebookConflictOverlay.scss rename frontend/src/scenes/notebooks/Notebook/{NotebookConflictOverlay.tsx => NotebookConflictWarning.tsx} (61%) diff --git a/frontend/src/initKea.ts b/frontend/src/initKea.ts index 384ae96d60010..c71326cf32d15 100644 --- a/frontend/src/initKea.ts +++ b/frontend/src/initKea.ts @@ -79,7 +79,7 @@ export function initKea({ routerHistory, routerLocation, beforePlugins }: InitKe if ( !ERROR_FILTER_WHITELIST.includes(actionKey) && (error?.message === 'Failed to fetch' || // Likely CORS headers errors (i.e. request failing without reaching Django) - (error?.status !== undefined && ![200, 201, 204].includes(error.status))) + (error?.status !== undefined && ![200, 201, 204, 409].includes(error.status))) ) { lemonToast.error( `${identifierToHuman(actionKey)} failed: ${ diff --git a/frontend/src/scenes/notebooks/Notebook/Notebook.tsx b/frontend/src/scenes/notebooks/Notebook/Notebook.tsx index 5bf8750ede377..e07d1a464ea63 100644 --- a/frontend/src/scenes/notebooks/Notebook/Notebook.tsx +++ b/frontend/src/scenes/notebooks/Notebook/Notebook.tsx @@ -23,7 +23,7 @@ import posthog from 'posthog-js' import { LemonBanner } from 'lib/lemon-ui/LemonBanner' import { SCRATCHPAD_NOTEBOOK } from './notebooksListLogic' import { FloatingSlashCommands, SlashCommandsExtension } from './SlashCommands' -import { NotebookConflictOverlay } from './NotebookConflictOverlay' +import { NotebookConflictWarning } from './NotebookConflictWarning' export type NotebookProps = { shortId: string @@ -38,7 +38,7 @@ const PLACEHOLDER_TITLES = ['Release notes', 'Product roadmap', 'Meeting notes', export function Notebook({ shortId, editable = false }: NotebookProps): JSX.Element { const logic = notebookLogic({ shortId }) - const { notebook, content, notebookLoading, isEmpty, conflictOverlayVisible } = useValues(logic) + const { notebook, content, notebookLoading, isEmpty, conflictWarningVisible } = useValues(logic) const { setEditorRef, onEditorUpdate, duplicateNotebook, loadNotebook } = useActions(logic) const { isExpanded } = useValues(notebookSettingsLogic) @@ -168,7 +168,7 @@ export function Notebook({ shortId, editable = false }: NotebookProps): JSX.Elem return ( -
+
{!notebook && notebookLoading ? (
@@ -212,8 +212,11 @@ export function Notebook({ shortId, editable = false }: NotebookProps): JSX.Elem browser. It's a great place to gather ideas before turning into a saved Notebook! ) : null} - - {conflictOverlayVisible && } + {conflictWarningVisible ? ( + + ) : ( + + )} )}
diff --git a/frontend/src/scenes/notebooks/Notebook/NotebookConflictOverlay.scss b/frontend/src/scenes/notebooks/Notebook/NotebookConflictOverlay.scss deleted file mode 100644 index 8d3f06f4f5360..0000000000000 --- a/frontend/src/scenes/notebooks/Notebook/NotebookConflictOverlay.scss +++ /dev/null @@ -1,15 +0,0 @@ -@import '../../../styles/mixins'; - -.NotebookConflictOverlay { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - background-color: rgb(255, 255, 255, 0.7); - backdrop-filter: blur(2px); - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} diff --git a/frontend/src/scenes/notebooks/Notebook/NotebookConflictOverlay.tsx b/frontend/src/scenes/notebooks/Notebook/NotebookConflictWarning.tsx similarity index 61% rename from frontend/src/scenes/notebooks/Notebook/NotebookConflictOverlay.tsx rename to frontend/src/scenes/notebooks/Notebook/NotebookConflictWarning.tsx index 8f56abe267bef..5151f746a87e9 100644 --- a/frontend/src/scenes/notebooks/Notebook/NotebookConflictOverlay.tsx +++ b/frontend/src/scenes/notebooks/Notebook/NotebookConflictWarning.tsx @@ -1,19 +1,21 @@ import { useActions } from 'kea' -import './NotebookConflictOverlay.scss' import { LemonButton } from 'lib/lemon-ui/LemonButton' import { notebookLogic } from './notebookLogic' -export function NotebookConflictOverlay(): JSX.Element { +export function NotebookConflictWarning(): JSX.Element { const { loadNotebook } = useActions(notebookLogic) return ( -
-
+
+

This Notebook has been edited elsewhere

+ +

It looks like someone else has been editing this content too. You will need to reload the notebook to see the latest version. -

+

+ - Reload + Reload to see the latest content
) diff --git a/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts b/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts index 9172f309811c6..9f82f0edb918f 100644 --- a/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts +++ b/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts @@ -34,7 +34,7 @@ export const notebookLogic = kea([ loadNotebook: true, saveNotebook: (notebook: Pick) => ({ notebook }), exportJSON: true, - showConflictOverlay: true, + showConflictWarning: true, }), reducers({ localContent: [ @@ -57,10 +57,10 @@ export const notebookLogic = kea([ setReady: () => true, }, ], - conflictOverlayVisible: [ + conflictWarningVisible: [ false, { - showConflictOverlay: () => true, + showConflictWarning: () => true, loadNotebook: () => false, }, ], @@ -89,10 +89,7 @@ export const notebookLogic = kea([ throw new Error('Notebook not found') } - if (!values.notebook) { - // If this is the first load we need to override the content fully - values.editor?.commands.setContent(response.content) - } + values.editor?.commands.setContent(response.content) return response }, @@ -235,7 +232,7 @@ export const notebookLogic = kea([ saveNotebookFailure: ({ errorObject }) => { if (errorObject.code === 'conflict') { - actions.showConflictOverlay() + actions.showConflictWarning() } }, diff --git a/posthog/api/notebook.py b/posthog/api/notebook.py index 9251ac80d4c94..ae4a0f05160f6 100644 --- a/posthog/api/notebook.py +++ b/posthog/api/notebook.py @@ -12,6 +12,7 @@ from posthog.api.forbid_destroy_model import ForbidDestroyModel from posthog.api.routing import StructuredViewSetMixin from posthog.api.shared import UserBasicSerializer +from posthog.exceptions import Conflict from posthog.models import User from posthog.models.activity_logging.activity_log import Change, Detail, changes_between, log_activity, load_activity from posthog.models.activity_logging.activity_page import activity_page_response @@ -98,25 +99,21 @@ def update(self, instance: Notebook, validated_data: Dict, **kwargs) -> Notebook except Notebook.DoesNotExist: before_update = None - if validated_data.keys(): - instance.last_modified_at = now() - instance.last_modified_by = self.context["request"].user + with transaction.atomic(): + # Lock the database row so we ensure version updates are atomic + locked_instance = Notebook.objects.select_for_update().get(pk=instance.pk) - raise serializers.ValidationError( - "Notebook was modified by someone else. Please refresh and try again.", "conflict" - ) + if validated_data.keys(): + locked_instance.last_modified_at = now() + locked_instance.last_modified_by = self.context["request"].user - # TODO: This is not atomic meaning we could still end up with race conditions - with transaction.atomic(): - if validated_data.get("content"): - if validated_data.get("version") != instance.version: - raise serializers.ValidationError( - "Notebook was modified by someone else. Please refresh and try again." - ) + if validated_data.get("content"): + if validated_data.get("version") != locked_instance.version: + raise Conflict("Someone else edited the Notebook") - validated_data["version"] = instance.version + 1 + validated_data["version"] = locked_instance.version + 1 - updated_notebook = super().update(instance, validated_data) + updated_notebook = super().update(locked_instance, validated_data) changes = changes_between("Notebook", previous=before_update, current=updated_notebook) diff --git a/posthog/exceptions.py b/posthog/exceptions.py index 295b0834a1e55..6d93c682f41b1 100644 --- a/posthog/exceptions.py +++ b/posthog/exceptions.py @@ -33,6 +33,11 @@ def __init__(self, feature: Optional[str] = None) -> None: ) +class Conflict(APIException): + status_code = status.HTTP_409_CONFLICT + default_code = "conflict" + + class EstimatedQueryExecutionTimeTooLong(APIException): status_code = 512 # Custom error code default_detail = "Estimated query execution time is too long" From 149bfdba4a53baf4cdce78bb50cfa19b1c753d0b Mon Sep 17 00:00:00 2001 From: David Newell Date: Mon, 3 Jul 2023 17:52:46 +0100 Subject: [PATCH 3/3] Add snapshot test for atomic saving of notebooks --- frontend/src/initKea.ts | 2 +- .../scenes/notebooks/Notebook/Notebook.tsx | 12 +- .../notebooks/Notebook/notebookLogic.ts | 53 +- .../src/scenes/notebooks/NotebookScene.tsx | 4 +- posthog/api/notebook.py | 2 +- .../__snapshots__/test_notebook.ambr | 709 ++++++++++++++++++ posthog/api/test/notebooks/test_notebook.py | 5 +- 7 files changed, 751 insertions(+), 36 deletions(-) create mode 100644 posthog/api/test/notebooks/__snapshots__/test_notebook.ambr diff --git a/frontend/src/initKea.ts b/frontend/src/initKea.ts index c71326cf32d15..384ae96d60010 100644 --- a/frontend/src/initKea.ts +++ b/frontend/src/initKea.ts @@ -79,7 +79,7 @@ export function initKea({ routerHistory, routerLocation, beforePlugins }: InitKe if ( !ERROR_FILTER_WHITELIST.includes(actionKey) && (error?.message === 'Failed to fetch' || // Likely CORS headers errors (i.e. request failing without reaching Django) - (error?.status !== undefined && ![200, 201, 204, 409].includes(error.status))) + (error?.status !== undefined && ![200, 201, 204].includes(error.status))) ) { lemonToast.error( `${identifierToHuman(actionKey)} failed: ${ diff --git a/frontend/src/scenes/notebooks/Notebook/Notebook.tsx b/frontend/src/scenes/notebooks/Notebook/Notebook.tsx index e07d1a464ea63..2acec2ac692cc 100644 --- a/frontend/src/scenes/notebooks/Notebook/Notebook.tsx +++ b/frontend/src/scenes/notebooks/Notebook/Notebook.tsx @@ -170,7 +170,9 @@ export function Notebook({ shortId, editable = false }: NotebookProps): JSX.Elem
- {!notebook && notebookLoading ? ( + {conflictWarningVisible ? ( + + ) : !notebook && notebookLoading ? (
@@ -178,7 +180,7 @@ export function Notebook({ shortId, editable = false }: NotebookProps): JSX.Elem
) : !notebook ? ( - + ) : isEmpty && !editable ? (

@@ -212,11 +214,7 @@ export function Notebook({ shortId, editable = false }: NotebookProps): JSX.Elem browser. It's a great place to gather ideas before turning into a saved Notebook! ) : null} - {conflictWarningVisible ? ( - - ) : ( - - )} + )}

diff --git a/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts b/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts index 9f82f0edb918f..2d4a66fb339a8 100644 --- a/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts +++ b/frontend/src/scenes/notebooks/Notebook/notebookLogic.ts @@ -61,17 +61,17 @@ export const notebookLogic = kea([ false, { showConflictWarning: () => true, - loadNotebook: () => false, + loadNotebookSuccess: () => false, }, ], }), loaders(({ values, props, actions }) => ({ notebook: [ - undefined as NotebookType | undefined, + null as NotebookType | null, { loadNotebook: async () => { // NOTE: This is all hacky and temporary until we have a backend - let response: NotebookType | undefined + let response: NotebookType | null if (props.shortId === SCRATCHPAD_NOTEBOOK.short_id) { response = { @@ -80,7 +80,8 @@ export const notebookLogic = kea([ version: 0, } } else if (props.shortId.startsWith('template-')) { - response = values.notebookTemplates.find((template) => template.short_id === props.shortId) + response = + values.notebookTemplates.find((template) => template.short_id === props.shortId) || null } else { response = await api.notebooks.get(props.shortId) } @@ -89,7 +90,10 @@ export const notebookLogic = kea([ throw new Error('Notebook not found') } - values.editor?.commands.setContent(response.content) + if (!values.notebook) { + // If this is the first load we need to override the content fully + values.editor?.commands.setContent(response.content) + } return response }, @@ -99,28 +103,37 @@ export const notebookLogic = kea([ return values.notebook } - const response = await api.notebooks.update(values.notebook.short_id, { - version: values.notebook.version, - content: notebook.content, - title: notebook.title, - }) + try { + const response = await api.notebooks.update(values.notebook.short_id, { + version: values.notebook.version, + content: notebook.content, + title: notebook.title, + }) - // If the object is identical then no edits were made, so we can safely clear the local changes - if (notebook.content === values.localContent) { - actions.clearLocalContent() - } + // If the object is identical then no edits were made, so we can safely clear the local changes + if (notebook.content === values.localContent) { + actions.clearLocalContent() + } - return response + return response + } catch (error: any) { + if (error.code === 'conflict') { + actions.showConflictWarning() + return null + } else { + throw error + } + } }, }, ], newNotebook: [ - undefined as NotebookType | undefined, + null as NotebookType | null, { duplicateNotebook: async () => { if (!values.notebook) { - return + return null } // We use the local content if set otherwise the notebook content. That way it supports templates, scratchpad etc. @@ -230,12 +243,6 @@ export const notebookLogic = kea([ saveNotebookSuccess: sharedListeners.onNotebookChange, loadNotebookSuccess: sharedListeners.onNotebookChange, - saveNotebookFailure: ({ errorObject }) => { - if (errorObject.code === 'conflict') { - actions.showConflictWarning() - } - }, - exportJSON: () => { const file = new File( [JSON.stringify(values.editor?.getJSON())], diff --git a/frontend/src/scenes/notebooks/NotebookScene.tsx b/frontend/src/scenes/notebooks/NotebookScene.tsx index 60026169bfb65..67a69dd57d0c9 100644 --- a/frontend/src/scenes/notebooks/NotebookScene.tsx +++ b/frontend/src/scenes/notebooks/NotebookScene.tsx @@ -31,12 +31,12 @@ export const scene: SceneExport = { export function NotebookScene(): JSX.Element { const { notebookId, mode } = useValues(notebookSceneLogic) const { setNotebookMode } = useActions(notebookSceneLogic) - const { notebook, notebookLoading } = useValues(notebookLogic({ shortId: notebookId })) + const { notebook, notebookLoading, conflictWarningVisible } = useValues(notebookLogic({ shortId: notebookId })) const { exportJSON } = useActions(notebookLogic({ shortId: notebookId })) const { selectNotebook, setNotebookSideBarShown } = useActions(notebookSidebarLogic) const { selectedNotebook, notebookSideBarShown } = useValues(notebookSidebarLogic) - if (!notebook && !notebookLoading) { + if (!notebook && !notebookLoading && !conflictWarningVisible) { return } diff --git a/posthog/api/notebook.py b/posthog/api/notebook.py index ae4a0f05160f6..0c063759d43b2 100644 --- a/posthog/api/notebook.py +++ b/posthog/api/notebook.py @@ -100,7 +100,7 @@ def update(self, instance: Notebook, validated_data: Dict, **kwargs) -> Notebook before_update = None with transaction.atomic(): - # Lock the database row so we ensure version updates are atomic + # select_for_update locks the database row so we ensure version updates are atomic locked_instance = Notebook.objects.select_for_update().get(pk=instance.pk) if validated_data.keys(): diff --git a/posthog/api/test/notebooks/__snapshots__/test_notebook.ambr b/posthog/api/test/notebooks/__snapshots__/test_notebook.ambr new file mode 100644 index 0000000000000..929634ace0b5b --- /dev/null +++ b/posthog/api/test/notebooks/__snapshots__/test_notebook.ambr @@ -0,0 +1,709 @@ +# name: TestNotebooks.test_updates_notebook + ' + SELECT "posthog_user"."id", + "posthog_user"."password", + "posthog_user"."last_login", + "posthog_user"."first_name", + "posthog_user"."last_name", + "posthog_user"."is_staff", + "posthog_user"."is_active", + "posthog_user"."date_joined", + "posthog_user"."uuid", + "posthog_user"."current_organization_id", + "posthog_user"."current_team_id", + "posthog_user"."email", + "posthog_user"."pending_email", + "posthog_user"."temporary_token", + "posthog_user"."distinct_id", + "posthog_user"."is_email_verified", + "posthog_user"."has_seen_product_intro_for", + "posthog_user"."email_opt_in", + "posthog_user"."partial_notification_settings", + "posthog_user"."anonymize_data", + "posthog_user"."toolbar_mode", + "posthog_user"."events_column_config" + FROM "posthog_user" + WHERE "posthog_user"."id" = 2 + LIMIT 21 /**/ + ' +--- +# name: TestNotebooks.test_updates_notebook.1 + ' + SELECT "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."session_recording_opt_in", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."primary_dashboard_id", + "posthog_team"."extra_settings", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days" + FROM "posthog_team" + WHERE "posthog_team"."id" = 2 + LIMIT 21 /*controller='project_notebooks-list',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.10 + ' + SELECT "posthog_notebook"."id", + "posthog_notebook"."short_id", + "posthog_notebook"."team_id", + "posthog_notebook"."title", + "posthog_notebook"."content", + "posthog_notebook"."deleted", + "posthog_notebook"."version", + "posthog_notebook"."created_at", + "posthog_notebook"."created_by_id", + "posthog_notebook"."last_modified_at", + "posthog_notebook"."last_modified_by_id" + FROM "posthog_notebook" + WHERE "posthog_notebook"."id" = '01891c84-73f6-0000-8ea4-5d223f446585'::uuid + LIMIT 21 /*controller='project_notebooks-detail',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/%28%3FP%3Cshort_id%3E%5B%5E/.%5D%2B%29/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.11 + ' + SELECT "posthog_notebook"."id", + "posthog_notebook"."short_id", + "posthog_notebook"."team_id", + "posthog_notebook"."title", + "posthog_notebook"."content", + "posthog_notebook"."deleted", + "posthog_notebook"."version", + "posthog_notebook"."created_at", + "posthog_notebook"."created_by_id", + "posthog_notebook"."last_modified_at", + "posthog_notebook"."last_modified_by_id" + FROM "posthog_notebook" + WHERE "posthog_notebook"."id" = '01891c84-73f6-0000-8ea4-5d223f446585'::uuid + LIMIT 21 + FOR + UPDATE /*controller='project_notebooks-detail',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/%28%3FP%3Cshort_id%3E%5B%5E/.%5D%2B%29/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.12 + ' + SELECT "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."session_recording_opt_in", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."primary_dashboard_id", + "posthog_team"."extra_settings", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days", + "posthog_team"."plugins_opt_in", + "posthog_team"."opt_out_capture", + "posthog_team"."event_names", + "posthog_team"."event_names_with_usage", + "posthog_team"."event_properties", + "posthog_team"."event_properties_with_usage", + "posthog_team"."event_properties_numerical" + FROM "posthog_team" + WHERE "posthog_team"."id" = 2 + LIMIT 21 /*controller='project_notebooks-detail',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/%28%3FP%3Cshort_id%3E%5B%5E/.%5D%2B%29/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.13 + ' + SELECT "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."session_recording_opt_in", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."primary_dashboard_id", + "posthog_team"."extra_settings", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days", + "posthog_team"."plugins_opt_in", + "posthog_team"."opt_out_capture", + "posthog_team"."event_names", + "posthog_team"."event_names_with_usage", + "posthog_team"."event_properties", + "posthog_team"."event_properties_with_usage", + "posthog_team"."event_properties_numerical" + FROM "posthog_team" + WHERE "posthog_team"."id" = 2 + LIMIT 21 /*controller='project_notebooks-detail',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/%28%3FP%3Cshort_id%3E%5B%5E/.%5D%2B%29/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.14 + ' + SELECT "posthog_user"."id", + "posthog_user"."password", + "posthog_user"."last_login", + "posthog_user"."first_name", + "posthog_user"."last_name", + "posthog_user"."is_staff", + "posthog_user"."is_active", + "posthog_user"."date_joined", + "posthog_user"."uuid", + "posthog_user"."current_organization_id", + "posthog_user"."current_team_id", + "posthog_user"."email", + "posthog_user"."pending_email", + "posthog_user"."temporary_token", + "posthog_user"."distinct_id", + "posthog_user"."is_email_verified", + "posthog_user"."requested_password_reset_at", + "posthog_user"."has_seen_product_intro_for", + "posthog_user"."email_opt_in", + "posthog_user"."partial_notification_settings", + "posthog_user"."anonymize_data", + "posthog_user"."toolbar_mode", + "posthog_user"."events_column_config" + FROM "posthog_user" + WHERE "posthog_user"."id" = 2 + LIMIT 21 /*controller='project_notebooks-detail',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/%28%3FP%3Cshort_id%3E%5B%5E/.%5D%2B%29/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.15 + ' + SELECT "posthog_user"."id", + "posthog_user"."password", + "posthog_user"."last_login", + "posthog_user"."first_name", + "posthog_user"."last_name", + "posthog_user"."is_staff", + "posthog_user"."is_active", + "posthog_user"."date_joined", + "posthog_user"."uuid", + "posthog_user"."current_organization_id", + "posthog_user"."current_team_id", + "posthog_user"."email", + "posthog_user"."pending_email", + "posthog_user"."temporary_token", + "posthog_user"."distinct_id", + "posthog_user"."is_email_verified", + "posthog_user"."has_seen_product_intro_for", + "posthog_user"."email_opt_in", + "posthog_user"."partial_notification_settings", + "posthog_user"."anonymize_data", + "posthog_user"."toolbar_mode", + "posthog_user"."events_column_config" + FROM "posthog_user" + WHERE "posthog_user"."id" = 2 + LIMIT 21 /**/ + ' +--- +# name: TestNotebooks.test_updates_notebook.16 + ' + SELECT "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."session_recording_opt_in", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."primary_dashboard_id", + "posthog_team"."extra_settings", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days" + FROM "posthog_team" + WHERE "posthog_team"."id" = 2 + LIMIT 21 /*controller='project_notebooks-all-activity',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/activity/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.17 + ' + SELECT "posthog_organizationmembership"."id", + "posthog_organizationmembership"."organization_id", + "posthog_organizationmembership"."user_id", + "posthog_organizationmembership"."level", + "posthog_organizationmembership"."joined_at", + "posthog_organizationmembership"."updated_at", + "posthog_organization"."id", + "posthog_organization"."name", + "posthog_organization"."slug", + "posthog_organization"."created_at", + "posthog_organization"."updated_at", + "posthog_organization"."plugins_access_level", + "posthog_organization"."for_internal_metrics", + "posthog_organization"."is_member_join_email_enabled", + "posthog_organization"."enforce_2fa", + "posthog_organization"."customer_id", + "posthog_organization"."available_features", + "posthog_organization"."available_product_features", + "posthog_organization"."usage", + "posthog_organization"."setup_section_2_completed", + "posthog_organization"."personalization", + "posthog_organization"."domain_whitelist" + FROM "posthog_organizationmembership" + INNER JOIN "posthog_organization" ON ("posthog_organizationmembership"."organization_id" = "posthog_organization"."id") + WHERE "posthog_organizationmembership"."user_id" = 2 /*controller='project_notebooks-all-activity',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/activity/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.18 + ' + SELECT "posthog_instancesetting"."id", + "posthog_instancesetting"."key", + "posthog_instancesetting"."raw_value" + FROM "posthog_instancesetting" + WHERE "posthog_instancesetting"."key" = 'constance:posthog:RATE_LIMIT_ENABLED' + ORDER BY "posthog_instancesetting"."id" ASC + LIMIT 1 /*controller='project_notebooks-all-activity',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/activity/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.19 + ' + SELECT COUNT(*) AS "__count" + FROM "posthog_activitylog" + WHERE ("posthog_activitylog"."scope" = 'Notebook' + AND "posthog_activitylog"."team_id" = 2) /*controller='project_notebooks-all-activity',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/activity/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.2 + ' + SELECT "posthog_organizationmembership"."id", + "posthog_organizationmembership"."organization_id", + "posthog_organizationmembership"."user_id", + "posthog_organizationmembership"."level", + "posthog_organizationmembership"."joined_at", + "posthog_organizationmembership"."updated_at", + "posthog_organization"."id", + "posthog_organization"."name", + "posthog_organization"."slug", + "posthog_organization"."created_at", + "posthog_organization"."updated_at", + "posthog_organization"."plugins_access_level", + "posthog_organization"."for_internal_metrics", + "posthog_organization"."is_member_join_email_enabled", + "posthog_organization"."enforce_2fa", + "posthog_organization"."customer_id", + "posthog_organization"."available_features", + "posthog_organization"."available_product_features", + "posthog_organization"."usage", + "posthog_organization"."setup_section_2_completed", + "posthog_organization"."personalization", + "posthog_organization"."domain_whitelist" + FROM "posthog_organizationmembership" + INNER JOIN "posthog_organization" ON ("posthog_organizationmembership"."organization_id" = "posthog_organization"."id") + WHERE "posthog_organizationmembership"."user_id" = 2 /*controller='project_notebooks-list',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.20 + ' + SELECT "posthog_activitylog"."id", + "posthog_activitylog"."team_id", + "posthog_activitylog"."organization_id", + "posthog_activitylog"."user_id", + "posthog_activitylog"."is_system", + "posthog_activitylog"."activity", + "posthog_activitylog"."item_id", + "posthog_activitylog"."scope", + "posthog_activitylog"."detail", + "posthog_activitylog"."created_at", + "posthog_user"."id", + "posthog_user"."password", + "posthog_user"."last_login", + "posthog_user"."first_name", + "posthog_user"."last_name", + "posthog_user"."is_staff", + "posthog_user"."is_active", + "posthog_user"."date_joined", + "posthog_user"."uuid", + "posthog_user"."current_organization_id", + "posthog_user"."current_team_id", + "posthog_user"."email", + "posthog_user"."pending_email", + "posthog_user"."temporary_token", + "posthog_user"."distinct_id", + "posthog_user"."is_email_verified", + "posthog_user"."requested_password_reset_at", + "posthog_user"."has_seen_product_intro_for", + "posthog_user"."email_opt_in", + "posthog_user"."partial_notification_settings", + "posthog_user"."anonymize_data", + "posthog_user"."toolbar_mode", + "posthog_user"."events_column_config" + FROM "posthog_activitylog" + LEFT OUTER JOIN "posthog_user" ON ("posthog_activitylog"."user_id" = "posthog_user"."id") + WHERE ("posthog_activitylog"."scope" = 'Notebook' + AND "posthog_activitylog"."team_id" = 2) + ORDER BY "posthog_activitylog"."created_at" DESC + LIMIT 2 /*controller='project_notebooks-all-activity',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/activity/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.3 + ' + SELECT "posthog_instancesetting"."id", + "posthog_instancesetting"."key", + "posthog_instancesetting"."raw_value" + FROM "posthog_instancesetting" + WHERE "posthog_instancesetting"."key" = 'constance:posthog:RATE_LIMIT_ENABLED' + ORDER BY "posthog_instancesetting"."id" ASC + LIMIT 1 /*controller='project_notebooks-list',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.4 + ' + SELECT "posthog_user"."id", + "posthog_user"."password", + "posthog_user"."last_login", + "posthog_user"."first_name", + "posthog_user"."last_name", + "posthog_user"."is_staff", + "posthog_user"."is_active", + "posthog_user"."date_joined", + "posthog_user"."uuid", + "posthog_user"."current_organization_id", + "posthog_user"."current_team_id", + "posthog_user"."email", + "posthog_user"."pending_email", + "posthog_user"."temporary_token", + "posthog_user"."distinct_id", + "posthog_user"."is_email_verified", + "posthog_user"."has_seen_product_intro_for", + "posthog_user"."email_opt_in", + "posthog_user"."partial_notification_settings", + "posthog_user"."anonymize_data", + "posthog_user"."toolbar_mode", + "posthog_user"."events_column_config" + FROM "posthog_user" + WHERE "posthog_user"."id" = 2 + LIMIT 21 /**/ + ' +--- +# name: TestNotebooks.test_updates_notebook.5 + ' + SELECT "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."session_recording_opt_in", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."primary_dashboard_id", + "posthog_team"."extra_settings", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days" + FROM "posthog_team" + WHERE "posthog_team"."id" = 2 + LIMIT 21 /*controller='project_notebooks-detail',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/%28%3FP%3Cshort_id%3E%5B%5E/.%5D%2B%29/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.6 + ' + SELECT "posthog_organizationmembership"."id", + "posthog_organizationmembership"."organization_id", + "posthog_organizationmembership"."user_id", + "posthog_organizationmembership"."level", + "posthog_organizationmembership"."joined_at", + "posthog_organizationmembership"."updated_at", + "posthog_organization"."id", + "posthog_organization"."name", + "posthog_organization"."slug", + "posthog_organization"."created_at", + "posthog_organization"."updated_at", + "posthog_organization"."plugins_access_level", + "posthog_organization"."for_internal_metrics", + "posthog_organization"."is_member_join_email_enabled", + "posthog_organization"."enforce_2fa", + "posthog_organization"."customer_id", + "posthog_organization"."available_features", + "posthog_organization"."available_product_features", + "posthog_organization"."usage", + "posthog_organization"."setup_section_2_completed", + "posthog_organization"."personalization", + "posthog_organization"."domain_whitelist" + FROM "posthog_organizationmembership" + INNER JOIN "posthog_organization" ON ("posthog_organizationmembership"."organization_id" = "posthog_organization"."id") + WHERE "posthog_organizationmembership"."user_id" = 2 /*controller='project_notebooks-detail',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/%28%3FP%3Cshort_id%3E%5B%5E/.%5D%2B%29/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.7 + ' + SELECT "posthog_instancesetting"."id", + "posthog_instancesetting"."key", + "posthog_instancesetting"."raw_value" + FROM "posthog_instancesetting" + WHERE "posthog_instancesetting"."key" = 'constance:posthog:RATE_LIMIT_ENABLED' + ORDER BY "posthog_instancesetting"."id" ASC + LIMIT 1 /*controller='project_notebooks-detail',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/%28%3FP%3Cshort_id%3E%5B%5E/.%5D%2B%29/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.8 + ' + SELECT "posthog_notebook"."id", + "posthog_notebook"."short_id", + "posthog_notebook"."team_id", + "posthog_notebook"."title", + "posthog_notebook"."content", + "posthog_notebook"."deleted", + "posthog_notebook"."version", + "posthog_notebook"."created_at", + "posthog_notebook"."created_by_id", + "posthog_notebook"."last_modified_at", + "posthog_notebook"."last_modified_by_id", + "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."session_recording_opt_in", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."primary_dashboard_id", + "posthog_team"."extra_settings", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days", + "posthog_team"."plugins_opt_in", + "posthog_team"."opt_out_capture", + "posthog_team"."event_names", + "posthog_team"."event_names_with_usage", + "posthog_team"."event_properties", + "posthog_team"."event_properties_with_usage", + "posthog_team"."event_properties_numerical", + "posthog_user"."id", + "posthog_user"."password", + "posthog_user"."last_login", + "posthog_user"."first_name", + "posthog_user"."last_name", + "posthog_user"."is_staff", + "posthog_user"."is_active", + "posthog_user"."date_joined", + "posthog_user"."uuid", + "posthog_user"."current_organization_id", + "posthog_user"."current_team_id", + "posthog_user"."email", + "posthog_user"."pending_email", + "posthog_user"."temporary_token", + "posthog_user"."distinct_id", + "posthog_user"."is_email_verified", + "posthog_user"."requested_password_reset_at", + "posthog_user"."has_seen_product_intro_for", + "posthog_user"."email_opt_in", + "posthog_user"."partial_notification_settings", + "posthog_user"."anonymize_data", + "posthog_user"."toolbar_mode", + "posthog_user"."events_column_config", + T4."id", + T4."password", + T4."last_login", + T4."first_name", + T4."last_name", + T4."is_staff", + T4."is_active", + T4."date_joined", + T4."uuid", + T4."current_organization_id", + T4."current_team_id", + T4."email", + T4."pending_email", + T4."temporary_token", + T4."distinct_id", + T4."is_email_verified", + T4."requested_password_reset_at", + T4."has_seen_product_intro_for", + T4."email_opt_in", + T4."partial_notification_settings", + T4."anonymize_data", + T4."toolbar_mode", + T4."events_column_config" + FROM "posthog_notebook" + INNER JOIN "posthog_team" ON ("posthog_notebook"."team_id" = "posthog_team"."id") + LEFT OUTER JOIN "posthog_user" ON ("posthog_notebook"."created_by_id" = "posthog_user"."id") + LEFT OUTER JOIN "posthog_user" T4 ON ("posthog_notebook"."last_modified_by_id" = T4."id") + WHERE ("posthog_notebook"."team_id" = 2 + AND "posthog_notebook"."short_id" = 'uQQDQ33m') + LIMIT 21 /*controller='project_notebooks-detail',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/%28%3FP%3Cshort_id%3E%5B%5E/.%5D%2B%29/%3F%24'*/ + ' +--- +# name: TestNotebooks.test_updates_notebook.9 + ' + SELECT "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."session_recording_opt_in", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."primary_dashboard_id", + "posthog_team"."extra_settings", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days", + "posthog_team"."plugins_opt_in", + "posthog_team"."opt_out_capture", + "posthog_team"."event_names", + "posthog_team"."event_names_with_usage", + "posthog_team"."event_properties", + "posthog_team"."event_properties_with_usage", + "posthog_team"."event_properties_numerical" + FROM "posthog_team" + WHERE "posthog_team"."id" = 2 + LIMIT 21 /*controller='project_notebooks-detail',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/notebooks/%28%3FP%3Cshort_id%3E%5B%5E/.%5D%2B%29/%3F%24'*/ + ' +--- diff --git a/posthog/api/test/notebooks/test_notebook.py b/posthog/api/test/notebooks/test_notebook.py index 0b72e0fb8daa0..bdac77898997e 100644 --- a/posthog/api/test/notebooks/test_notebook.py +++ b/posthog/api/test/notebooks/test_notebook.py @@ -8,10 +8,10 @@ from posthog.models import Team, Organization from posthog.models.notebook.notebook import Notebook from posthog.models.user import User -from posthog.test.base import APIBaseTest +from posthog.test.base import APIBaseTest, QueryMatchingTest, snapshot_postgres_queries -class TestNotebooks(APIBaseTest): +class TestNotebooks(APIBaseTest, QueryMatchingTest): def created_activity(self, item_id: str, short_id: str) -> Dict: return { "activity": "created", @@ -100,6 +100,7 @@ def test_gets_individual_notebook_by_shortid(self) -> None: assert response.status_code == status.HTTP_200_OK assert response.json()["short_id"] == create_response.json()["short_id"] + @snapshot_postgres_queries def test_updates_notebook(self) -> None: response = self.client.post(f"/api/projects/{self.team.id}/notebooks/", data={}) assert response.status_code == status.HTTP_201_CREATED