From 3fdef43e0b415e6c96cbe63abf19576a00023319 Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 13 Oct 2023 13:23:57 +0200 Subject: [PATCH] feat: Add basic Group / cohort Notebok nodes (#17962) --- frontend/src/scenes/cohorts/Cohort.tsx | 7 +- frontend/src/scenes/cohorts/CohortEdit.tsx | 17 +- .../CohortFilters/CohortCriteriaGroups.tsx | 3 +- .../CohortCriteriaRowBuilder.tsx | 3 +- .../scenes/cohorts/cohortEditLogic.test.ts | 3 +- .../src/scenes/cohorts/cohortEditLogic.ts | 5 +- .../{cohortLogic.ts => cohortSceneLogic.ts} | 10 +- frontend/src/scenes/groups/Group.tsx | 37 +++- frontend/src/scenes/groups/groupLogic.ts | 101 ++++------ .../notebooks/Nodes/NotebookNodeCohort.tsx | 181 ++++++++++++++++++ .../notebooks/Nodes/NotebookNodeGroup.tsx | 113 +++++++++++ .../src/scenes/notebooks/Notebook/Editor.tsx | 4 + .../src/scenes/notebooks/Notebook/utils.ts | 2 + .../NotebooksTable/ContainsTypeFilter.tsx | 2 + frontend/src/types.ts | 2 + 15 files changed, 399 insertions(+), 91 deletions(-) rename frontend/src/scenes/cohorts/{cohortLogic.ts => cohortSceneLogic.ts} (78%) create mode 100644 frontend/src/scenes/notebooks/Nodes/NotebookNodeCohort.tsx create mode 100644 frontend/src/scenes/notebooks/Nodes/NotebookNodeGroup.tsx diff --git a/frontend/src/scenes/cohorts/Cohort.tsx b/frontend/src/scenes/cohorts/Cohort.tsx index 72481de9acefe..d597f2a93964a 100644 --- a/frontend/src/scenes/cohorts/Cohort.tsx +++ b/frontend/src/scenes/cohorts/Cohort.tsx @@ -1,12 +1,13 @@ -import { cohortLogic, CohortLogicProps } from './cohortLogic' +import { cohortSceneLogic } from './cohortSceneLogic' import 'antd/lib/dropdown/style/index.css' import { SceneExport } from 'scenes/sceneTypes' import { CohortEdit } from 'scenes/cohorts/CohortEdit' +import { CohortLogicProps } from './cohortEditLogic' export const scene: SceneExport = { component: Cohort, - logic: cohortLogic, - paramsToProps: ({ params: { id } }): (typeof cohortLogic)['props'] => ({ + logic: cohortSceneLogic, + paramsToProps: ({ params: { id } }): (typeof cohortSceneLogic)['props'] => ({ id: id && id !== 'new' ? parseInt(id) : 'new', }), } diff --git a/frontend/src/scenes/cohorts/CohortEdit.tsx b/frontend/src/scenes/cohorts/CohortEdit.tsx index 878104df2349d..ed5f3767cbfd2 100644 --- a/frontend/src/scenes/cohorts/CohortEdit.tsx +++ b/frontend/src/scenes/cohorts/CohortEdit.tsx @@ -1,7 +1,6 @@ -import { cohortEditLogic } from 'scenes/cohorts/cohortEditLogic' +import { CohortLogicProps, cohortEditLogic } from 'scenes/cohorts/cohortEditLogic' import { useActions, useValues } from 'kea' import { userLogic } from 'scenes/userLogic' -import { CohortLogicProps } from 'scenes/cohorts/cohortLogic' import { PageHeader } from 'lib/components/PageHeader' import { LemonButton } from 'lib/lemon-ui/LemonButton' import { router } from 'kea-router' @@ -12,7 +11,7 @@ import { LemonInput } from 'lib/lemon-ui/LemonInput/LemonInput' import { LemonSelect } from 'lib/lemon-ui/LemonSelect' import { COHORT_TYPE_OPTIONS } from 'scenes/cohorts/CohortFilters/constants' import { CohortTypeEnum } from 'lib/constants' -import { AvailableFeature } from '~/types' +import { AvailableFeature, NotebookNodeType } from '~/types' import { LemonTextArea } from 'lib/lemon-ui/LemonTextArea/LemonTextArea' import Dragger from 'antd/lib/upload/Dragger' import { UploadFile } from 'antd/es/upload/interface' @@ -27,6 +26,7 @@ import { pluralize } from 'lib/utils' import { LemonDivider } from '@posthog/lemon-ui' import { AndOrFilterSelect } from '~/queries/nodes/InsightViz/PropertyGroupFilters/AndOrFilterSelect' import { More } from 'lib/lemon-ui/LemonButton/More' +import { NotebookSelectButton } from 'scenes/notebooks/NotebookSelectButton/NotebookSelectButton' export function CohortEdit({ id }: CohortLogicProps): JSX.Element { const logicProps = { id } @@ -105,6 +105,17 @@ export function CohortEdit({ id }: CohortLogicProps): JSX.Element { /> )} + {!isNewCohort && ( + + )} { let logic: ReturnType diff --git a/frontend/src/scenes/cohorts/cohortEditLogic.ts b/frontend/src/scenes/cohorts/cohortEditLogic.ts index 6e30a5e34b19d..0b53d5ce25e6a 100644 --- a/frontend/src/scenes/cohorts/cohortEditLogic.ts +++ b/frontend/src/scenes/cohorts/cohortEditLogic.ts @@ -27,12 +27,15 @@ import { } from 'scenes/cohorts/cohortUtils' import { NEW_COHORT, NEW_CRITERIA, NEW_CRITERIA_GROUP } from 'scenes/cohorts/CohortFilters/constants' import type { cohortEditLogicType } from './cohortEditLogicType' -import { CohortLogicProps } from 'scenes/cohorts/cohortLogic' import { processCohort } from 'lib/utils' import { DataTableNode, Node, NodeKind } from '~/queries/schema' import { isDataTableNode } from '~/queries/utils' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' +export type CohortLogicProps = { + id?: CohortType['id'] +} + export const cohortEditLogic = kea([ props({} as CohortLogicProps), key((props) => props.id || 'new'), diff --git a/frontend/src/scenes/cohorts/cohortLogic.ts b/frontend/src/scenes/cohorts/cohortSceneLogic.ts similarity index 78% rename from frontend/src/scenes/cohorts/cohortLogic.ts rename to frontend/src/scenes/cohorts/cohortSceneLogic.ts index af1aa108c7d8d..52ea69b500b72 100644 --- a/frontend/src/scenes/cohorts/cohortLogic.ts +++ b/frontend/src/scenes/cohorts/cohortSceneLogic.ts @@ -1,14 +1,12 @@ import { kea, key, path, props, selectors } from 'kea' -import type { cohortLogicType } from './cohortLogicType' -import { Breadcrumb, CohortType } from '~/types' +import { Breadcrumb } from '~/types' import { urls } from 'scenes/urls' import { cohortsModel } from '~/models/cohortsModel' +import { CohortLogicProps } from './cohortEditLogic' -export interface CohortLogicProps { - id?: CohortType['id'] -} +import type { cohortSceneLogicType } from './cohortSceneLogicType' -export const cohortLogic = kea([ +export const cohortSceneLogic = kea([ props({} as CohortLogicProps), key((props) => props.id || 'new'), path(['scenes', 'cohorts', 'cohortLogic']), diff --git a/frontend/src/scenes/groups/Group.tsx b/frontend/src/scenes/groups/Group.tsx index ed84536cf0953..f3ed2116c2aed 100644 --- a/frontend/src/scenes/groups/Group.tsx +++ b/frontend/src/scenes/groups/Group.tsx @@ -1,11 +1,11 @@ import { useActions, useValues } from 'kea' import { PropertiesTable } from 'lib/components/PropertiesTable' import { TZLabel } from 'lib/components/TZLabel' -import { groupLogic } from 'scenes/groups/groupLogic' +import { GroupLogicProps, groupLogic } from 'scenes/groups/groupLogic' import { RelatedGroups } from 'scenes/groups/RelatedGroups' import { SceneExport } from 'scenes/sceneTypes' import { groupDisplayId } from 'scenes/persons/GroupActorDisplay' -import { Group as IGroup, PersonsTabType, PropertyDefinitionType } from '~/types' +import { Group as IGroup, NotebookNodeType, PersonsTabType, PropertyDefinitionType } from '~/types' import { PageHeader } from 'lib/components/PageHeader' import { CopyToClipboardInline } from 'lib/components/CopyToClipboard' import { Spinner, SpinnerOverlay } from 'lib/lemon-ui/Spinner/Spinner' @@ -14,13 +14,24 @@ import { RelatedFeatureFlags } from 'scenes/persons/RelatedFeatureFlags' import { Query } from '~/queries/Query/Query' import { LemonTabs } from 'lib/lemon-ui/LemonTabs' import { GroupDashboard } from 'scenes/groups/GroupDashboard' +import { router } from 'kea-router' +import { urls } from 'scenes/urls' +import { NotebookSelectButton } from 'scenes/notebooks/NotebookSelectButton/NotebookSelectButton' +interface GroupSceneProps { + groupTypeIndex?: string + groupKey?: string +} export const scene: SceneExport = { component: Group, logic: groupLogic, + paramsToProps: ({ params: { groupTypeIndex, groupKey } }: { params: GroupSceneProps }): GroupLogicProps => ({ + groupTypeIndex: parseInt(groupTypeIndex ?? '0'), + groupKey: decodeURIComponent(groupKey ?? ''), + }), } -function GroupCaption({ groupData, groupTypeName }: { groupData: IGroup; groupTypeName: string }): JSX.Element { +export function GroupCaption({ groupData, groupTypeName }: { groupData: IGroup; groupTypeName: string }): JSX.Element { return (
@@ -46,17 +57,17 @@ function GroupCaption({ groupData, groupTypeName }: { groupData: IGroup; groupTy export function Group(): JSX.Element { const { + logicProps, groupData, groupDataLoading, groupTypeName, - groupKey, - groupTypeIndex, groupType, groupTab, groupEventsQuery, showCustomerSuccessDashboards, } = useValues(groupLogic) - const { setGroupTab, setGroupEventsQuery } = useActions(groupLogic) + const { groupKey, groupTypeIndex } = logicProps + const { setGroupEventsQuery } = useActions(groupLogic) if (!groupData) { return groupDataLoading ? : @@ -67,10 +78,22 @@ export function Group(): JSX.Element { } + buttons={ + + } /> setGroupTab(tab)} + onChange={(tab) => router.actions.push(urls.group(String(groupTypeIndex), groupKey, true, tab))} tabs={[ { key: PersonsTabType.PROPERTIES, diff --git a/frontend/src/scenes/groups/groupLogic.ts b/frontend/src/scenes/groups/groupLogic.ts index 42fe4904c4867..0800a07890f14 100644 --- a/frontend/src/scenes/groups/groupLogic.ts +++ b/frontend/src/scenes/groups/groupLogic.ts @@ -1,4 +1,4 @@ -import { kea } from 'kea' +import { actions, afterMount, connect, kea, key, path, props, reducers, selectors } from 'kea' import api from 'lib/api' import { toParams } from 'lib/utils' import { teamLogic } from 'scenes/teamLogic' @@ -13,6 +13,8 @@ import { defaultDataTableColumns } from '~/queries/nodes/DataTable/utils' import { isDataTableNode } from '~/queries/utils' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { FEATURE_FLAGS } from 'lib/constants' +import { loaders } from 'kea-loaders' +import { urlToAction } from 'kea-router' function getGroupEventsQuery(groupTypeIndex: number, groupKey: string): DataTableNode { return { @@ -34,9 +36,16 @@ function getGroupEventsQuery(groupTypeIndex: number, groupKey: string): DataTabl } } -export const groupLogic = kea({ - path: ['groups', 'groupLogic'], - connect: { +export type GroupLogicProps = { + groupTypeIndex: number + groupKey: string +} + +export const groupLogic = kea([ + props({} as GroupLogicProps), + key((props) => `${props.groupTypeIndex}-${props.groupKey}`), + path((key) => ['scenes', 'groups', 'groupLogic', key]), + connect({ values: [ teamLogic, ['currentTeamId'], @@ -45,71 +54,54 @@ export const groupLogic = kea({ featureFlagLogic, ['featureFlags'], ], - }, - actions: () => ({ - setGroup: (groupTypeIndex: number, groupKey: string, groupTab?: string | null) => ({ - groupTypeIndex, - groupKey, - groupTab, - }), + }), + actions(() => ({ setGroupTab: (groupTab: string | null) => ({ groupTab }), setGroupEventsQuery: (query: Node) => ({ query }), - }), - loaders: ({ values }) => ({ + })), + loaders(({ values, props }) => ({ groupData: [ null as Group | null, { loadGroup: async () => { - const params = { group_type_index: values.groupTypeIndex, group_key: values.groupKey } + const params = { group_type_index: props.groupTypeIndex, group_key: props.groupKey } const url = `api/projects/${values.currentTeamId}/groups/find?${toParams(params)}` return await api.get(url) }, }, ], - }), - reducers: { - groupTypeIndex: [ - 0, - { - setGroup: (_, { groupTypeIndex }) => groupTypeIndex, - }, - ], - groupKey: [ - '', - { - setGroup: (_, { groupKey }) => groupKey, - }, - ], + })), + reducers({ groupTab: [ null as string | null, { - setGroup: (_, { groupTab }) => groupTab ?? null, setGroupTab: (_, { groupTab }) => groupTab, }, ], groupEventsQuery: [ null as DataTableNode | null, { - setGroup: (_, { groupTypeIndex, groupKey }) => getGroupEventsQuery(groupTypeIndex, groupKey), setGroupEventsQuery: (_, { query }) => (isDataTableNode(query) ? query : null), }, ], - }, - selectors: { + }), + selectors({ + logicProps: [() => [(_, props) => props], (props): GroupLogicProps => props], + showCustomerSuccessDashboards: [ (s) => [s.featureFlags], (featureFlags) => featureFlags[FEATURE_FLAGS.CS_DASHBOARDS], ], groupTypeName: [ - (s) => [s.aggregationLabel, s.groupTypeIndex], + (s, p) => [s.aggregationLabel, p.groupTypeIndex], (aggregationLabel, index): string => aggregationLabel(index).singular, ], groupType: [ - (s) => [s.groupTypes, s.groupTypeIndex], + (s, p) => [s.groupTypes, p.groupTypeIndex], (groupTypes, index): string => groupTypes[index]?.group_type, ], breadcrumbs: [ - (s) => [s.groupTypeName, s.groupTypeIndex, s.groupKey, s.groupData], + (s, p) => [s.groupTypeName, p.groupTypeIndex, p.groupKey, s.groupData], (groupTypeName, groupTypeIndex, groupKey, groupData): Breadcrumb[] => [ { name: capitalizeFirstLetter(groupTypeName), @@ -121,36 +113,15 @@ export const groupLogic = kea({ }, ], ], - }, - actionToUrl: ({ values }) => ({ - setGroup: () => { - const { groupTypeIndex, groupKey, groupTab } = values - return urls.group(String(groupTypeIndex), groupKey, true, groupTab) - }, - setGroupTab: () => { - const { groupTypeIndex, groupKey, groupTab } = values - return urls.group(String(groupTypeIndex), groupKey, true, groupTab) - }, - }), - urlToAction: ({ actions, values }) => ({ - '/groups/:groupTypeIndex/:groupKey(/:groupTab)': ({ groupTypeIndex, groupKey, groupTab }) => { - if (groupTypeIndex && groupKey) { - if (+groupTypeIndex === values.groupTypeIndex && groupKey === values.groupKey) { - actions.setGroupTab(groupTab || null) - } else { - actions.setGroup(+groupTypeIndex, decodeURIComponent(groupKey), groupTab) - } - } - }, }), - listeners: ({ actions, selectors, values }) => ({ - setGroup: (_, __, ___, previousState) => { - if ( - selectors.groupTypeIndex(previousState) !== values.groupTypeIndex || - selectors.groupKey(previousState) !== values.groupKey - ) { - actions.loadGroup() - } + urlToAction(({ actions }) => ({ + '/groups/:groupTypeIndex/:groupKey(/:groupTab)': ({ groupTab }) => { + actions.setGroupTab(groupTab || null) }, + })), + + afterMount(({ actions, props }) => { + actions.loadGroup() + actions.setGroupEventsQuery(getGroupEventsQuery(props.groupTypeIndex, props.groupKey)) }), -}) +]) diff --git a/frontend/src/scenes/notebooks/Nodes/NotebookNodeCohort.tsx b/frontend/src/scenes/notebooks/Nodes/NotebookNodeCohort.tsx new file mode 100644 index 0000000000000..82f7800f6ccf9 --- /dev/null +++ b/frontend/src/scenes/notebooks/Nodes/NotebookNodeCohort.tsx @@ -0,0 +1,181 @@ +import { createPostHogWidgetNode } from 'scenes/notebooks/Nodes/NodeWrapper' +import { NotebookNodeType, PropertyFilterType } from '~/types' +import { useActions, useValues } from 'kea' +import { urls } from 'scenes/urls' +import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton' +import { notebookNodeLogic } from './notebookNodeLogic' +import { NotebookNodeProps } from '../Notebook/utils' +import { useEffect, useMemo } from 'react' +import clsx from 'clsx' +import { NotFound } from 'lib/components/NotFound' +import { cohortEditLogic } from 'scenes/cohorts/cohortEditLogic' +import { IconCohort, IconPerson, InsightsTrendsIcon } from 'lib/lemon-ui/icons' +import { Query } from '~/queries/Query/Query' +import { LemonDivider, LemonTag } from '@posthog/lemon-ui' +import { DataTableNode, NodeKind } from '~/queries/schema' + +const Component = ({ attributes, updateAttributes }: NotebookNodeProps): JSX.Element => { + const { id } = attributes + + const { expanded } = useValues(notebookNodeLogic) + const { setExpanded, setActions, insertAfter } = useActions(notebookNodeLogic) + + const { cohort, cohortLoading, cohortMissing, query } = useValues(cohortEditLogic({ id })) + const { setQuery } = useActions(cohortEditLogic({ id })) + + const title = cohort ? `Cohort: ${cohort.name}` : 'Cohort' + + const modifiedQuery = useMemo(() => { + return { + ...query, + embedded: true, + // TODO: Add back in controls in a way that actually works - maybe sync with NotebookNodeQuery + full: false, + showElapsedTime: false, + showTimings: false, + showOpenEditorButton: false, + } + }, [query]) + + useEffect(() => { + updateAttributes({ + title, + }) + setActions( + !cohortMissing + ? [ + { + text: 'People in cohort', + icon: , + onClick: () => { + setExpanded(false) + insertAfter({ + type: NotebookNodeType.Query, + attrs: { + title: `People in cohort ${cohort.name}`, + query: { + kind: NodeKind.DataTableNode, + source: { + kind: NodeKind.PersonsQuery, + properties: [ + { + type: PropertyFilterType.Cohort, + key: 'id', + value: id, + }, + ], + }, + full: true, + }, + }, + }) + }, + }, + + { + text: 'Cohort trends', + icon: , + onClick: () => { + setExpanded(false) + insertAfter({ + type: NotebookNodeType.Query, + attrs: { + title: `Pageviews for cohort '${cohort.name}'`, + + query: { + kind: 'InsightVizNode', + source: { + kind: 'TrendsQuery', + filterTestAccounts: true, + series: [ + { + kind: 'EventsNode', + event: '$pageview', + name: '$pageview', + math: 'total', + }, + ], + interval: 'day', + trendsFilter: { + display: 'ActionsLineGraph', + }, + properties: { + type: 'AND', + values: [ + { + type: 'AND', + values: [ + { + key: 'id', + value: id, + type: 'cohort', + }, + ], + }, + ], + }, + }, + }, + }, + }) + }, + }, + ] + : [] + ) + }, [cohort, cohortMissing]) + + if (cohortMissing) { + return + } + return ( +
+
+ {cohortLoading ? ( + + ) : ( +
+ + {cohort.name} + ({cohort.count} persons) + {cohort.is_static ? 'Static' : 'Dynamic'} +
+ )} +
+ + {expanded ? ( + <> + + + + ) : null} +
+ ) +} + +type NotebookNodeCohortAttributes = { + id: number +} + +export const NotebookNodeCohort = createPostHogWidgetNode({ + nodeType: NotebookNodeType.Cohort, + defaultTitle: 'Cohort', + Component, + heightEstimate: 300, + minHeight: 100, + href: (attrs) => urls.cohort(attrs.id), + attributes: { + id: {}, + }, + pasteOptions: { + find: urls.cohort('(.+)'), + getAttributes: async (match) => { + return { id: parseInt(match[1]) } + }, + }, + serializedText: (attrs) => { + const title = attrs?.title || '' + const id = attrs?.id || '' + return `${title} ${id}`.trim() + }, +}) diff --git a/frontend/src/scenes/notebooks/Nodes/NotebookNodeGroup.tsx b/frontend/src/scenes/notebooks/Nodes/NotebookNodeGroup.tsx new file mode 100644 index 0000000000000..d2278f9b714e3 --- /dev/null +++ b/frontend/src/scenes/notebooks/Nodes/NotebookNodeGroup.tsx @@ -0,0 +1,113 @@ +import { createPostHogWidgetNode } from 'scenes/notebooks/Nodes/NodeWrapper' +import { NotebookNodeType, PropertyFilterType, PropertyOperator } from '~/types' +import { useActions, useValues } from 'kea' +import { urls } from 'scenes/urls' +import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton' +import { notebookNodeLogic } from './notebookNodeLogic' +import { NotebookNodeProps } from '../Notebook/utils' +import { useEffect } from 'react' +import clsx from 'clsx' +import { NotFound } from 'lib/components/NotFound' +import { groupLogic } from 'scenes/groups/groupLogic' +import { groupDisplayId } from 'scenes/persons/GroupActorDisplay' +import { GroupCaption } from 'scenes/groups/Group' +import { NodeKind } from '~/queries/schema' +import { defaultDataTableColumns } from '~/queries/nodes/DataTable/utils' + +const Component = ({ attributes, updateAttributes }: NotebookNodeProps): JSX.Element => { + const { id, groupTypeIndex } = attributes + + const logic = groupLogic({ groupKey: id, groupTypeIndex: groupTypeIndex }) + const { groupData, groupDataLoading, groupTypeName } = useValues(logic) + const { setActions, insertAfter } = useActions(notebookNodeLogic) + + const groupDisplay = groupData ? groupDisplayId(groupData.group_key, groupData.group_properties) : 'Group' + const title = groupData ? `${groupTypeName}: ${groupDisplay}` : 'Group' + + useEffect(() => { + updateAttributes({ + title, + }) + setActions([ + { + text: 'Events for this group', + onClick: () => { + insertAfter({ + type: NotebookNodeType.Query, + attrs: { + title: `Events for ${title}`, + query: { + kind: NodeKind.DataTableNode, + full: true, + source: { + kind: NodeKind.EventsQuery, + select: defaultDataTableColumns(NodeKind.EventsQuery), + after: '-24h', + properties: [ + { + key: `$group_${groupTypeIndex}`, + value: id, + type: PropertyFilterType.Event, + operator: PropertyOperator.Exact, + }, + ], + }, + }, + }, + }) + }, + }, + ]) + }, [groupData]) + + if (!groupData && !groupDataLoading) { + return + } + + return ( +
+
+ {groupDataLoading ? ( + + ) : groupData ? ( + <> +
{groupDisplay}
+ + + ) : null} +
+
+ ) +} + +type NotebookNodeGroupAttributes = { + id: string + groupTypeIndex: number +} + +export const NotebookNodeGroup = createPostHogWidgetNode({ + nodeType: NotebookNodeType.Group, + defaultTitle: 'Group', + Component, + heightEstimate: 300, + minHeight: 100, + href: (attrs) => urls.group(attrs.groupTypeIndex, attrs.id), + resizeable: false, + expandable: false, + attributes: { + id: {}, + groupTypeIndex: {}, + }, + pasteOptions: { + find: urls.groups('(.+)'), + getAttributes: async (match) => { + const [groupTypeIndex, id] = match[1].split('/') + return { id: decodeURIComponent(id), groupTypeIndex: parseInt(groupTypeIndex) } + }, + }, + serializedText: (attrs) => { + const title = attrs?.title || '' + const id = attrs?.id || '' + return `${title} ${id}`.trim() + }, +}) diff --git a/frontend/src/scenes/notebooks/Notebook/Editor.tsx b/frontend/src/scenes/notebooks/Notebook/Editor.tsx index c05cf3c31cb42..39c6c29115958 100644 --- a/frontend/src/scenes/notebooks/Notebook/Editor.tsx +++ b/frontend/src/scenes/notebooks/Notebook/Editor.tsx @@ -34,6 +34,8 @@ import { InlineMenu } from './InlineMenu' import NodeGapInsertionExtension from './Extensions/NodeGapInsertion' import { notebookLogic } from './notebookLogic' import { sampleOne } from 'lib/utils' +import { NotebookNodeGroup } from '../Nodes/NotebookNodeGroup' +import { NotebookNodeCohort } from '../Nodes/NotebookNodeCohort' const CustomDocument = ExtensionDocument.extend({ content: 'heading block*', @@ -99,6 +101,8 @@ export function Editor(): JSX.Element { NotebookNodeReplayTimestamp, NotebookNodePlaylist, NotebookNodePerson, + NotebookNodeCohort, + NotebookNodeGroup, NotebookNodeFlagCodeExample, NotebookNodeFlag, NotebookNodeExperiment, diff --git a/frontend/src/scenes/notebooks/Notebook/utils.ts b/frontend/src/scenes/notebooks/Notebook/utils.ts index 6fb341763b37b..377b216e00b25 100644 --- a/frontend/src/scenes/notebooks/Notebook/utils.ts +++ b/frontend/src/scenes/notebooks/Notebook/utils.ts @@ -117,6 +117,8 @@ export const textContent = (node: any): string => { 'ph-recording-playlist': customOrTitleSerializer, 'ph-replay-timestamp': customOrTitleSerializer, 'ph-survey': customOrTitleSerializer, + 'ph-group': customOrTitleSerializer, + 'ph-cohort': customOrTitleSerializer, } return getText(node, { diff --git a/frontend/src/scenes/notebooks/NotebooksTable/ContainsTypeFilter.tsx b/frontend/src/scenes/notebooks/NotebooksTable/ContainsTypeFilter.tsx index ac8f58010de68..5437da52bc4c1 100644 --- a/frontend/src/scenes/notebooks/NotebooksTable/ContainsTypeFilter.tsx +++ b/frontend/src/scenes/notebooks/NotebooksTable/ContainsTypeFilter.tsx @@ -15,6 +15,8 @@ export const fromNodeTypeToLabel: Omit, Noteboo [NotebookNodeType.Recording]: 'Session recordings', [NotebookNodeType.RecordingPlaylist]: 'Session replay playlists', [NotebookNodeType.ReplayTimestamp]: 'Session recording comments', + [NotebookNodeType.Cohort]: 'Cohorts', + [NotebookNodeType.Group]: 'Groups', } export function ContainsTypeFilters({ diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 35170c4eb8e6a..fe343947f81cd 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -3072,6 +3072,8 @@ export enum NotebookNodeType { EarlyAccessFeature = 'ph-early-access-feature', Survey = 'ph-survey', Person = 'ph-person', + Group = 'ph-group', + Cohort = 'ph-cohort', Backlink = 'ph-backlink', ReplayTimestamp = 'ph-replay-timestamp', Image = 'ph-image',