Skip to content

Commit

Permalink
feat: Recompute notebook title (#17526)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjackwhite authored Sep 19, 2023
1 parent e6b9703 commit b653af8
Show file tree
Hide file tree
Showing 14 changed files with 82 additions and 124 deletions.
11 changes: 7 additions & 4 deletions frontend/src/scenes/notebooks/Nodes/NodeWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
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,8 @@ 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 = attributes.title ? 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
2 changes: 1 addition & 1 deletion frontend/src/scenes/notebooks/Nodes/NotebookNodeImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ type NotebookNodeImageAttributes = {

export const NotebookNodeImage = createPostHogWidgetNode<NotebookNodeImageAttributes>({
nodeType: NotebookNodeType.Image,
title: 'Image',
defaultTitle: 'Image',
Component,
serializedText: (attrs) => {
// TODO file is null when this runs... should it be?
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 @@ -159,29 +184,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' })
}, [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

0 comments on commit b653af8

Please sign in to comment.