Skip to content

Commit

Permalink
feat: Add basic Group / cohort Notebok nodes (#17962)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjackwhite authored Oct 13, 2023
1 parent 170827a commit 3fdef43
Show file tree
Hide file tree
Showing 15 changed files with 399 additions and 91 deletions.
7 changes: 4 additions & 3 deletions frontend/src/scenes/cohorts/Cohort.tsx
Original file line number Diff line number Diff line change
@@ -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',
}),
}
Expand Down
17 changes: 14 additions & 3 deletions frontend/src/scenes/cohorts/CohortEdit.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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'
Expand All @@ -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 }
Expand Down Expand Up @@ -105,6 +105,17 @@ export function CohortEdit({ id }: CohortLogicProps): JSX.Element {
/>
)}
<LemonDivider vertical />
{!isNewCohort && (
<NotebookSelectButton
type="secondary"
resource={{
type: NotebookNodeType.Cohort,
attrs: {
id,
},
}}
/>
)}
<LemonButton
type="primary"
data-attr="save-cohort"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import { LemonDivider } from 'lib/lemon-ui/LemonDivider'
import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
import { useActions, useValues } from 'kea'
import { CohortCriteriaRowBuilder } from 'scenes/cohorts/CohortFilters/CohortCriteriaRowBuilder'
import { cohortEditLogic } from 'scenes/cohorts/cohortEditLogic'
import { CohortLogicProps } from 'scenes/cohorts/cohortLogic'
import { CohortLogicProps, cohortEditLogic } from 'scenes/cohorts/cohortEditLogic'
import { AndOrFilterSelect } from '~/queries/nodes/InsightViz/PropertyGroupFilters/AndOrFilterSelect'

export function CohortCriteriaGroups(logicProps: CohortLogicProps): JSX.Element {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import { Field as KeaField } from 'kea-forms'
import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
import { useActions } from 'kea'
import { cleanCriteria } from 'scenes/cohorts/cohortUtils'
import { cohortEditLogic } from 'scenes/cohorts/cohortEditLogic'
import { CohortLogicProps } from 'scenes/cohorts/cohortLogic'
import { CohortLogicProps, cohortEditLogic } from 'scenes/cohorts/cohortEditLogic'

export interface CohortCriteriaRowBuilderProps {
id: CohortLogicProps['id']
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/scenes/cohorts/cohortEditLogic.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { initKeaTests } from '~/test/init'
import { CohortLogicProps } from 'scenes/cohorts/cohortLogic'
import { expectLogic, partial } from 'kea-test-utils'
import { useMocks } from '~/mocks/jest'
import { mockCohort } from '~/test/mocks'
Expand All @@ -19,7 +18,7 @@ import {
import { BehavioralFilterKey } from 'scenes/cohorts/CohortFilters/types'
import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
import { CRITERIA_VALIDATIONS, NEW_CRITERIA, ROWS } from 'scenes/cohorts/CohortFilters/constants'
import { cohortEditLogic } from 'scenes/cohorts/cohortEditLogic'
import { CohortLogicProps, cohortEditLogic } from 'scenes/cohorts/cohortEditLogic'

describe('cohortEditLogic', () => {
let logic: ReturnType<typeof cohortEditLogic.build>
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/scenes/cohorts/cohortEditLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<cohortEditLogicType>([
props({} as CohortLogicProps),
key((props) => props.id || 'new'),
Expand Down
Original file line number Diff line number Diff line change
@@ -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<cohortLogicType>([
export const cohortSceneLogic = kea<cohortSceneLogicType>([
props({} as CohortLogicProps),
key((props) => props.id || 'new'),
path(['scenes', 'cohorts', 'cohortLogic']),
Expand Down
37 changes: 30 additions & 7 deletions frontend/src/scenes/groups/Group.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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 (
<div className="flex items-center flex-wrap">
<div className="mr-4">
Expand All @@ -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 ? <SpinnerOverlay sceneLevel /> : <NotFound object="group" />
Expand All @@ -67,10 +78,22 @@ export function Group(): JSX.Element {
<PageHeader
title={groupDisplayId(groupData.group_key, groupData.group_properties)}
caption={<GroupCaption groupData={groupData} groupTypeName={groupTypeName} />}
buttons={
<NotebookSelectButton
type="secondary"
resource={{
type: NotebookNodeType.Group,
attrs: {
id: groupKey,
groupTypeIndex: groupTypeIndex,
},
}}
/>
}
/>
<LemonTabs
activeKey={groupTab ?? PersonsTabType.PROPERTIES}
onChange={(tab) => setGroupTab(tab)}
onChange={(tab) => router.actions.push(urls.group(String(groupTypeIndex), groupKey, true, tab))}
tabs={[
{
key: PersonsTabType.PROPERTIES,
Expand Down
101 changes: 36 additions & 65 deletions frontend/src/scenes/groups/groupLogic.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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 {
Expand All @@ -34,9 +36,16 @@ function getGroupEventsQuery(groupTypeIndex: number, groupKey: string): DataTabl
}
}

export const groupLogic = kea<groupLogicType>({
path: ['groups', 'groupLogic'],
connect: {
export type GroupLogicProps = {
groupTypeIndex: number
groupKey: string
}

export const groupLogic = kea<groupLogicType>([
props({} as GroupLogicProps),
key((props) => `${props.groupTypeIndex}-${props.groupKey}`),
path((key) => ['scenes', 'groups', 'groupLogic', key]),
connect({
values: [
teamLogic,
['currentTeamId'],
Expand All @@ -45,71 +54,54 @@ export const groupLogic = kea<groupLogicType>({
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),
Expand All @@ -121,36 +113,15 @@ export const groupLogic = kea<groupLogicType>({
},
],
],
},
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))
}),
})
])
Loading

0 comments on commit 3fdef43

Please sign in to comment.