Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: notebooks sharing step 1 #16415

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
60b7f93
basic listing of notebooks
pauldambra Jul 6, 2023
6ac86ac
wip
pauldambra Jul 6, 2023
13ffbf5
wip
pauldambra Jul 6, 2023
30a6f6b
Merge branch 'master' into feat/notebooks-sharing-step1
pauldambra Jul 6, 2023
173ebd9
working
pauldambra Jul 6, 2023
87f2eba
put the button behind a feature flag
pauldambra Jul 6, 2023
545a7dc
fix
pauldambra Jul 6, 2023
252f9be
first pass hiding nodes that don't work yet
pauldambra Jul 6, 2023
21a65a1
Update query snapshots
github-actions[bot] Jul 6, 2023
c11cf0c
Merge branch 'master' into feat/notebooks-sharing-step1
pauldambra Jul 7, 2023
2d6ba84
Merge branch 'master' into feat/notebooks-sharing-step1
pauldambra Jul 7, 2023
406a6e3
allow constraints added in migrations
pauldambra Jul 7, 2023
4e31dbd
Update query snapshots
github-actions[bot] Jul 7, 2023
1a25e48
nicer cannot share panel
pauldambra Jul 7, 2023
f133050
not even fake editing
pauldambra Jul 7, 2023
716fb30
Update query snapshots
github-actions[bot] Jul 7, 2023
e048788
fix OG image gen
pauldambra Jul 7, 2023
a56b285
Merge branch 'master' into feat/notebooks-sharing-step1
pauldambra Jul 8, 2023
d09e62f
Merge branch 'master' into feat/notebooks-sharing-step1
pauldambra Jul 8, 2023
e397dca
allow migration through the test
pauldambra Jul 8, 2023
cdee7e7
notebook exports can't wait for insight loading state
pauldambra Jul 9, 2023
9730d39
Merge branch 'master' into feat/notebooks-sharing-step1
pauldambra Jul 9, 2023
7656c80
so, you provide the class you expect _after_ loading?
pauldambra Jul 9, 2023
a0f1bb1
Update UI snapshots for `chromium` (2)
github-actions[bot] Jul 9, 2023
337a3b5
Merge branch 'master' into feat/notebooks-sharing-step1
pauldambra Jul 10, 2023
d95ddfb
noice
pauldambra Jul 10, 2023
eb46c60
fix
pauldambra Jul 10, 2023
caeee75
nice
pauldambra Jul 10, 2023
d4257d7
if a developer makes a mistake, throw an error and tell them
pauldambra Jul 10, 2023
a372fa2
fix
pauldambra Jul 10, 2023
aba5be1
Merge branch 'master' into feat/notebooks-sharing-step1
pauldambra Jul 11, 2023
1961586
don't reuse the viewmode like a fool
pauldambra Jul 11, 2023
266bce4
see no evil
pauldambra Jul 11, 2023
9ba24d7
sharing storybook
pauldambra Jul 11, 2023
eb9eed9
Update frontend/src/scenes/notebooks/Nodes/NodeWrapper.tsx
pauldambra Jul 11, 2023
a30d58f
Update UI snapshots for `chromium` (2)
github-actions[bot] Jul 11, 2023
71854ea
Merge branch 'master' into feat/notebooks-sharing-step1
pauldambra Aug 29, 2023
2bec210
all definitely broken
pauldambra Aug 29, 2023
ef43e09
Update query snapshots
github-actions[bot] Aug 29, 2023
ffaf809
Update query snapshots
github-actions[bot] Aug 29, 2023
4ef33a6
Merge branch 'master' into feat/notebooks-sharing-step1
pauldambra Aug 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import './commands'
import 'cypress-axe'
import { decideResponse } from '../fixtures/api/decide'

try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('cypress-terminal-report/src/installLogsCollector')()
} catch {}
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('cypress-terminal-report/src/installLogsCollector')()

