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: Recompute notebook title #17526

Merged
merged 16 commits into from
Sep 19, 2023
19 changes: 14 additions & 5 deletions frontend/src/scenes/notebooks/Nodes/NodeWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
NodeViewProps,
getExtensionField,
} from '@tiptap/react'
import { ReactNode, useCallback, useRef } from 'react'
import { ReactNode, useCallback, useMemo, useRef } from 'react'
import clsx from 'clsx'
import { IconClose, IconDragHandle, IconFilter, IconLink, IconUnfoldLess, IconUnfoldMore } from 'lib/lemon-ui/icons'
import { LemonButton } from '@posthog/lemon-ui'
Expand All @@ -29,9 +29,11 @@ import {
} from '../Notebook/utils'

export interface NodeWrapperProps<T extends CustomNotebookNodeAttributes> {
title: string | ((attributes: CustomNotebookNodeAttributes) => Promise<string>)
nodeType: NotebookNodeType
children?: ReactNode | ((isEdit: boolean, isPreview: boolean) => ReactNode)

// Meta properties - these should never be too advanced - more advanced should be done via updateAttributes in the component
defaultTitle: string | ((attributes: NotebookNodeAttributes<T>) => string)
href?: string | ((attributes: NotebookNodeAttributes<T>) => string | undefined)

// Sizing
Expand All @@ -48,7 +50,7 @@ export interface NodeWrapperProps<T extends CustomNotebookNodeAttributes> {
}

export function NodeWrapper<T extends CustomNotebookNodeAttributes>({
title: titleOrGenerator,
defaultTitle,
nodeType,
children,
selected,
Expand Down Expand Up @@ -80,13 +82,12 @@ export function NodeWrapper<T extends CustomNotebookNodeAttributes>({
nodeId,
notebookLogic: mountedNotebookLogic,
getPos,
title: titleOrGenerator,
resizeable: resizeableOrGenerator,
widgets,
startExpanded,
}
const nodeLogic = useMountedLogic(notebookNodeLogic(nodeLogicProps))
const { title, resizeable, expanded } = useValues(nodeLogic)
const { resizeable, expanded } = useValues(nodeLogic)
const { setExpanded, deleteNode } = useActions(nodeLogic)

const [ref, inView] = useInView({ triggerOnce: true })
Expand Down Expand Up @@ -115,6 +116,14 @@ export function NodeWrapper<T extends CustomNotebookNodeAttributes>({
}, [resizeable, updateAttributes])

const parsedHref = typeof href === 'function' ? href(attributes) : href
// If a title is set on the attrs we use it. Otherwise we use the base component title.
const title = useMemo(() => {
return attributes.title
? attributes.title
: typeof defaultTitle === 'function'
? defaultTitle(attributes)
benjackwhite marked this conversation as resolved.
Show resolved Hide resolved
: defaultTitle
}, [attributes.title, defaultTitle])

// Element is resizable if resizable is set to true. If expandable is set to true then is is only resizable if expanded is true
const isResizeable = resizeable && (!expandable || expanded)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,28 @@ import { urls } from 'scenes/urls'
import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton'
import { notebookNodeLogic } from './notebookNodeLogic'
import { JSONContent, NotebookNodeViewProps } from '../Notebook/utils'
import api from 'lib/api'
import {
EarlyAccessFeatureLogicProps,
earlyAccessFeatureLogic,
} from 'scenes/early-access-features/earlyAccessFeatureLogic'
import { PersonList } from 'scenes/early-access-features/EarlyAccessFeature'
import { buildFlagContent } from './NotebookNodeFlag'
import { useEffect } from 'react'

const Component = (props: NotebookNodeViewProps<NotebookNodeEarlyAccessAttributes>): JSX.Element => {
const { id } = props.attributes
const { earlyAccessFeature, earlyAccessFeatureLoading } = useValues(earlyAccessFeatureLogic({ id }))
const { expanded } = useValues(notebookNodeLogic)
const { insertAfter } = useActions(notebookNodeLogic)

useEffect(() => {
props.updateAttributes({
title: earlyAccessFeature.name
? `Early Access Management: ${earlyAccessFeature.name}`
: 'Early Access Management',
})
}, [earlyAccessFeature?.name])

return (
<div>
<BindLogic logic={earlyAccessFeatureLogic} props={{ id }}>
Expand Down Expand Up @@ -109,18 +117,7 @@ type NotebookNodeEarlyAccessAttributes = {

export const NotebookNodeEarlyAccessFeature = createPostHogWidgetNode<NotebookNodeEarlyAccessAttributes>({
nodeType: NotebookNodeType.EarlyAccessFeature,
title: async (attributes) => {
const mountedEarlyAccessFeatureLogic = earlyAccessFeatureLogic.findMounted({ id: attributes.id })
let title = mountedEarlyAccessFeatureLogic?.values.earlyAccessFeature.name || null
if (title === null) {
const retrievedEarlyAccessFeature: EarlyAccessFeatureType = await api.earlyAccessFeatures.get(attributes.id)
if (retrievedEarlyAccessFeature) {
title = retrievedEarlyAccessFeature.name
}
}

return title ? `Early Access Management: ${title}` : 'Early Access Management'
},
defaultTitle: 'Early Access Management',
Component,
heightEstimate: '3rem',
href: (attrs) => urls.earlyAccessFeature(attrs.id),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ type NotebookNodeExperimentAttributes = {

export const NotebookNodeExperiment = createPostHogWidgetNode<NotebookNodeExperimentAttributes>({
nodeType: NotebookNodeType.Experiment,
title: 'Experiment',
defaultTitle: 'Experiment',
Component,
heightEstimate: '3rem',
href: (attrs) => urls.experiment(attrs.id),
Expand Down
21 changes: 7 additions & 14 deletions frontend/src/scenes/notebooks/Nodes/NotebookNodeFlag.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createPostHogWidgetNode } from 'scenes/notebooks/Nodes/NodeWrapper'
import { FeatureFlagType, NotebookNodeType } from '~/types'
import { NotebookNodeType } from '~/types'
import { BindLogic, useActions, useValues } from 'kea'
import { featureFlagLogic, FeatureFlagLogicProps } from 'scenes/feature-flags/featureFlagLogic'
import { IconFlag, IconRecording, IconRocketLaunch, IconSurveys } from 'lib/lemon-ui/icons'
Expand All @@ -12,10 +12,10 @@ import { JSONContent, NotebookNodeViewProps } from '../Notebook/utils'
import { buildPlaylistContent } from './NotebookNodePlaylist'
import { buildCodeExampleContent } from './NotebookNodeFlagCodeExample'
import { FeatureFlagReleaseConditions } from 'scenes/feature-flags/FeatureFlagReleaseConditions'
import api from 'lib/api'
import { buildEarlyAccessFeatureContent } from './NotebookNodeEarlyAccessFeature'
import { notebookNodeFlagLogic } from './NotebookNodeFlagLogic'
import { buildSurveyContent } from './NotebookNodeSurvey'
import { useEffect } from 'react'

const Component = (props: NotebookNodeViewProps<NotebookNodeFlagAttributes>): JSX.Element => {
const { id } = props.attributes
Expand All @@ -37,6 +37,10 @@ const Component = (props: NotebookNodeViewProps<NotebookNodeFlagAttributes>): JS
notebookNodeFlagLogic({ id, insertAfter })
)

useEffect(() => {
props.updateAttributes({ title: featureFlag.key ? `Feature flag: ${featureFlag.key}` : 'Feature flag' })
}, [featureFlag.key])

return (
<div>
<BindLogic logic={featureFlagLogic} props={{ id }}>
Expand Down Expand Up @@ -173,18 +177,7 @@ type NotebookNodeFlagAttributes = {

export const NotebookNodeFlag = createPostHogWidgetNode<NotebookNodeFlagAttributes>({
nodeType: NotebookNodeType.FeatureFlag,
title: async (attributes) => {
const mountedFlagLogic = featureFlagLogic.findMounted({ id: attributes.id })
let title = mountedFlagLogic?.values.featureFlag.key || null
if (title === null) {
const retrievedFlag: FeatureFlagType = await api.featureFlags.get(Number(attributes.id))
if (retrievedFlag) {
title = retrievedFlag.key
}
}

return title ? `Feature flag: ${title}` : 'Feature flag'
},
defaultTitle: 'Feature flag',
Component,
heightEstimate: '3rem',
href: (attrs) => urls.featureFlag(attrs.id),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { createPostHogWidgetNode } from 'scenes/notebooks/Nodes/NodeWrapper'
import { FeatureFlagType, NotebookNodeType } from '~/types'
import { NotebookNodeType } from '~/types'
import { useValues } from 'kea'
import { FeatureFlagLogicProps, featureFlagLogic } from 'scenes/feature-flags/featureFlagLogic'
import { FeatureFlagCodeExample } from 'scenes/feature-flags/FeatureFlagCodeExample'
import { urls } from 'scenes/urls'
import { JSONContent, NotebookNodeViewProps } from '../Notebook/utils'
import { notebookNodeLogic } from './notebookNodeLogic'
import api from 'lib/api'
import { useEffect } from 'react'

const Component = (props: NotebookNodeViewProps<NotebookNodeFlagCodeExampleAttributes>): JSX.Element => {
const { id } = props.attributes
const { featureFlag } = useValues(featureFlagLogic({ id }))
const { expanded } = useValues(notebookNodeLogic)

useEffect(() => {
props.updateAttributes({
title: featureFlag.key ? `Feature flag code example: ${featureFlag.key}` : 'Feature flag code example',
})
}, [featureFlag?.key])

return <div className="p-2">{expanded && <FeatureFlagCodeExample featureFlag={featureFlag} />}</div>
}

Expand All @@ -22,19 +28,7 @@ type NotebookNodeFlagCodeExampleAttributes = {

export const NotebookNodeFlagCodeExample = createPostHogWidgetNode<NotebookNodeFlagCodeExampleAttributes>({
nodeType: NotebookNodeType.FeatureFlagCodeExample,
title: async (attributes) => {
const mountedFlagLogic = featureFlagLogic.findMounted({ id: attributes.id })
let title = mountedFlagLogic?.values.featureFlag.key || null

if (title === null) {
const retrievedFlag: FeatureFlagType = await api.featureFlags.get(Number(attributes.id))
if (retrievedFlag) {
title = retrievedFlag.key
}
}

return title ? `Feature flag code example: ${title}` : 'Feature flag code example'
},
defaultTitle: 'Feature flag code example',
Component,
heightEstimate: '3rem',
startExpanded: true,
Expand Down
20 changes: 8 additions & 12 deletions frontend/src/scenes/notebooks/Nodes/NotebookNodePerson.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@ import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton'
import { notebookNodeLogic } from './notebookNodeLogic'
import { NotebookNodeViewProps } from '../Notebook/utils'
import { asDisplay } from 'scenes/persons/person-utils'
import api from 'lib/api'
import { useEffect } from 'react'

const Component = (props: NotebookNodeViewProps<NotebookNodePersonAttributes>): JSX.Element => {
const { id } = props.attributes
const logic = personLogic({ id })
const { person, personLoading } = useValues(logic)
const { expanded } = useValues(notebookNodeLogic)

useEffect(() => {
props.updateAttributes({
title: person ? `Person: ${asDisplay(person)}` : 'Person',
})
}, [person])

return (
<div className="flex flex-col overflow-hidden">
<div className="p-4 flex-0 font-semibold">
Expand Down Expand Up @@ -51,17 +57,7 @@ type NotebookNodePersonAttributes = {

export const NotebookNodePerson = createPostHogWidgetNode<NotebookNodePersonAttributes>({
nodeType: NotebookNodeType.Person,
title: async (attributes) => {
const theMountedPersonLogic = personLogic.findMounted({ id: attributes.id })
let person = theMountedPersonLogic?.values.person || null

if (person === null) {
const response = await api.persons.list({ distinct_id: attributes.id })
person = response.results[0]
}

return person ? `Person: ${asDisplay(person)}` : 'Person'
},
defaultTitle: 'Person',
Component,
heightEstimate: 300,
minHeight: 100,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ type NotebookNodePlaylistAttributes = {

export const NotebookNodePlaylist = createPostHogWidgetNode<NotebookNodePlaylistAttributes>({
nodeType: NotebookNodeType.RecordingPlaylist,
title: 'Session replays',
defaultTitle: 'Session replays',
Component,
heightEstimate: 'calc(100vh - 20rem)',
href: (attrs) => {
Expand Down
53 changes: 28 additions & 25 deletions frontend/src/scenes/notebooks/Nodes/NotebookNodeQuery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import { DataTableNode, InsightVizNode, NodeKind, QuerySchema } from '~/queries/
import { createPostHogWidgetNode } from 'scenes/notebooks/Nodes/NodeWrapper'
import { InsightLogicProps, InsightShortId, NotebookNodeType } from '~/types'
import { useMountedLogic, useValues } from 'kea'
import { useMemo } from 'react'
import { useEffect, useMemo } from 'react'
import { notebookNodeLogic } from './notebookNodeLogic'
import { NotebookNodeViewProps, NotebookNodeAttributeProperties } from '../Notebook/utils'
import { containsHogQLQuery, isHogQLQuery, isNodeWithSource } from '~/queries/utils'
import { LemonButton } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { urls } from 'scenes/urls'
import api from 'lib/api'

import './NotebookNodeQuery.scss'
import { insightDataLogic } from 'scenes/insights/insightDataLogic'
import { insightLogic } from 'scenes/insights/insightLogic'

const DEFAULT_QUERY: QuerySchema = {
kind: NodeKind.DataTableNode,
Expand All @@ -31,6 +31,31 @@ const Component = (props: NotebookNodeViewProps<NotebookNodeQueryAttributes>): J
const nodeLogic = useMountedLogic(notebookNodeLogic)
const { expanded } = useValues(nodeLogic)

useEffect(() => {
let title = 'Query'

if (query.kind === NodeKind.DataTableNode) {
if (query.source.kind) {
title = query.source.kind.replace('Node', '').replace('Query', '')
} else {
title = 'Data exploration'
}
}
if (query.kind === NodeKind.InsightVizNode) {
if (query.source.kind) {
title = query.source.kind.replace('Node', '').replace('Query', '')
} else {
title = 'Insight'
}
}
if (query.kind === NodeKind.SavedInsightNode) {
const logic = insightLogic.findMounted({ dashboardItemId: query.shortId })
title = (logic?.values.insight.name || logic?.values.insight.derived_name) ?? 'Saved Insight'
}

props.updateAttributes({ title: title })
}, [query])

const modifiedQuery = useMemo(() => {
const modifiedQuery = { ...query, full: false }

Expand Down Expand Up @@ -158,29 +183,7 @@ export const Settings = ({

export const NotebookNodeQuery = createPostHogWidgetNode<NotebookNodeQueryAttributes>({
nodeType: NotebookNodeType.Query,
title: async (attributes) => {
const query = attributes.query
let title = 'HogQL'
if (NodeKind.SavedInsightNode === query.kind) {
const response = await api.insights.loadInsight(query.shortId)
title = response.results[0].name?.length
? response.results[0].name
: response.results[0].derived_name || 'Saved insight'
} else if (NodeKind.DataTableNode === query.kind) {
if (query.source.kind) {
title = query.source.kind.replace('Node', '').replace('Query', '')
} else {
title = 'Data exploration'
}
} else if (NodeKind.InsightVizNode === query.kind) {
if (query.source.kind) {
title = query.source.kind.replace('Node', '').replace('Query', '')
} else {
title = 'Insight'
}
}
return Promise.resolve(title)
},
defaultTitle: 'Query',
Component,
heightEstimate: 500,
minHeight: 200,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ type NotebookNodeRecordingAttributes = {

export const NotebookNodeRecording = createPostHogWidgetNode<NotebookNodeRecordingAttributes>({
nodeType: NotebookNodeType.Recording,
title: 'Session replay',
defaultTitle: 'Session replay',
Component,
heightEstimate: HEIGHT,
minHeight: MIN_HEIGHT,
Expand Down
18 changes: 6 additions & 12 deletions frontend/src/scenes/notebooks/Nodes/NotebookNodeSurvey.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@ import { StatusTag } from 'scenes/surveys/Surveys'
import { SurveyResult } from 'scenes/surveys/SurveyView'
import { SurveyAppearance } from 'scenes/surveys/SurveyAppearance'
import { SurveyReleaseSummary } from 'scenes/surveys/Survey'
import api from 'lib/api'
import { useEffect } from 'react'

const Component = (props: NotebookNodeViewProps<NotebookNodeSurveyAttributes>): JSX.Element => {
const { id } = props.attributes
const { survey, surveyLoading, hasTargetingFlag } = useValues(surveyLogic({ id }))
const { expanded, nextNode } = useValues(notebookNodeLogic)
const { insertAfter } = useActions(notebookNodeLogic)

useEffect(() => {
props.updateAttributes({ title: survey.name ? `Survey: ${survey.name}` : 'Survey' })
benjackwhite marked this conversation as resolved.
Show resolved Hide resolved
}, [survey.name])

return (
<div>
<BindLogic logic={surveyLogic} props={{ id }}>
Expand Down Expand Up @@ -115,17 +119,7 @@ type NotebookNodeSurveyAttributes = {

export const NotebookNodeSurvey = createPostHogWidgetNode<NotebookNodeSurveyAttributes>({
nodeType: NotebookNodeType.Survey,
title: async (attributes) => {
const mountedLogic = surveyLogic.findMounted({ id: attributes.id })
let title = mountedLogic?.values.survey.name || null
if (title === null) {
const retrievedSurvey: Survey = await api.surveys.get(attributes.id)
if (retrievedSurvey) {
title = retrievedSurvey.name
}
}
return title ? `Survey: ${title}` : 'Survey'
},
defaultTitle: 'Survey',
Component,
heightEstimate: '3rem',
href: (attrs) => urls.survey(attrs.id),
Expand Down
Loading
Loading