// Add console errors into cypress logs. This helps with failures in Github Actions which otherwise swallows them.
// From: https://github.com/cypress-io/cypress/issues/300#issuecomment-688915086
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions frontend/src/exporter/ExportedNotebook/ExportedNotebook.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Notebook } from 'scenes/notebooks/Notebook/Notebook'
import { NotebookMode, NotebookType } from '~/types'

export function ExportedNotebook(props: { notebook: NotebookType }): JSX.Element {
return (
<div className={'ExportedNotebook Notebook--compact pt-4 px-8 mb-8'}>
<Notebook
shortId={props.notebook.short_id}
cachedNotebook={props.notebook}
editable={false}
viewMode={NotebookMode.SharedView}
/>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect } from 'react'
import { Meta, StoryFn, StoryObj } from '@storybook/react'
import { Exporter } from './Exporter'
import { dashboard } from '~/exporter/__mocks__/Exporter.mocks'
import { dashboard, notebook } from '~/exporter/__mocks__/Exporter.mocks'
import { ExportType } from '~/exporter/types'

type Story = StoryObj<typeof Exporter>
Expand Down Expand Up @@ -142,3 +142,6 @@ UserPathsInsight.args = { insight: require('../mocks/fixtures/api/projects/:team

export const Dashboard: Story = Template.bind({})
Dashboard.args = { dashboard }

export const Notebook = Template.bind({})
Notebook.args = { notebook }
44 changes: 44 additions & 0 deletions frontend/src/exporter/Exporter.notebook.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useEffect } from 'react'
import { ComponentMeta, ComponentStory } from '@storybook/react'
import { Exporter } from './Exporter'
import { notebook } from '~/exporter/__mocks__/Exporter.mocks'

export default {
title: 'Exporter/Exporter',
component: Exporter,
args: {
type: 'embed',
whitelabel: false,
noHeader: false,
legend: false,
},
parameters: {
docs: {
inlineStories: false,
iframeHeight: 400,
source: { state: 'close' },
},
testOptions: {
// KLUDGE: duplicated from Exporter.insight-and-dashboard.stories.tsx
// so that we can set the correct waitForLoadersToDisappear
waitForLoadersToDisappear: '.Notebook',
},
mockDate: '2023-02-01',
viewMode: 'story',
},
} as ComponentMeta<typeof Exporter>

const Template: ComponentStory<typeof Exporter> = (props) => {
useEffect(() => {
document.body.className = ''
document.documentElement.className = `export-type-${props.type}`
}, [props.type])
return (
<div className={`storybook-export-type-${props.type}`}>
<Exporter {...props} />
</div>
)
}

export const Notebook = Template.bind({})
Notebook.args = { notebook }
11 changes: 8 additions & 3 deletions frontend/src/exporter/Exporter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ import { teamLogic } from 'scenes/teamLogic'
import { SessionRecordingPlayer } from 'scenes/session-recordings/player/SessionRecordingPlayer'
import { SessionRecordingPlayerMode } from 'scenes/session-recordings/player/sessionRecordingPlayerLogic'
import { exporterViewLogic } from './exporterViewLogic'
import { ExportedNotebook } from '~/exporter/ExportedNotebook/ExportedNotebook'

export function Exporter(props: ExportedData): JSX.Element {
// NOTE: Mounting the logic is important as it is used by sub-logics
const { exportedData } = useValues(exporterViewLogic(props))
const { type, dashboard, insight, recording, accessToken, ...exportOptions } = exportedData
const { type, dashboard, insight, recording, notebook, accessToken, ...exportOptions } = exportedData
const { whitelabel, showInspector = false } = exportOptions

const { currentTeam } = useValues(teamLogic)
Expand Down Expand Up @@ -83,16 +84,20 @@ export function Exporter(props: ExportedData): JSX.Element {
autoPlay={false}
noInspector={!showInspector}
/>
) : notebook ? (
<ExportedNotebook notebook={notebook} />
) : (
<h1 className="text-center p-4">Something went wrong...</h1>
)}
{!whitelabel && dashboard && (
{!whitelabel && (dashboard || notebook) && (
<div className="text-center pb-4">
{type === ExportType.Image ? <Logo className="text-lg" /> : null}
<div>
Made with{' '}
<Link
to="https://posthog.com?utm_medium=in-product&utm_campaign=shared-dashboard"
to={`https://posthog.com?utm_medium=in-product&utm_campaign=shared-${
dashboard ? 'dashboard' : 'notebook'
}`}
target="_blank"
>
PostHog — open-source product analytics
Expand Down
90 changes: 90 additions & 0 deletions frontend/src/exporter/__mocks__/Exporter.mocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
InsightColor,
InsightShortId,
InsightType,
NotebookType,
} from '~/types'
import { FunnelLayout, ShownAsValue } from 'lib/constants'

Expand Down Expand Up @@ -1258,3 +1259,92 @@ export const dashboard: DashboardType = {
effective_privilege_level: 37,
tags: [],
}

export const notebook = {
short_id: 'BF5V6QRk',
title: 'Example Exported Notebook! 🥳',
content: {
type: 'doc',
content: [
{
type: 'heading',
attrs: {
level: 1,
},
content: [
{
text: 'Example Exported Notebook! 🥳',
type: 'text',
},
],
},
{
type: 'paragraph',
content: [
{
text: 'Notebooks are a powerful way of working with all of the various parts of PostHog, allowing you to bring Insights, Replays, Feature Flags, Events and much more into one place. Whether it is an ',
type: 'text',
},
{
text: 'ad-hoc analysis',
type: 'text',
marks: [
{
type: 'bold',
},
],
},
{
text: ' or a ',
type: 'text',
},
{
text: 'bug investigation',
type: 'text',
marks: [
{
type: 'bold',
},
],
},
{
text: ' or a ',
type: 'text',
},
{
text: 'feature release',
type: 'text',
marks: [
{
type: 'bold',
},
],
},
{
text: '. We have only just got started with Notebooks so try it out and let us know what you think.',
type: 'text',
},
],
},
],
},
version: 0,
created_at: '2023-06-19T16:05:39.318986Z',
created_by: {
id: 17167,
uuid: '01830cdb-626b-0000-1f82-98ea06a5e75e',
distinct_id: 'NqCqvZGlRnjkrqXDUfjrj889UfgJWMV5Gzoluet5W65',
first_name: 'Annika',
email: 'annika@posthog.com',
is_email_verified: true,
},
last_modified_at: '2023-06-19T16:05:39.318458Z',
last_modified_by: {
id: 17167,
uuid: '01830cdb-626b-0000-1f82-98ea06a5e75e',
distinct_id: 'NqCqvZGlRnjkrqXDUfjrj889UfgJWMV5Gzoluet5W65',
first_name: 'Annika',
email: 'annika@posthog.com',
is_email_verified: true,
},
} satisfies NotebookType
3 changes: 2 additions & 1 deletion frontend/src/exporter/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DashboardType, InsightModel, SessionRecordingType } from '~/types'
import { DashboardType, InsightModel, NotebookType, SessionRecordingType } from '~/types'

export enum ExportType {
Image = 'image',
Expand All @@ -19,5 +19,6 @@ export interface ExportedData extends ExportOptions {
type: ExportType
dashboard?: DashboardType
insight?: InsightModel
notebook?: NotebookType
recording?: SessionRecordingType
}
17 changes: 17 additions & 0 deletions frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
IntegrationType,
MediaUploadResponse,
NewEarlyAccessFeatureType,
NotebookListItemType,
NotebookType,
OrganizationResourcePermissionType,
OrganizationType,
Expand Down Expand Up @@ -519,6 +520,10 @@ class ApiRequest {
return this.notebooks(teamId).addPathComponent(id)
}

public notebookSharing(id: NotebookType['short_id'], teamId?: TeamType['id']): ApiRequest {
return this.notebook(id, teamId).addPathComponent('sharing')
}

// Batch Exports
public batchExports(teamId?: TeamType['id']): ApiRequest {
return this.projectsDetail(teamId).addPathComponent('batch_exports')
Expand Down Expand Up @@ -1101,17 +1106,21 @@ const api = {
dashboardId,
insightId,
recordingId,
notebookShortId,
}: {
dashboardId?: DashboardType['id']
insightId?: InsightModel['id']
recordingId?: SessionRecordingType['id']
notebookShortId?: NotebookType['short_id']
}): Promise<SharingConfigurationType | null> {
return dashboardId
? new ApiRequest().dashboardSharing(dashboardId).get()
: insightId
? new ApiRequest().insightSharing(insightId).get()
: recordingId
? new ApiRequest().recordingSharing(recordingId).get()
: notebookShortId
? new ApiRequest().notebookSharing(notebookShortId).get()
: null
},

Expand All @@ -1120,10 +1129,12 @@ const api = {
dashboardId,
insightId,
recordingId,
notebookShortId,
}: {
dashboardId?: DashboardType['id']
insightId?: InsightModel['id']
recordingId?: SessionRecordingType['id']
notebookShortId?: NotebookType['short_id']
},
data: Partial<SharingConfigurationType>
): Promise<SharingConfigurationType | null> {
Expand All @@ -1133,6 +1144,8 @@ const api = {
? new ApiRequest().insightSharing(insightId).update({ data })
: recordingId
? new ApiRequest().recordingSharing(recordingId).update({ data })
: notebookShortId
? new ApiRequest().notebookSharing(notebookShortId).update({ data })
: null
},
},
Expand Down Expand Up @@ -1306,6 +1319,10 @@ const api = {
): Promise<NotebookType> {
return await new ApiRequest().notebook(notebookId).update({ data })
},
// TODO do I need this?
async listBasic(): Promise<PaginatedResponse<NotebookListItemType>> {
return await new ApiRequest().notebooks().withQueryString('basic=true').get()
},
async list(
contains?: { type: NotebookNodeType; attrs: Record<string, string> }[],
createdBy?: UserBasicType['uuid'],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { columnConfiguratorLogicType } from './columnConfiguratorLogicType'
import { tableConfigLogic } from 'lib/components/ResizableTable/tableConfigLogic'
import { kea } from 'kea'
import { teamLogic } from 'scenes/teamLogic'

export interface ColumnConfiguratorLogicProps {
selectedColumns: string[] // the columns the table is currently displaying
}

export const columnConfiguratorLogic = kea<columnConfiguratorLogicType>({
path: ['lib', 'components', 'ResizableTable', 'columnConfiguratorLogic'],
props: { selectedColumns: [] } as ColumnConfiguratorLogicProps,
connect: [tableConfigLogic],
actions: {
selectColumn: (column: string) => ({ column }),
unselectColumn: (column: string) => ({ column }),
resetColumns: (columns: string[]) => ({ columns }),
setColumns: (columns: string[]) => ({ columns }),
toggleSaveAsDefault: true,
save: true,
},
reducers: ({ props }) => ({
selectedColumns: [
props.selectedColumns,
{
selectColumn: (state, { column }) => Array.from(new Set([...state, column])),
unselectColumn: (state, { column }) => state.filter((c) => c !== column),
resetColumns: (_, { columns }) => columns,
setColumns: (_, { columns }) => columns,
},
],
saveAsDefault: [
false,
{
toggleSaveAsDefault: (state) => !state,
},
],
}),
listeners: ({ values, actions }) => ({
save: () => {
tableConfigLogic.actions.setSelectedColumns(values.selectedColumns)
if (values.saveAsDefault) {
teamLogic.actions.updateCurrentTeam({ live_events_columns: values.selectedColumns })
actions.toggleSaveAsDefault()
}
},
}),
})
Loading