From 90694b542c2ddca419b9d30c9017a4d552735baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Obermu=CC=88ller?= Date: Thu, 26 Oct 2023 21:34:54 +0200 Subject: [PATCH] chore(kea): upgrade logics to v3 format with codemod --- .../src/layout/navigation/navigationLogic.ts | 62 ++--- .../ActivityLog/activityLogLogic.tsx | 43 +-- .../addToDashboardModalLogic.ts | 39 ++- .../ChartFilter/chartFilterLogic.ts | 31 +-- .../CommandPalette/commandPaletteLogic.tsx | 144 +++++----- .../CompareFilter/compareFilterLogic.ts | 31 +-- .../definitionPopoverLogic.ts | 63 ++--- .../lib/components/HelpButton/HelpButton.tsx | 34 +-- .../IntervalFilter/intervalFilterLogic.ts | 28 +- .../PersonalAPIKeys/personalAPIKeysLogic.ts | 22 +- .../taxonomicPropertyFilterLogic.ts | 37 ++- .../TaxonomicFilter/infiniteListLogic.ts | 179 ++++++------- .../TaxonomicFilter/taxonomicFilterLogic.tsx | 36 ++- .../visibilitySensorLogic.tsx | 65 +++-- .../lib/introductions/groupsAccessLogic.ts | 16 +- frontend/src/lib/logic/featureFlagLogic.ts | 22 +- frontend/src/lib/utils/eventUsageLogic.ts | 20 +- frontend/src/models/actionsModel.ts | 28 +- frontend/src/models/cohortsModel.ts | 42 ++- frontend/src/models/dashboardsModel.tsx | 54 ++-- frontend/src/models/funnelsModel.ts | 35 +-- frontend/src/models/groupPropertiesModel.ts | 25 +- frontend/src/models/index.ts | 10 +- frontend/src/models/insightsModel.tsx | 18 +- frontend/src/scenes/App.tsx | 26 +- frontend/src/scenes/actions/actionLogic.ts | 58 ++--- .../batch_exports/batchExportLogsLogic.ts | 66 +++-- .../dashboard/dashboardCollaboratorsLogic.ts | 47 ++-- .../DataManagementPageTabs.tsx | 33 +-- .../data-warehouse/DataWarehousePageTabs.tsx | 25 +- .../scenes/feature-flags/featureFlagsLogic.ts | 123 ++++----- frontend/src/scenes/groups/groupsListLogic.ts | 41 +-- .../src/scenes/groups/relatedGroupsLogic.ts | 37 +-- .../views/Histogram/histogramLogic.ts | 16 +- .../views/InsightsTable/insightsTableLogic.ts | 30 ++- .../views/LineGraph/lineGraphLogic.ts | 12 +- .../instance/SystemStatus/staffUsersLogic.ts | 43 +-- .../SystemStatus/systemStatusLogic.ts | 53 ++-- .../src/scenes/onboarding/onboardingLogic.tsx | 40 +-- .../src/scenes/onboarding/sdks/sdksLogic.tsx | 33 ++- .../scenes/organization/Settings/index.tsx | 25 +- .../organization/Settings/inviteLogic.ts | 117 ++++----- .../organization/Settings/invitesLogic.tsx | 21 +- .../organization/Settings/membersLogic.tsx | 38 +-- .../scenes/persons/mergeSplitPersonLogic.ts | 65 ++--- frontend/src/scenes/persons/personsLogic.tsx | 245 +++++++++--------- .../scenes/plugins/plugin/pluginLogsLogic.ts | 63 +++-- .../src/scenes/products/productsLogic.tsx | 16 +- .../Settings/groupAnalyticsConfigLogic.ts | 28 +- .../Settings/webhookIntegrationLogic.ts | 27 +- .../retention/retentionLineGraphLogic.ts | 21 +- .../src/scenes/retention/retentionLogic.ts | 20 +- .../scenes/retention/retentionModalLogic.ts | 32 +-- .../scenes/retention/retentionPeopleLogic.ts | 39 +-- .../scenes/retention/retentionTableLogic.ts | 20 +- .../saved-insights/savedInsightsLogic.ts | 43 +-- frontend/src/scenes/sceneLogic.ts | 98 +++---- frontend/src/scenes/trends/mathsLogic.tsx | 16 +- frontend/src/toolbar/actions/actionsLogic.ts | 36 +-- .../src/toolbar/button/toolbarButtonLogic.ts | 37 ++- .../src/toolbar/elements/elementsLogic.ts | 143 +++++----- .../src/toolbar/flags/featureFlagsLogic.ts | 95 +++---- .../src/toolbar/stats/currentPageLogic.ts | 22 +- 63 files changed, 1499 insertions(+), 1535 deletions(-) diff --git a/frontend/src/layout/navigation/navigationLogic.ts b/frontend/src/layout/navigation/navigationLogic.ts index 6042b54565561..0305de942663d 100644 --- a/frontend/src/layout/navigation/navigationLogic.ts +++ b/frontend/src/layout/navigation/navigationLogic.ts @@ -1,4 +1,6 @@ -import { kea } from 'kea' +import { windowValues } from 'kea-windowvalues' +import { loaders } from 'kea-loaders' +import { kea, path, connect, actions, reducers, selectors, listeners } from 'kea' import api from 'lib/api' import { organizationLogic } from 'scenes/organizationLogic' import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic' @@ -17,13 +19,13 @@ export type ProjectNoticeVariant = | 'unverified_email' | 'is_impersonated' -export const navigationLogic = kea({ - path: ['layout', 'navigation', 'navigationLogic'], - connect: { +export const navigationLogic = kea([ + path(['layout', 'navigation', 'navigationLogic']), + connect({ values: [sceneLogic, ['sceneConfig', 'activeScene'], membersLogic, ['members', 'membersLoading']], actions: [eventUsageLogic, ['reportProjectNoticeDismissed']], - }, - actions: { + }), + actions({ toggleSideBarBase: (override?: boolean) => ({ override }), // Only use the override for testing toggleSideBarMobile: (override?: boolean) => ({ override }), // Only use the override for testing toggleActivationSideBar: true, @@ -43,8 +45,25 @@ export const navigationLogic = kea({ closeAppSourceEditor: true, setOpenAppMenu: (id: number | null) => ({ id }), closeProjectNotice: (projectNoticeVariant: ProjectNoticeVariant) => ({ projectNoticeVariant }), - }, - reducers: { + }), + loaders({ + navigationStatus: [ + { system_status_ok: true, async_migrations_ok: true } as { + system_status_ok: boolean + async_migrations_ok: boolean + }, + { + loadNavigationStatus: async () => { + return await api.get('api/instance_settings') + }, + }, + ], + }), + windowValues(() => ({ + fullscreen: (window) => !!window.document.fullscreenElement, + mobileLayout: (window) => window.innerWidth < 992, // Sync width threshold with Sass variable $lg! + })), + reducers({ // Non-mobile base isSideBarShownBase: [ true, @@ -112,12 +131,8 @@ export const navigationLogic = kea({ closeProjectNotice: (state, { projectNoticeVariant }) => ({ ...state, [projectNoticeVariant]: true }), }, ], - }, - windowValues: () => ({ - fullscreen: (window) => !!window.document.fullscreenElement, - mobileLayout: (window) => window.innerWidth < 992, // Sync width threshold with Sass variable $lg! }), - selectors: { + selectors({ /** `noSidebar` whether the current scene should display a sidebar at all */ noSidebar: [ (s) => [s.fullscreen, s.sceneConfig], @@ -201,21 +216,8 @@ export const navigationLogic = kea({ return null }, ], - }, - loaders: { - navigationStatus: [ - { system_status_ok: true, async_migrations_ok: true } as { - system_status_ok: boolean - async_migrations_ok: boolean - }, - { - loadNavigationStatus: async () => { - return await api.get('api/instance_settings') - }, - }, - ], - }, - listeners: ({ actions, values }) => ({ + }), + listeners(({ actions, values }) => ({ closeProjectNotice: ({ projectNoticeVariant }) => { actions.reportProjectNoticeDismissed(projectNoticeVariant) }, @@ -226,5 +228,5 @@ export const navigationLogic = kea({ actions.showActivationSideBar() } }, - }), -}) + })), +]) diff --git a/frontend/src/lib/components/ActivityLog/activityLogLogic.tsx b/frontend/src/lib/components/ActivityLog/activityLogLogic.tsx index a011dd6f6ad6c..2287859a196f4 100644 --- a/frontend/src/lib/components/ActivityLog/activityLogLogic.tsx +++ b/frontend/src/lib/components/ActivityLog/activityLogLogic.tsx @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, props, key, path, actions, reducers, selectors, listeners, events } from 'kea' import api, { ACTIVITY_PAGE_SIZE, ActivityLogPaginatedResponse } from 'lib/api' import { ActivityLogItem, @@ -11,7 +12,7 @@ import { import type { activityLogLogicType } from './activityLogLogicType' import { PaginationManual } from 'lib/lemon-ui/PaginationControl' import { urls } from 'scenes/urls' -import { router } from 'kea-router' +import { router, urlToAction } from 'kea-router' import { flagActivityDescriber } from 'scenes/feature-flags/activityDescriptions' import { pluginActivityDescriber } from 'scenes/plugins/pluginActivityDescriptions' import { insightActivityDescriber } from 'scenes/saved-insights/activityDescriptions' @@ -51,30 +52,30 @@ export type ActivityLogLogicProps = { id?: number | string } -export const activityLogLogic = kea({ - path: (key) => ['lib', 'components', 'ActivityLog', 'activitylog', 'logic', key], - props: {} as ActivityLogLogicProps, - key: ({ scope, id }) => `activity/${scope}/${id || 'all'}`, - actions: { +export const activityLogLogic = kea([ + props({} as ActivityLogLogicProps), + key(({ scope, id }) => `activity/${scope}/${id || 'all'}`), + path((key) => ['lib', 'components', 'ActivityLog', 'activitylog', 'logic', key]), + actions({ setPage: (page: number) => ({ page }), - }, - loaders: ({ values, props }) => ({ + }), + loaders(({ values, props }) => ({ activity: [ { results: [], total_count: 0 } as ActivityLogPaginatedResponse, { fetchActivity: async () => await api.activity.list(props, values.page), }, ], - }), - reducers: () => ({ + })), + reducers(() => ({ page: [ 1, { setPage: (_, { page }) => page, }, ], - }), - selectors: ({ actions }) => ({ + })), + selectors(({ actions }) => ({ pagination: [ (s) => [s.page, s.totalCount], (page, totalCount): PaginationManual => { @@ -100,14 +101,14 @@ export const activityLogLogic = kea({ return activity.total_count ?? null }, ], - }), - listeners: ({ actions }) => ({ + })), + listeners(({ actions }) => ({ setPage: async (_, breakpoint) => { await breakpoint() actions.fetchActivity() }, - }), - urlToAction: ({ values, actions, props }) => { + })), + urlToAction(({ values, actions, props }) => { const onPageChange = ( searchParams: Record, hashParams: Record, @@ -153,10 +154,10 @@ export const activityLogLogic = kea({ [urls.appHistory(':pluginConfigId')]: (_, searchParams, hashParams) => onPageChange(searchParams, hashParams, ActivityScope.PLUGIN, true), } - }, - events: ({ actions }) => ({ + }), + events(({ actions }) => ({ afterMount: () => { actions.fetchActivity() }, - }), -}) + })), +]) diff --git a/frontend/src/lib/components/AddToDashboard/addToDashboardModalLogic.ts b/frontend/src/lib/components/AddToDashboard/addToDashboardModalLogic.ts index 014f6cb8c8cae..c6e363e9fa415 100644 --- a/frontend/src/lib/components/AddToDashboard/addToDashboardModalLogic.ts +++ b/frontend/src/lib/components/AddToDashboard/addToDashboardModalLogic.ts @@ -1,4 +1,4 @@ -import { kea } from 'kea' +import { kea, props, key, path, connect, actions, reducers, selectors, listeners } from 'kea' import { dashboardsModel } from '~/models/dashboardsModel' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' import { newDashboardLogic } from 'scenes/dashboard/newDashboardLogic' @@ -17,19 +17,19 @@ export interface AddToDashboardModalLogicProps { } // Helping kea-typegen navigate the exported default class for Fuse -// eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface Fuse extends FuseClass {} -export const addToDashboardModalLogic = kea({ - path: ['lib', 'components', 'AddToDashboard', 'saveToDashboardModalLogic'], - props: {} as AddToDashboardModalLogicProps, - key: ({ insight }) => { +export const addToDashboardModalLogic = kea([ + props({} as AddToDashboardModalLogicProps), + key(({ insight }) => { if (!insight.short_id) { throw Error('must provide an insight with a short id') } return insight.short_id - }, - connect: (props: AddToDashboardModalLogicProps) => ({ + }), + path(['lib', 'components', 'AddToDashboard', 'saveToDashboardModalLogic']), + connect((props: AddToDashboardModalLogicProps) => ({ actions: [ insightLogic({ dashboardItemId: props.insight.short_id, cachedInsight: props.insight }), ['updateInsight', 'updateInsightSuccess', 'updateInsightFailure'], @@ -38,8 +38,8 @@ export const addToDashboardModalLogic = kea({ newDashboardLogic, ['showNewDashboardModal'], ], - }), - actions: { + })), + actions({ addNewDashboard: true, setDashboardId: (id: number) => ({ id }), setSearchQuery: (query: string) => ({ query }), @@ -47,9 +47,8 @@ export const addToDashboardModalLogic = kea({ setScrollIndex: (index: number) => ({ index }), addToDashboard: (insight: Partial, dashboardId: number) => ({ insight, dashboardId }), removeFromDashboard: (insight: Partial, dashboardId: number) => ({ insight, dashboardId }), - }, - - reducers: { + }), + reducers({ _dashboardId: [null as null | number, { setDashboardId: (_, { id }) => id }], searchQuery: ['', { setSearchQuery: (_, { query }) => query }], scrollIndex: [-1 as number, { setScrollIndex: (_, { index }) => index }], @@ -62,9 +61,8 @@ export const addToDashboardModalLogic = kea({ updateInsightFailure: () => null, }, ], - }, - - selectors: { + }), + selectors({ dashboardId: [ (s) => [ s._dashboardId, @@ -108,9 +106,8 @@ export const addToDashboardModalLogic = kea({ ...availableDashboards, ], ], - }, - - listeners: ({ actions, values, props }) => ({ + }), + listeners(({ actions, values, props }) => ({ setDashboardId: ({ id }) => { dashboardsModel.actions.setLastDashboardId(id) }, @@ -153,5 +150,5 @@ export const addToDashboardModalLogic = kea({ } ) }, - }), -}) + })), +]) diff --git a/frontend/src/lib/components/ChartFilter/chartFilterLogic.ts b/frontend/src/lib/components/ChartFilter/chartFilterLogic.ts index a94e57dcc76cd..67a756a544362 100644 --- a/frontend/src/lib/components/ChartFilter/chartFilterLogic.ts +++ b/frontend/src/lib/components/ChartFilter/chartFilterLogic.ts @@ -1,27 +1,24 @@ -import { kea } from 'kea' +import { kea, props, key, path, connect, actions, selectors, listeners } from 'kea' import type { chartFilterLogicType } from './chartFilterLogicType' import { ChartDisplayType, InsightLogicProps } from '~/types' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' -export const chartFilterLogic = kea({ - props: {} as InsightLogicProps, - key: keyForInsightLogicProps('new'), - path: (key) => ['lib', 'components', 'ChartFilter', 'chartFilterLogic', key], - connect: (props: InsightLogicProps) => ({ +export const chartFilterLogic = kea([ + props({} as InsightLogicProps), + key(keyForInsightLogicProps('new')), + path((key) => ['lib', 'components', 'ChartFilter', 'chartFilterLogic', key]), + connect((props: InsightLogicProps) => ({ actions: [insightVizDataLogic(props), ['updateInsightFilter', 'updateBreakdown']], values: [insightVizDataLogic(props), ['isTrends', 'isStickiness', 'display', 'series']], - }), - - actions: () => ({ + })), + actions(() => ({ setChartFilter: (chartFilter: ChartDisplayType) => ({ chartFilter }), - }), - - selectors: { + })), + selectors({ chartFilter: [(s) => [s.display], (display): ChartDisplayType | null | undefined => display], - }, - - listeners: ({ actions, values }) => ({ + }), + listeners(({ actions, values }) => ({ setChartFilter: ({ chartFilter }) => { const { isTrends, isStickiness, display, series } = values const newDisplay = chartFilter as ChartDisplayType @@ -42,5 +39,5 @@ export const chartFilterLogic = kea({ } } }, - }), -}) + })), +]) diff --git a/frontend/src/lib/components/CommandPalette/commandPaletteLogic.tsx b/frontend/src/lib/components/CommandPalette/commandPaletteLogic.tsx index f96e27254dcd0..a29dca16342ae 100644 --- a/frontend/src/lib/components/CommandPalette/commandPaletteLogic.tsx +++ b/frontend/src/lib/components/CommandPalette/commandPaletteLogic.tsx @@ -1,4 +1,4 @@ -import { kea } from 'kea' +import { kea, path, connect, actions, reducers, selectors, listeners, events } from 'kea' import { router } from 'kea-router' import type { commandPaletteLogicType } from './commandPaletteLogicType' import Fuse from 'fuse.js' @@ -112,14 +112,14 @@ function resolveCommand(source: Command | CommandFlow, argument?: string, prefix return resultsWithCommand } -export const commandPaletteLogic = kea({ - path: ['lib', 'components', 'CommandPalette', 'commandPaletteLogic'], - connect: { +export const commandPaletteLogic = kea([ + path(['lib', 'components', 'CommandPalette', 'commandPaletteLogic']), + connect({ actions: [personalAPIKeysLogic, ['createKey'], router, ['push']], values: [teamLogic, ['currentTeam'], userLogic, ['user']], logic: [preflightLogic], - }, - actions: { + }), + actions({ hidePalette: true, showPalette: true, togglePalette: true, @@ -135,8 +135,8 @@ export const commandPaletteLogic = kea({ deregisterCommand: (commandKey: string) => ({ commandKey }), setCustomCommand: (commandKey: string) => ({ commandKey }), deregisterScope: (scope: string) => ({ scope }), - }, - reducers: { + }), + reducers({ isPaletteShown: [ false, { @@ -196,67 +196,8 @@ export const commandPaletteLogic = kea({ }, }, ], - }, - - listeners: ({ actions, values }) => ({ - showPalette: () => { - posthog.capture('palette shown', { isMobile: isMobile() }) - }, - togglePalette: () => { - if (values.isPaletteShown) { - posthog.capture('palette shown', { isMobile: isMobile() }) - } - }, - executeResult: ({ result }: { result: CommandResult }) => { - if (result.executor === true) { - actions.activateFlow(null) - actions.hidePalette() - } else { - const possibleFlow = result.executor?.() || null - actions.activateFlow(possibleFlow) - if (!possibleFlow) { - actions.hidePalette() - } - } - // Capture command execution, without useless data - const { icon, index, ...cleanedResult }: Record = result - const { resolver, ...cleanedCommand } = cleanedResult.source - cleanedResult.source = cleanedCommand - cleanedResult.isMobile = isMobile() - posthog.capture('palette command executed', cleanedResult) - }, - deregisterScope: ({ scope }) => { - for (const command of Object.values(values.commandRegistrations)) { - if (command.scope === scope) { - actions.deregisterCommand(command.key) - } - } - }, - setInput: async ({ input }, breakpoint) => { - await breakpoint(300) - if (input.length > 8) { - const response = await api.persons.list({ search: input }) - const person = response.results[0] - if (person) { - actions.registerCommand({ - key: `person-${person.distinct_ids[0]}`, - resolver: [ - { - icon: IconPersonFilled, - display: `View person ${input}`, - executor: () => { - const { push } = router.actions - push(urls.personByDistinctId(person.distinct_ids[0])) - }, - }, - ], - scope: GLOBAL_COMMAND_SCOPE, - }) - } - } - }, }), - selectors: { + selectors({ isSqueak: [ (selectors) => [selectors.input], (input: string) => { @@ -390,9 +331,66 @@ export const commandPaletteLogic = kea({ return resultsGroupedInOrder }, ], - }, - - events: ({ actions, values }) => ({ + }), + listeners(({ actions, values }) => ({ + showPalette: () => { + posthog.capture('palette shown', { isMobile: isMobile() }) + }, + togglePalette: () => { + if (values.isPaletteShown) { + posthog.capture('palette shown', { isMobile: isMobile() }) + } + }, + executeResult: ({ result }: { result: CommandResult }) => { + if (result.executor === true) { + actions.activateFlow(null) + actions.hidePalette() + } else { + const possibleFlow = result.executor?.() || null + actions.activateFlow(possibleFlow) + if (!possibleFlow) { + actions.hidePalette() + } + } + // Capture command execution, without useless data + const { icon, index, ...cleanedResult }: Record = result + const { resolver, ...cleanedCommand } = cleanedResult.source + cleanedResult.source = cleanedCommand + cleanedResult.isMobile = isMobile() + posthog.capture('palette command executed', cleanedResult) + }, + deregisterScope: ({ scope }) => { + for (const command of Object.values(values.commandRegistrations)) { + if (command.scope === scope) { + actions.deregisterCommand(command.key) + } + } + }, + setInput: async ({ input }, breakpoint) => { + await breakpoint(300) + if (input.length > 8) { + const response = await api.persons.list({ search: input }) + const person = response.results[0] + if (person) { + actions.registerCommand({ + key: `person-${person.distinct_ids[0]}`, + resolver: [ + { + icon: IconPersonFilled, + display: `View person ${input}`, + executor: () => { + const { push } = router.actions + push(urls.personByDistinctId(person.distinct_ids[0])) + }, + }, + ], + scope: GLOBAL_COMMAND_SCOPE, + }) + } + } + }, + })), + events(({ actions, values }) => ({ afterMount: () => { const { push } = actions @@ -761,5 +759,5 @@ export const commandPaletteLogic = kea({ actions.deregisterCommand('share-feedback') actions.deregisterCommand('debug-copy-session-recording-url') }, - }), -}) + })), +]) diff --git a/frontend/src/lib/components/CompareFilter/compareFilterLogic.ts b/frontend/src/lib/components/CompareFilter/compareFilterLogic.ts index c3e2d1b65dcbe..899d545987442 100644 --- a/frontend/src/lib/components/CompareFilter/compareFilterLogic.ts +++ b/frontend/src/lib/components/CompareFilter/compareFilterLogic.ts @@ -1,15 +1,15 @@ -import { kea } from 'kea' +import { kea, props, key, path, connect, actions, selectors, listeners } from 'kea' import { ChartDisplayType, InsightLogicProps } from '~/types' import type { compareFilterLogicType } from './compareFilterLogicType' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' import { insightLogic } from 'scenes/insights/insightLogic' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' -export const compareFilterLogic = kea({ - props: {} as InsightLogicProps, - key: keyForInsightLogicProps('new'), - path: (key) => ['lib', 'components', 'CompareFilter', 'compareFilterLogic', key], - connect: (props: InsightLogicProps) => ({ +export const compareFilterLogic = kea([ + props({} as InsightLogicProps), + key(keyForInsightLogicProps('new')), + path((key) => ['lib', 'components', 'CompareFilter', 'compareFilterLogic', key]), + connect((props: InsightLogicProps) => ({ values: [ insightLogic(props), ['canEditInsight'], @@ -17,14 +17,12 @@ export const compareFilterLogic = kea({ ['compare', 'display', 'insightFilter', 'isLifecycle', 'dateRange'], ], actions: [insightVizDataLogic(props), ['updateInsightFilter']], - }), - - actions: () => ({ + })), + actions(() => ({ setCompare: (compare: boolean) => ({ compare }), toggleCompare: true, - }), - - selectors: { + })), + selectors({ disabled: [ (s) => [s.canEditInsight, s.isLifecycle, s.display, s.dateRange], (canEditInsight, isLifecycle, display, dateRange) => @@ -33,14 +31,13 @@ export const compareFilterLogic = kea({ display === ChartDisplayType.WorldMap || dateRange?.date_from === 'all', ], - }, - - listeners: ({ values, actions }) => ({ + }), + listeners(({ values, actions }) => ({ setCompare: ({ compare }) => { actions.updateInsightFilter({ compare }) }, toggleCompare: () => { actions.setCompare(!values.compare) }, - }), -}) + })), +]) diff --git a/frontend/src/lib/components/DefinitionPopover/definitionPopoverLogic.ts b/frontend/src/lib/components/DefinitionPopover/definitionPopoverLogic.ts index 89f78c9702f4a..931d1b00114d6 100644 --- a/frontend/src/lib/components/DefinitionPopover/definitionPopoverLogic.ts +++ b/frontend/src/lib/components/DefinitionPopover/definitionPopoverLogic.ts @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, props, path, connect, actions, reducers, selectors, listeners, events } from 'kea' import type { definitionPopoverLogicType } from './definitionPopoverLogicType' import { TaxonomicDefinitionTypes, TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types' import { capitalizeFirstLetter } from 'lib/utils' @@ -33,35 +34,20 @@ export interface DefinitionPopoverLogicProps { openDetailInNewTab?: boolean } -export const definitionPopoverLogic = kea({ - props: {} as DefinitionPopoverLogicProps, - connect: { +export const definitionPopoverLogic = kea([ + props({} as DefinitionPopoverLogicProps), + path(['lib', 'components', 'DefinitionPanel', 'definitionPopoverLogic']), + connect({ values: [userLogic, ['hasAvailableFeature']], - }, - path: ['lib', 'components', 'DefinitionPanel', 'definitionPopoverLogic'], - actions: { + }), + actions({ setDefinition: (item: Partial) => ({ item }), setLocalDefinition: (item: Partial) => ({ item }), setPopoverState: (state: DefinitionPopoverState) => ({ state }), handleCancel: true, recordHoverActivity: true, - }, - reducers: { - state: [ - DefinitionPopoverState.View as DefinitionPopoverState, - { - setPopoverState: (_, { state }) => state, - }, - ], - localDefinition: [ - {} as Partial, - { - setDefinition: (_, { item }) => item, - setLocalDefinition: (state, { item }) => ({ ...state, ...item } as Partial), - }, - ], - }, - loaders: ({ values, props, cache }) => ({ + }), + loaders(({ values, props, cache }) => ({ definition: [ {} as Partial, { @@ -121,8 +107,23 @@ export const definitionPopoverLogic = kea({ }, }, ], + })), + reducers({ + state: [ + DefinitionPopoverState.View as DefinitionPopoverState, + { + setPopoverState: (_, { state }) => state, + }, + ], + localDefinition: [ + {} as Partial, + { + setDefinition: (_, { item }) => item, + setLocalDefinition: (state, { item }) => ({ ...state, ...item } as Partial), + }, + ], }), - selectors: { + selectors({ type: [() => [(_, props) => props.type], (type) => type], hideView: [() => [(_, props) => props.hideView], (hideView) => hideView ?? false], hideEdit: [() => [(_, props) => props.hideEdit], (hideEdit) => hideEdit ?? false], @@ -198,8 +199,8 @@ export const definitionPopoverLogic = kea({ return undefined }, ], - }, - listeners: ({ actions, selectors, values, props, cache }) => ({ + }), + listeners(({ actions, selectors, values, props, cache }) => ({ setDefinition: (_, __, ___, previousState) => { // Reset definition popover to view mode if context is switched if ( @@ -247,10 +248,10 @@ export const definitionPopoverLogic = kea({ await breakpoint(IS_TEST_MODE ? 1 : 1000) // Tests will wait for all breakpoints to finish eventUsageLogic.findMounted()?.actions?.reportDataManagementDefinitionHovered(values.type) }, - }), - events: ({ actions }) => ({ + })), + events(({ actions }) => ({ afterMount: () => { actions.recordHoverActivity() }, - }), -}) + })), +]) diff --git a/frontend/src/lib/components/HelpButton/HelpButton.tsx b/frontend/src/lib/components/HelpButton/HelpButton.tsx index c9d55ae9e5c81..c831d51f21f09 100644 --- a/frontend/src/lib/components/HelpButton/HelpButton.tsx +++ b/frontend/src/lib/components/HelpButton/HelpButton.tsx @@ -1,5 +1,5 @@ import './HelpButton.scss' -import { kea, useActions, useValues } from 'kea' +import { kea, useActions, useValues, props, key, path, connect, actions, reducers, listeners } from 'kea' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' import { HelpType } from '~/types' import type { helpButtonLogicType } from './HelpButtonType' @@ -27,21 +27,23 @@ import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic' const HELP_UTM_TAGS = '?utm_medium=in-product&utm_campaign=help-button-top' -export const helpButtonLogic = kea({ - props: {} as { - key?: string - }, - key: (props: { key?: string }) => props.key || 'global', - path: (key) => ['lib', 'components', 'HelpButton', key], - connect: { +export const helpButtonLogic = kea([ + props( + {} as { + key?: string + } + ), + key((props: { key?: string }) => props.key || 'global'), + path((key) => ['lib', 'components', 'HelpButton', key]), + connect({ actions: [eventUsageLogic, ['reportHelpButtonViewed']], - }, - actions: { + }), + actions({ toggleHelp: true, showHelp: true, hideHelp: true, - }, - reducers: { + }), + reducers({ isHelpVisible: [ false, { @@ -50,8 +52,8 @@ export const helpButtonLogic = kea({ hideHelp: () => false, }, ], - }, - listeners: ({ actions, values }) => ({ + }), + listeners(({ actions, values }) => ({ showHelp: () => { actions.reportHelpButtonViewed() }, @@ -60,8 +62,8 @@ export const helpButtonLogic = kea({ actions.reportHelpButtonViewed() } }, - }), -}) + })), +]) export interface HelpButtonProps { placement?: Placement diff --git a/frontend/src/lib/components/IntervalFilter/intervalFilterLogic.ts b/frontend/src/lib/components/IntervalFilter/intervalFilterLogic.ts index a2ee9404353d3..d2136d8d8a682 100644 --- a/frontend/src/lib/components/IntervalFilter/intervalFilterLogic.ts +++ b/frontend/src/lib/components/IntervalFilter/intervalFilterLogic.ts @@ -1,4 +1,4 @@ -import { kea } from 'kea' +import { kea, props, key, path, connect, actions, reducers, listeners } from 'kea' import { objectsEqual, dateMapping } from 'lib/utils' import type { intervalFilterLogicType } from './intervalFilterLogicType' import { IntervalKeyType, Intervals, intervals } from 'lib/components/IntervalFilter/intervals' @@ -10,27 +10,27 @@ import { lemonToast } from 'lib/lemon-ui/lemonToast' import { BASE_MATH_DEFINITIONS } from 'scenes/trends/mathsLogic' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' -export const intervalFilterLogic = kea({ - props: {} as InsightLogicProps, - key: keyForInsightLogicProps('new'), - path: (key) => ['lib', 'components', 'IntervalFilter', 'intervalFilterLogic', key], - connect: (props: InsightLogicProps) => ({ +export const intervalFilterLogic = kea([ + props({} as InsightLogicProps), + key(keyForInsightLogicProps('new')), + path((key) => ['lib', 'components', 'IntervalFilter', 'intervalFilterLogic', key]), + connect((props: InsightLogicProps) => ({ actions: [insightVizDataLogic(props), ['updateQuerySource']], values: [insightVizDataLogic(props), ['interval', 'querySource']], - }), - actions: () => ({ + })), + actions(() => ({ setInterval: (interval: IntervalKeyType) => ({ interval }), setEnabledIntervals: (enabledIntervals: Intervals) => ({ enabledIntervals }), - }), - reducers: () => ({ + })), + reducers(() => ({ enabledIntervals: [ { ...intervals } as Intervals, { setEnabledIntervals: (_, { enabledIntervals }) => enabledIntervals, }, ], - }), - listeners: ({ values, actions, selectors }) => ({ + })), + listeners(({ values, actions, selectors }) => ({ setInterval: ({ interval }) => { if (values.interval !== interval) { actions.updateQuerySource({ interval } as Partial) @@ -140,5 +140,5 @@ export const intervalFilterLogic = kea({ } actions.updateQuerySource({ interval } as Partial) }, - }), -}) + })), +]) diff --git a/frontend/src/lib/components/PersonalAPIKeys/personalAPIKeysLogic.ts b/frontend/src/lib/components/PersonalAPIKeys/personalAPIKeysLogic.ts index 94e852aea865d..ec44ba725b377 100644 --- a/frontend/src/lib/components/PersonalAPIKeys/personalAPIKeysLogic.ts +++ b/frontend/src/lib/components/PersonalAPIKeys/personalAPIKeysLogic.ts @@ -1,13 +1,14 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, path, listeners, events } from 'kea' import api from 'lib/api' import { PersonalAPIKeyType } from '~/types' import type { personalAPIKeysLogicType } from './personalAPIKeysLogicType' import { copyToClipboard } from 'lib/utils' import { lemonToast } from 'lib/lemon-ui/lemonToast' -export const personalAPIKeysLogic = kea({ - path: ['lib', 'components', 'PersonalAPIKeys', 'personalAPIKeysLogic'], - loaders: ({ values }) => ({ +export const personalAPIKeysLogic = kea([ + path(['lib', 'components', 'PersonalAPIKeys', 'personalAPIKeysLogic']), + loaders(({ values }) => ({ keys: [ [] as PersonalAPIKeyType[], { @@ -27,17 +28,16 @@ export const personalAPIKeysLogic = kea({ }, }, ], - }), - listeners: () => ({ + })), + listeners(() => ({ createKeySuccess: async ({ keys }: { keys: PersonalAPIKeyType[] }) => { keys[0]?.value && (await copyToClipboard(keys[0].value, 'personal API key value')) }, deleteKeySuccess: () => { lemonToast.success(`Personal API key deleted`) }, - }), - - events: ({ actions }) => ({ + })), + events(({ actions }) => ({ afterMount: [actions.loadKeys], - }), -}) + })), +]) diff --git a/frontend/src/lib/components/PropertyFilters/components/taxonomicPropertyFilterLogic.ts b/frontend/src/lib/components/PropertyFilters/components/taxonomicPropertyFilterLogic.ts index 3820a5824c895..7933cc25fef43 100644 --- a/frontend/src/lib/components/PropertyFilters/components/taxonomicPropertyFilterLogic.ts +++ b/frontend/src/lib/components/PropertyFilters/components/taxonomicPropertyFilterLogic.ts @@ -1,4 +1,4 @@ -import { kea } from 'kea' +import { kea, props, key, path, connect, actions, reducers, selectors, listeners } from 'kea' import { TaxonomicPropertyFilterLogicProps } from 'lib/components/PropertyFilters/types' import { AnyPropertyFilter, @@ -23,12 +23,11 @@ import { import { taxonomicFilterLogic } from 'lib/components/TaxonomicFilter/taxonomicFilterLogic' import { propertyDefinitionsModel } from '~/models/propertyDefinitionsModel' -export const taxonomicPropertyFilterLogic = kea({ - path: (key) => ['lib', 'components', 'PropertyFilters', 'components', 'taxonomicPropertyFilterLogic', key], - props: {} as TaxonomicPropertyFilterLogicProps, - key: (props) => `${props.pageKey}-${props.filterIndex}`, - - connect: (props: TaxonomicPropertyFilterLogicProps) => ({ +export const taxonomicPropertyFilterLogic = kea([ + props({} as TaxonomicPropertyFilterLogicProps), + key((props) => `${props.pageKey}-${props.filterIndex}`), + path((key) => ['lib', 'components', 'PropertyFilters', 'components', 'taxonomicPropertyFilterLogic', key]), + connect((props: TaxonomicPropertyFilterLogicProps) => ({ values: [ props.propertyFilterLogic, ['filters'], @@ -42,18 +41,16 @@ export const taxonomicPropertyFilterLogic = kea ({ taxonomicGroup, propertyKey, }), openDropdown: true, closeDropdown: true, - }, - - reducers: { + }), + reducers({ dropdownOpen: [ false, { @@ -61,9 +58,8 @@ export const taxonomicPropertyFilterLogic = kea false, }, ], - }, - - selectors: { + }), + selectors({ filter: [ (s, p) => [s.filters, p.filterIndex], (filters, filterIndex): AnyPropertyFilter | null => @@ -82,9 +78,8 @@ export const taxonomicPropertyFilterLogic = kea ({ + }), + listeners(({ actions, values, props }) => ({ selectItem: ({ taxonomicGroup, propertyKey }) => { const propertyType = taxonomicFilterTypeToPropertyFilterType(taxonomicGroup.type) if (propertyKey && propertyType) { @@ -134,5 +129,5 @@ export const taxonomicPropertyFilterLogic = kea({ - path: (key) => ['lib', 'components', 'TaxonomicFilter', 'infiniteListLogic', key], - props: {} as InfiniteListLogicProps, - - key: (props) => `${props.taxonomicFilterLogicKey}-${props.listGroupType}`, - - connect: (props: InfiniteListLogicProps) => ({ +export const infiniteListLogic = kea([ + props({} as InfiniteListLogicProps), + key((props) => `${props.taxonomicFilterLogicKey}-${props.listGroupType}`), + path((key) => ['lib', 'components', 'TaxonomicFilter', 'infiniteListLogic', key]), + connect((props: InfiniteListLogicProps) => ({ values: [ taxonomicFilterLogic(props), ['searchQuery', 'value', 'groupType', 'taxonomicGroups'], @@ -79,9 +78,8 @@ export const infiniteListLogic = kea({ ['featureFlags'], ], actions: [taxonomicFilterLogic(props), ['setSearchQuery', 'selectItem', 'infiniteListResultsReceived']], - }), - - actions: { + })), + actions({ selectSelected: true, moveUp: true, moveDown: true, @@ -92,29 +90,8 @@ export const infiniteListLogic = kea({ updateRemoteItem: (item: TaxonomicDefinitionTypes) => ({ item }), expand: true, abortAnyRunningQuery: true, - }, - - reducers: ({ props }) => ({ - index: [ - (props.selectFirstItem === false ? NO_ITEM_SELECTED : 0) as number, - { - setIndex: (_, { index }) => index, - loadRemoteItemsSuccess: (state, { remoteItems }) => (remoteItems.queryChanged ? 0 : state), - }, - ], - showPopover: [props.popoverEnabled !== false, {}], - limit: [ - 100, - { - setLimit: (_, { limit }) => limit, - }, - ], - startIndex: [0, { onRowsRendered: (_, { rowInfo: { startIndex } }) => startIndex }], - stopIndex: [0, { onRowsRendered: (_, { rowInfo: { stopIndex } }) => stopIndex }], - isExpanded: [false, { expand: () => true }], }), - - loaders: ({ actions, values, cache }) => ({ + loaders(({ actions, values, cache }) => ({ remoteItems: [ createEmptyListStorage('', true), { @@ -208,61 +185,27 @@ export const infiniteListLogic = kea({ }, }, ], - }), - - listeners: ({ values, actions, props, cache }) => ({ - onRowsRendered: ({ rowInfo: { startIndex, stopIndex, overscanStopIndex } }) => { - if (values.hasRemoteDataSource) { - let loadFrom: number | null = null - for (let i = startIndex; i < (stopIndex + overscanStopIndex) / 2; i++) { - if (!values.results[i]) { - loadFrom = i - break - } - } - if (loadFrom !== null) { - const offset = (loadFrom || startIndex) - values.localItems.count - actions.loadRemoteItems({ offset, limit: values.limit }) - } - } - }, - setSearchQuery: () => { - if (values.hasRemoteDataSource) { - actions.loadRemoteItems({ offset: 0, limit: values.limit }) - } else { - actions.setIndex(0) - } - }, - moveUp: () => { - const { index, totalListCount } = values - actions.setIndex((index - 1 + totalListCount) % totalListCount) - }, - moveDown: () => { - const { index, totalListCount } = values - actions.setIndex((index + 1) % totalListCount) - }, - selectSelected: () => { - if (values.isExpandableButtonSelected) { - actions.expand() - } else { - actions.selectItem(values.group, values.selectedItemValue, values.selectedItem) - } - }, - loadRemoteItemsSuccess: ({ remoteItems }) => { - actions.infiniteListResultsReceived(props.listGroupType, remoteItems) - }, - expand: () => { - actions.loadRemoteItems({ offset: values.index, limit: values.limit }) - }, - abortAnyRunningQuery: () => { - if (cache.abortController) { - cache.abortController.abort() - } - cache.abortController = new AbortController() - }, - }), - - selectors: { + })), + reducers(({ props }) => ({ + index: [ + (props.selectFirstItem === false ? NO_ITEM_SELECTED : 0) as number, + { + setIndex: (_, { index }) => index, + loadRemoteItemsSuccess: (state, { remoteItems }) => (remoteItems.queryChanged ? 0 : state), + }, + ], + showPopover: [props.popoverEnabled !== false, {}], + limit: [ + 100, + { + setLimit: (_, { limit }) => limit, + }, + ], + startIndex: [0, { onRowsRendered: (_, { rowInfo: { startIndex } }) => startIndex }], + stopIndex: [0, { onRowsRendered: (_, { rowInfo: { stopIndex } }) => stopIndex }], + isExpanded: [false, { expand: () => true }], + })), + selectors({ listGroupType: [() => [(_, props) => props.listGroupType], (listGroupType) => listGroupType], isLoading: [(s) => [s.remoteItemsLoading], (remoteItemsLoading) => remoteItemsLoading], group: [ @@ -391,9 +334,59 @@ export const infiniteListLogic = kea({ (s) => [s.index, s.startIndex, s.stopIndex], (index, startIndex, stopIndex) => typeof index === 'number' && index >= startIndex && index <= stopIndex, ], - }, - - events: ({ actions, values, props }) => ({ + }), + listeners(({ values, actions, props, cache }) => ({ + onRowsRendered: ({ rowInfo: { startIndex, stopIndex, overscanStopIndex } }) => { + if (values.hasRemoteDataSource) { + let loadFrom: number | null = null + for (let i = startIndex; i < (stopIndex + overscanStopIndex) / 2; i++) { + if (!values.results[i]) { + loadFrom = i + break + } + } + if (loadFrom !== null) { + const offset = (loadFrom || startIndex) - values.localItems.count + actions.loadRemoteItems({ offset, limit: values.limit }) + } + } + }, + setSearchQuery: () => { + if (values.hasRemoteDataSource) { + actions.loadRemoteItems({ offset: 0, limit: values.limit }) + } else { + actions.setIndex(0) + } + }, + moveUp: () => { + const { index, totalListCount } = values + actions.setIndex((index - 1 + totalListCount) % totalListCount) + }, + moveDown: () => { + const { index, totalListCount } = values + actions.setIndex((index + 1) % totalListCount) + }, + selectSelected: () => { + if (values.isExpandableButtonSelected) { + actions.expand() + } else { + actions.selectItem(values.group, values.selectedItemValue, values.selectedItem) + } + }, + loadRemoteItemsSuccess: ({ remoteItems }) => { + actions.infiniteListResultsReceived(props.listGroupType, remoteItems) + }, + expand: () => { + actions.loadRemoteItems({ offset: values.index, limit: values.limit }) + }, + abortAnyRunningQuery: () => { + if (cache.abortController) { + cache.abortController.abort() + } + cache.abortController = new AbortController() + }, + })), + events(({ actions, values, props }) => ({ afterMount: () => { if (values.hasRemoteDataSource) { actions.loadRemoteItems({ offset: 0, limit: values.limit }) @@ -402,5 +395,5 @@ export const infiniteListLogic = kea({ actions.setIndex(results.findIndex((r) => group?.getValue?.(r) === value)) } }, - }), -}) + })), +]) diff --git a/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx b/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx index f15d9b8778d3c..cbb65b827a890 100644 --- a/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx +++ b/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx @@ -1,4 +1,4 @@ -import { BuiltLogic, kea } from 'kea' +import { BuiltLogic, kea, props, key, path, connect, actions, reducers, selectors, listeners } from 'kea' import type { taxonomicFilterLogicType } from './taxonomicFilterLogicType' import { ListStorage, @@ -66,11 +66,11 @@ export const propertyTaxonomicGroupProps = ( getIcon: getPropertyDefinitionIcon, }) -export const taxonomicFilterLogic = kea({ - path: ['lib', 'components', 'TaxonomicFilter', 'taxonomicFilterLogic'], - props: {} as TaxonomicFilterLogicProps, - key: (props) => `${props.taxonomicFilterLogicKey}`, - connect: { +export const taxonomicFilterLogic = kea([ + props({} as TaxonomicFilterLogicProps), + key((props) => `${props.taxonomicFilterLogicKey}`), + path(['lib', 'components', 'TaxonomicFilter', 'taxonomicFilterLogic']), + connect({ values: [ teamLogic, ['currentTeamId'], @@ -79,8 +79,8 @@ export const taxonomicFilterLogic = kea({ groupPropertiesModel, ['allGroupProperties'], ], - }, - actions: () => ({ + }), + actions(() => ({ moveUp: true, moveDown: true, selectSelected: (onComplete?: () => void) => ({ onComplete }), @@ -98,9 +98,8 @@ export const taxonomicFilterLogic = kea({ groupType, results, }), - }), - - reducers: ({ selectors }) => ({ + })), + reducers(({ selectors }) => ({ searchQuery: [ '', { @@ -126,11 +125,8 @@ export const taxonomicFilterLogic = kea({ enableMouseInteractions: () => true, }, ], - }), - - // NB, don't change to the async "selectors: (logic) => {}", as this causes a white screen when infiniteListLogic-s - // connect to taxonomicFilterLogic to select their initial values. They won't be built yet and will be unknown. - selectors: { + })), + selectors({ taxonomicFilterLogicKey: [ (_, p) => [p.taxonomicFilterLogicKey], (taxonomicFilterLogicKey) => taxonomicFilterLogicKey, @@ -546,8 +542,8 @@ export const taxonomicFilterLogic = kea({ .join('') }, ], - }, - listeners: ({ actions, values, props }) => ({ + }), + listeners(({ actions, values, props }) => ({ selectItem: ({ group, value, item }) => { if (item) { props.onChange?.(group, value, item) @@ -648,5 +644,5 @@ export const taxonomicFilterLogic = kea({ updatePropertyDefinitions(newPropertyDefinitions) } }, - }), -}) + })), +]) diff --git a/frontend/src/lib/components/VisibilitySensor/visibilitySensorLogic.tsx b/frontend/src/lib/components/VisibilitySensor/visibilitySensorLogic.tsx index 6300bdad6ad2c..d278114c05ce7 100644 --- a/frontend/src/lib/components/VisibilitySensor/visibilitySensorLogic.tsx +++ b/frontend/src/lib/components/VisibilitySensor/visibilitySensorLogic.tsx @@ -1,46 +1,32 @@ -import { kea } from 'kea' +import { windowValues } from 'kea-windowvalues' +import { kea, props, key, path, actions, reducers, selectors, listeners } from 'kea' import type { visibilitySensorLogicType } from './visibilitySensorLogicType' -export const visibilitySensorLogic = kea({ - path: (key) => ['lib', 'components', 'VisibilitySensor', 'visibilitySensorLogic', key], - props: {} as { - id: string - offset?: number - }, - - key: (props) => props.id || 'undefined', - - actions: () => ({ +export const visibilitySensorLogic = kea([ + props( + {} as { + id: string + offset?: number + } + ), + key((props) => props.id || 'undefined'), + path((key) => ['lib', 'components', 'VisibilitySensor', 'visibilitySensorLogic', key]), + actions(() => ({ setVisible: (visible: boolean) => ({ visible }), scrolling: (element: HTMLElement) => ({ element }), + })), + windowValues({ + innerHeight: (window) => window.innerHeight, }), - - reducers: () => ({ + reducers(() => ({ visible: [ false, { setVisible: (_, { visible }) => visible, }, ], - }), - - windowValues: { - innerHeight: (window) => window.innerHeight, - }, - - listeners: ({ actions, values }) => ({ - scrolling: async ({ element }, breakpoint) => { - await breakpoint(500) - - if (values.checkIsVisible(element) && !values.visible) { - actions.setVisible(true) - } else if (!values.checkIsVisible(element) && values.visible) { - actions.setVisible(false) - } - }, - }), - - selectors: () => ({ + })), + selectors(() => ({ checkIsVisible: [ (selectors) => [selectors.innerHeight, (_, props) => props.offset || 0], (windowHeight, offset) => (element: HTMLElement) => { @@ -55,5 +41,16 @@ export const visibilitySensorLogic = kea({ return top + offset >= 0 && top + offset <= windowHeight }, ], - }), -}) + })), + listeners(({ actions, values }) => ({ + scrolling: async ({ element }, breakpoint) => { + await breakpoint(500) + + if (values.checkIsVisible(element) && !values.visible) { + actions.setVisible(true) + } else if (!values.checkIsVisible(element) && values.visible) { + actions.setVisible(false) + } + }, + })), +]) diff --git a/frontend/src/lib/introductions/groupsAccessLogic.ts b/frontend/src/lib/introductions/groupsAccessLogic.ts index 703524f3df15a..7e6b45edf201f 100644 --- a/frontend/src/lib/introductions/groupsAccessLogic.ts +++ b/frontend/src/lib/introductions/groupsAccessLogic.ts @@ -1,4 +1,4 @@ -import { kea } from 'kea' +import { kea, path, connect, selectors } from 'kea' import { AvailableFeature } from '~/types' import { teamLogic } from 'scenes/teamLogic' import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic' @@ -13,12 +13,12 @@ export enum GroupsAccessStatus { Hidden, } -export const groupsAccessLogic = kea({ - path: ['lib', 'introductions', 'groupsAccessLogic'], - connect: { +export const groupsAccessLogic = kea([ + path(['lib', 'introductions', 'groupsAccessLogic']), + connect({ values: [teamLogic, ['currentTeam'], preflightLogic, ['preflight'], userLogic, ['hasAvailableFeature']], - }, - selectors: { + }), + selectors({ groupsEnabled: [ (s) => [s.hasAvailableFeature], (hasAvailableFeature) => hasAvailableFeature(AvailableFeature.GROUP_ANALYTICS), @@ -50,5 +50,5 @@ export const groupsAccessLogic = kea({ (s) => [s.groupsAccessStatus], (groupsAccessStatus) => groupsAccessStatus === GroupsAccessStatus.HasAccess, ], - }, -}) + }), +]) diff --git a/frontend/src/lib/logic/featureFlagLogic.ts b/frontend/src/lib/logic/featureFlagLogic.ts index 61ef991820447..7b2869d12cc0a 100644 --- a/frontend/src/lib/logic/featureFlagLogic.ts +++ b/frontend/src/lib/logic/featureFlagLogic.ts @@ -1,4 +1,4 @@ -import { kea } from 'kea' +import { kea, path, actions, reducers, events } from 'kea' import type { featureFlagLogicType } from './featureFlagLogicType' import posthog from 'posthog-js' import { getAppContext } from 'lib/utils/getAppContext' @@ -64,13 +64,12 @@ function spyOnFeatureFlags(featureFlags: FeatureFlagsSet): FeatureFlagsSet { } } -export const featureFlagLogic = kea({ - path: ['lib', 'logic', 'featureFlagLogic'], - actions: { +export const featureFlagLogic = kea([ + path(['lib', 'logic', 'featureFlagLogic']), + actions({ setFeatureFlags: (flags: string[], variants: Record) => ({ flags, variants }), - }, - - reducers: { + }), + reducers({ featureFlags: [ getPersistedFeatureFlags(), { persist: true }, @@ -84,11 +83,10 @@ export const featureFlagLogic = kea({ setFeatureFlags: () => true, }, ], - }, - - events: ({ actions }) => ({ + }), + events(({ actions }) => ({ afterMount: () => { posthog.onFeatureFlags(actions.setFeatureFlags) }, - }), -}) + })), +]) diff --git a/frontend/src/lib/utils/eventUsageLogic.ts b/frontend/src/lib/utils/eventUsageLogic.ts index a4f41659fe2d6..95e26683e52e0 100644 --- a/frontend/src/lib/utils/eventUsageLogic.ts +++ b/frontend/src/lib/utils/eventUsageLogic.ts @@ -1,4 +1,4 @@ -import { kea } from 'kea' +import { kea, path, connect, actions, listeners } from 'kea' import { isPostHogProp, keyMappingKeys } from 'lib/taxonomy' import posthog from 'posthog-js' import { userLogic } from 'scenes/userLogic' @@ -208,12 +208,12 @@ function sanitizeFilterParams(filters: AnyPartialFilterType): Record({ - path: ['lib', 'utils', 'eventUsageLogic'], - connect: () => ({ +export const eventUsageLogic = kea([ + path(['lib', 'utils', 'eventUsageLogic']), + connect(() => ({ values: [preflightLogic, ['realm'], userLogic, ['user']], - }), - actions: { + })), + actions({ // persons related reportPersonDetailViewed: (person: PersonType) => ({ person }), reportPersonsModalViewed: (params: any) => ({ @@ -505,8 +505,8 @@ export const eventUsageLogic = kea({ reportOnboardingProductSelected: (productKey: string) => ({ productKey }), reportOnboardingCompleted: (productKey: string) => ({ productKey }), reportSubscribedDuringOnboarding: (productKey: string) => ({ productKey }), - }, - listeners: ({ values }) => ({ + }), + listeners(({ values }) => ({ reportAxisUnitsChanged: (properties) => { posthog.capture('axis units changed', properties) }, @@ -1264,5 +1264,5 @@ export const eventUsageLogic = kea({ product_key: productKey, }) }, - }), -}) + })), +]) diff --git a/frontend/src/models/actionsModel.ts b/frontend/src/models/actionsModel.ts index e2c7c91cbed05..28644a1f07f15 100644 --- a/frontend/src/models/actionsModel.ts +++ b/frontend/src/models/actionsModel.ts @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, props, path, connect, selectors, events } from 'kea' import api from 'lib/api' import { ActionType } from '~/types' import type { actionsModelType } from './actionsModelType' @@ -12,13 +13,13 @@ export function findActionName(id: number): string | null { return actionsModel.findMounted()?.values.actions.find((a) => a.id === id)?.name || null } -export const actionsModel = kea({ - path: ['models', 'actionsModel'], - props: {} as ActionsModelProps, - connect: { +export const actionsModel = kea([ + props({} as ActionsModelProps), + path(['models', 'actionsModel']), + connect({ values: [teamLogic, ['currentTeam']], - }, - loaders: ({ props, values }) => ({ + }), + loaders(({ props, values }) => ({ actions: { __default: [] as ActionType[], loadActions: async () => { @@ -27,8 +28,8 @@ export const actionsModel = kea({ }, updateAction: (action: ActionType) => (values.actions || []).map((a) => (action.id === a.id ? action : a)), }, - }), - selectors: ({ selectors }) => ({ + })), + selectors(({ selectors }) => ({ actionsGrouped: [ () => [selectors.actions], (actions: ActionType[]) => { @@ -47,14 +48,13 @@ export const actionsModel = kea({ (actions): Partial> => Object.fromEntries(actions.map((action) => [action.id, action])), ], - }), - - events: ({ values, actions }) => ({ + })), + events(({ values, actions }) => ({ afterMount: () => { if (isAuthenticatedTeam(values.currentTeam)) { // Don't load on shared insights/dashboards actions.loadActions() } }, - }), -}) + })), +]) diff --git a/frontend/src/models/cohortsModel.ts b/frontend/src/models/cohortsModel.ts index 6d0ca78e3b38d..841bcad73f1a4 100644 --- a/frontend/src/models/cohortsModel.ts +++ b/frontend/src/models/cohortsModel.ts @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, path, connect, actions, reducers, selectors, listeners, events } from 'kea' import api from 'lib/api' import type { cohortsModelType } from './cohortsModelType' import { CohortType, ExporterFormat } from '~/types' @@ -9,20 +10,19 @@ import { isAuthenticatedTeam, teamLogic } from 'scenes/teamLogic' const POLL_TIMEOUT = 5000 -export const cohortsModel = kea({ - path: ['models', 'cohortsModel'], - connect: { +export const cohortsModel = kea([ + path(['models', 'cohortsModel']), + connect({ values: [teamLogic, ['currentTeam']], - }, - actions: () => ({ + }), + actions(() => ({ setPollTimeout: (pollTimeout: number | null) => ({ pollTimeout }), updateCohort: (cohort: CohortType) => ({ cohort }), deleteCohort: (cohort: Partial) => ({ cohort }), cohortCreated: (cohort: CohortType) => ({ cohort }), exportCohortPersons: (id: CohortType['id'], columns?: string[]) => ({ id, columns }), - }), - - loaders: () => ({ + })), + loaders(() => ({ cohorts: { __default: [] as CohortType[], loadCohorts: async () => { @@ -32,9 +32,8 @@ export const cohortsModel = kea({ return response?.results?.map((cohort) => processCohort(cohort)) || [] }, }, - }), - - reducers: { + })), + reducers({ pollTimeout: [ null as number | null, { @@ -61,18 +60,16 @@ export const cohortsModel = kea({ return [...state].filter((c) => c.id !== cohort.id) }, }, - }, - - selectors: { + }), + selectors({ cohortsWithAllUsers: [(s) => [s.cohorts], (cohorts) => [{ id: 'all', name: 'All Users*' }, ...cohorts]], cohortsById: [ (s) => [s.cohorts], (cohorts): Partial> => Object.fromEntries(cohorts.map((cohort) => [cohort.id, cohort])), ], - }, - - listeners: ({ actions }) => ({ + }), + listeners(({ actions }) => ({ loadCohortsSuccess: async ({ cohorts }: { cohorts: CohortType[] }) => { const is_calculating = cohorts.filter((cohort) => cohort.is_calculating).length > 0 if (!is_calculating) { @@ -99,9 +96,8 @@ export const cohortsModel = kea({ callback: actions.loadCohorts, }) }, - }), - - events: ({ actions, values }) => ({ + })), + events(({ actions, values }) => ({ afterMount: () => { if (isAuthenticatedTeam(values.currentTeam)) { // Don't load on shared insights/dashboards @@ -111,5 +107,5 @@ export const cohortsModel = kea({ beforeUnmount: () => { clearTimeout(values.pollTimeout || undefined) }, - }), -}) + })), +]) diff --git a/frontend/src/models/dashboardsModel.tsx b/frontend/src/models/dashboardsModel.tsx index 5f5f9d213fb40..6abe045e1cc0a 100644 --- a/frontend/src/models/dashboardsModel.tsx +++ b/frontend/src/models/dashboardsModel.tsx @@ -1,5 +1,6 @@ -import { kea } from 'kea' -import { router } from 'kea-router' +import { loaders } from 'kea-loaders' +import { kea, path, connect, actions, reducers, selectors, listeners, events } from 'kea' +import { router, urlToAction } from 'kea-router' import api, { PaginatedResponse } from 'lib/api' import { idToKey, isUserLoggedIn } from 'lib/utils' import { DashboardEventSource, eventUsageLogic } from 'lib/utils/eventUsageLogic' @@ -11,12 +12,12 @@ import { lemonToast } from 'lib/lemon-ui/lemonToast' import { tagsModel } from '~/models/tagsModel' import { GENERATED_DASHBOARD_PREFIX } from 'lib/constants' -export const dashboardsModel = kea({ - path: ['models', 'dashboardsModel'], - connect: { +export const dashboardsModel = kea([ + path(['models', 'dashboardsModel']), + connect({ actions: [tagsModel, ['loadTags']], - }, - actions: () => ({ + }), + actions(() => ({ // we page through the dashboards and need to manually track when that is finished dashboardsFullyLoaded: true, delayedDeleteDashboard: (id: number) => ({ id }), @@ -77,8 +78,8 @@ export const dashboardsModel = kea({ dashboardId, }), tileAddedToDashboard: (dashboardId: number) => ({ dashboardId }), - }), - loaders: ({ values, actions }) => ({ + })), + loaders(({ values, actions }) => ({ pagedDashboards: [ null as PaginatedResponse | null, { @@ -176,9 +177,8 @@ export const dashboardsModel = kea({ return result }, }, - }), - - reducers: { + })), + reducers({ pagingDashboardsCompleted: [ false, { @@ -232,9 +232,8 @@ export const dashboardsModel = kea({ setLastDashboardId: (_, { id }) => id, }, ], - }, - - selectors: ({ selectors }) => ({ + }), + selectors(({ selectors }) => ({ nameSortedDashboards: [ () => [selectors.rawDashboards], (rawDashboards) => { @@ -260,15 +259,8 @@ export const dashboardsModel = kea({ () => [selectors.nameSortedDashboards], (nameSortedDashboards) => nameSortedDashboards.filter((d) => d.pinned), ], - }), - - events: ({ actions }) => ({ - afterMount: () => { - actions.loadDashboards() - }, - }), - - listeners: ({ actions, values }) => ({ + })), + listeners(({ actions, values }) => ({ loadDashboardsSuccess: ({ pagedDashboards }) => { if (pagedDashboards?.next) { actions.loadDashboards(pagedDashboards.next) @@ -321,16 +313,20 @@ export const dashboardsModel = kea({ ) }, - }), - - urlToAction: ({ actions }) => ({ + })), + urlToAction(({ actions }) => ({ '/dashboard/:id': ({ id }) => { if (id) { actions.setLastDashboardId(parseInt(id)) } }, - }), -}) + })), + events(({ actions }) => ({ + afterMount: () => { + actions.loadDashboards() + }, + })), +]) export function nameCompareFunction(a: DashboardBasicType, b: DashboardBasicType): number { // No matter where we're comparing dashboards, we want to sort generated dashboards last diff --git a/frontend/src/models/funnelsModel.ts b/frontend/src/models/funnelsModel.ts index fbd28dddda5bc..63aa6ec71f4d6 100644 --- a/frontend/src/models/funnelsModel.ts +++ b/frontend/src/models/funnelsModel.ts @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, path, actions, reducers, listeners, events } from 'kea' import api from 'lib/api' import { toParams } from 'lib/utils' import { SavedFunnel, InsightType } from '~/types' @@ -17,9 +18,14 @@ const parseSavedFunnel = (result: Record): SavedFunnel => { } } -export const funnelsModel = kea({ - path: ['models', 'funnelsModel'], - loaders: ({ values, actions }) => ({ +export const funnelsModel = kea([ + path(['models', 'funnelsModel']), + actions(() => ({ + setNext: (next) => ({ next }), + loadNext: true, + appendFunnels: (funnels) => ({ funnels }), + })), + loaders(({ values, actions }) => ({ funnels: { __default: [] as SavedFunnel[], loadFunnels: async () => { @@ -40,8 +46,8 @@ export const funnelsModel = kea({ return values.funnels.filter((funnel) => funnel.id !== funnelId) }, }, - }), - reducers: () => ({ + })), + reducers(() => ({ next: [ null as null | string, { @@ -58,13 +64,8 @@ export const funnelsModel = kea({ setNext: () => false, }, ], - }), - actions: () => ({ - setNext: (next) => ({ next }), - loadNext: true, - appendFunnels: (funnels) => ({ funnels }), - }), - listeners: ({ values, actions }) => ({ + })), + listeners(({ values, actions }) => ({ loadNext: async () => { if (!values.next) { throw new Error('URL of next page of funnels is not known.') @@ -74,8 +75,8 @@ export const funnelsModel = kea({ actions.setNext(response.next) actions.appendFunnels(results) }, - }), - events: ({ actions }) => ({ + })), + events(({ actions }) => ({ afterMount: actions.loadFunnels, - }), -}) + })), +]) diff --git a/frontend/src/models/groupPropertiesModel.ts b/frontend/src/models/groupPropertiesModel.ts index 1692752fef6f5..067ff5ff8beba 100644 --- a/frontend/src/models/groupPropertiesModel.ts +++ b/frontend/src/models/groupPropertiesModel.ts @@ -1,16 +1,17 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, path, connect, selectors, events } from 'kea' import type { groupPropertiesModelType } from './groupPropertiesModelType' import api from 'lib/api' import { GroupTypeProperties, PersonProperty } from '~/types' import { teamLogic } from 'scenes/teamLogic' import { groupsAccessLogic } from 'lib/introductions/groupsAccessLogic' -export const groupPropertiesModel = kea({ - path: ['models', 'groupPropertiesModel'], - connect: { +export const groupPropertiesModel = kea([ + path(['models', 'groupPropertiesModel']), + connect({ values: [teamLogic, ['currentTeamId'], groupsAccessLogic, ['groupsEnabled']], - }, - loaders: ({ values }) => ({ + }), + loaders(({ values }) => ({ allGroupProperties: [ {} as GroupTypeProperties, { @@ -22,8 +23,8 @@ export const groupPropertiesModel = kea({ }, }, ], - }), - selectors: { + })), + selectors({ groupProperties: [ (s) => [s.allGroupProperties], (groupProperties: GroupTypeProperties) => @@ -35,8 +36,8 @@ export const groupPropertiesModel = kea({ groupProperties_2: [(s) => [s.allGroupProperties], (groupProperties) => groupProperties['2']], groupProperties_3: [(s) => [s.allGroupProperties], (groupProperties) => groupProperties['3']], groupProperties_4: [(s) => [s.allGroupProperties], (groupProperties) => groupProperties['4']], - }, - events: ({ actions }) => ({ - afterMount: actions.loadAllGroupProperties, }), -}) + events(({ actions }) => ({ + afterMount: actions.loadAllGroupProperties, + })), +]) diff --git a/frontend/src/models/index.ts b/frontend/src/models/index.ts index 2ab535addeb98..31a15fb580275 100644 --- a/frontend/src/models/index.ts +++ b/frontend/src/models/index.ts @@ -1,4 +1,4 @@ -import { kea } from 'kea' +import { kea, path, connect } from 'kea' import { actionsModel } from './actionsModel' import { annotationsModel } from './annotationsModel' import { cohortsModel } from './cohortsModel' @@ -7,7 +7,7 @@ import { propertyDefinitionsModel } from './propertyDefinitionsModel' import type { modelsType } from './indexType' /** "Models" are logics that are persistently mounted (start with app) */ -export const models = kea({ - path: ['models', 'index'], - connect: [actionsModel, annotationsModel, cohortsModel, dashboardsModel, propertyDefinitionsModel], -}) +export const models = kea([ + path(['models', 'index']), + connect([actionsModel, annotationsModel, cohortsModel, dashboardsModel, propertyDefinitionsModel]), +]) diff --git a/frontend/src/models/insightsModel.tsx b/frontend/src/models/insightsModel.tsx index 077b063721c54..3be3e93faa231 100644 --- a/frontend/src/models/insightsModel.tsx +++ b/frontend/src/models/insightsModel.tsx @@ -1,4 +1,4 @@ -import { kea } from 'kea' +import { kea, path, connect, actions, listeners } from 'kea' import api from 'lib/api' import { promptLogic } from 'lib/logic/promptLogic' import { InsightModel } from '~/types' @@ -6,10 +6,10 @@ import { teamLogic } from 'scenes/teamLogic' import type { insightsModelType } from './insightsModelType' import { lemonToast } from 'lib/lemon-ui/lemonToast' -export const insightsModel = kea({ - path: ['models', 'insightsModel'], - connect: [promptLogic({ key: 'rename-insight' }), teamLogic], - actions: () => ({ +export const insightsModel = kea([ + path(['models', 'insightsModel']), + connect([promptLogic({ key: 'rename-insight' }), teamLogic]), + actions(() => ({ renameInsight: (item: InsightModel) => ({ item }), renameInsightSuccess: (item: InsightModel) => ({ item }), //TODO this duplicates the insight but not the dashboard tile (e.g. if duplicated from dashboard you lose tile color @@ -22,8 +22,8 @@ export const insightsModel = kea({ dashboardId, insightIds, }), - }), - listeners: ({ actions }) => ({ + })), + listeners(({ actions }) => ({ renameInsight: async ({ item }) => { promptLogic({ key: 'rename-insight' }).actions.prompt({ title: 'Rename insight', @@ -58,5 +58,5 @@ export const insightsModel = kea({ actions.duplicateInsightSuccess(addedItem) lemonToast.success('Insight duplicated') }, - }), -}) + })), +]) diff --git a/frontend/src/scenes/App.tsx b/frontend/src/scenes/App.tsx index 1c3cea6bed946..deecc672d59ec 100644 --- a/frontend/src/scenes/App.tsx +++ b/frontend/src/scenes/App.tsx @@ -1,4 +1,4 @@ -import { kea, useMountedLogic, useValues, BindLogic } from 'kea' +import { kea, useMountedLogic, useValues, BindLogic, path, connect, actions, reducers, selectors, events } from 'kea' import { ToastContainer, Slide } from 'react-toastify' import { preflightLogic } from './PreflightCheck/preflightLogic' import { userLogic } from 'scenes/userLogic' @@ -28,18 +28,18 @@ import { useEffect } from 'react' import { themeLogic } from '~/layout/navigation-3000/themeLogic' import { FeaturePreviewsModal } from '~/layout/FeaturePreviews' -export const appLogic = kea({ - path: ['scenes', 'App'], - connect: [teamLogic, organizationLogic, frontendAppsLogic, inAppPromptLogic], - actions: { +export const appLogic = kea([ + path(['scenes', 'App']), + connect([teamLogic, organizationLogic, frontendAppsLogic, inAppPromptLogic]), + actions({ enableDelayedSpinner: true, ignoreFeatureFlags: true, - }, - reducers: { + }), + reducers({ showingDelayedSpinner: [false, { enableDelayedSpinner: () => true }], featureFlagsTimedOut: [false, { ignoreFeatureFlags: () => true }], - }, - selectors: { + }), + selectors({ showApp: [ (s) => [ userLogic.selectors.userLoading, @@ -57,8 +57,8 @@ export const appLogic = kea({ ) }, ], - }, - events: ({ actions, cache }) => ({ + }), + events(({ actions, cache }) => ({ afterMount: () => { cache.spinnerTimeout = window.setTimeout(() => actions.enableDelayedSpinner(), 1000) cache.featureFlagTimeout = window.setTimeout(() => actions.ignoreFeatureFlags(), 3000) @@ -67,8 +67,8 @@ export const appLogic = kea({ window.clearTimeout(cache.spinnerTimeout) window.clearTimeout(cache.featureFlagTimeout) }, - }), -}) + })), +]) export function App(): JSX.Element | null { const { showApp, showingDelayedSpinner } = useValues(appLogic) diff --git a/frontend/src/scenes/actions/actionLogic.ts b/frontend/src/scenes/actions/actionLogic.ts index a416e9aa17e5c..e3781d37902f0 100644 --- a/frontend/src/scenes/actions/actionLogic.ts +++ b/frontend/src/scenes/actions/actionLogic.ts @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, props, key, path, actions, reducers, selectors, listeners, events } from 'kea' import api from 'lib/api' import type { actionLogicType } from './actionLogicType' import { ActionType, Breadcrumb } from '~/types' @@ -8,17 +9,29 @@ export interface ActionLogicProps { id?: ActionType['id'] } -export const actionLogic = kea({ - props: {} as ActionLogicProps, - key: (props) => props.id || 'new', - path: (key) => ['scenes', 'actions', 'actionLogic', key], - - actions: () => ({ +export const actionLogic = kea([ + props({} as ActionLogicProps), + key((props) => props.id || 'new'), + path((key) => ['scenes', 'actions', 'actionLogic', key]), + actions(() => ({ checkIsFinished: (action) => ({ action }), setPollTimeout: (pollTimeout) => ({ pollTimeout }), setIsComplete: (isComplete) => ({ isComplete }), - }), - reducers: () => ({ + })), + loaders(({ actions, props }) => ({ + action: { + loadAction: async () => { + actions.setIsComplete(false) + if (!props.id) { + throw new Error('Cannot fetch an unsaved action from the API.') + } + const action = await api.actions.get(props.id) + actions.checkIsFinished(action) + return action + }, + }, + })), + reducers(() => ({ pollTimeout: [ null as number | null, { @@ -31,8 +44,8 @@ export const actionLogic = kea({ setIsComplete: (_, { isComplete }) => isComplete, }, ], - }), - selectors: { + })), + selectors({ breadcrumbs: [ (s) => [s.action], (action): Breadcrumb[] => [ @@ -50,21 +63,8 @@ export const actionLogic = kea({ }, ], ], - }, - loaders: ({ actions, props }) => ({ - action: { - loadAction: async () => { - actions.setIsComplete(false) - if (!props.id) { - throw new Error('Cannot fetch an unsaved action from the API.') - } - const action = await api.actions.get(props.id) - actions.checkIsFinished(action) - return action - }, - }, }), - listeners: ({ actions, values }) => ({ + listeners(({ actions, values }) => ({ checkIsFinished: ({ action }) => { if (action.is_calculating) { actions.setPollTimeout(setTimeout(() => actions.loadAction(), 1000)) @@ -73,13 +73,13 @@ export const actionLogic = kea({ values.pollTimeout && clearTimeout(values.pollTimeout) } }, - }), - events: ({ values, actions, props }) => ({ + })), + events(({ values, actions, props }) => ({ afterMount: () => { props.id && actions.loadAction() }, beforeUnmount: () => { values.pollTimeout && clearTimeout(values.pollTimeout) }, - }), -}) + })), +]) diff --git a/frontend/src/scenes/batch_exports/batchExportLogsLogic.ts b/frontend/src/scenes/batch_exports/batchExportLogsLogic.ts index 5323175d4c6e3..b63e4f50abf20 100644 --- a/frontend/src/scenes/batch_exports/batchExportLogsLogic.ts +++ b/frontend/src/scenes/batch_exports/batchExportLogsLogic.ts @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, props, key, path, connect, actions, reducers, selectors, listeners, events } from 'kea' import api from '~/lib/api' import { BatchExportLogEntryLevel, BatchExportLogEntry } from '~/types' import { CheckboxValueType } from 'antd/lib/checkbox/Group' @@ -12,25 +13,22 @@ export interface BatchExportLogsProps { export const LOGS_PORTION_LIMIT = 50 -export const batchExportLogsLogic = kea({ - path: (batchExportId) => ['scenes', 'batch_exports', 'batchExportLogsLogic', batchExportId], - props: {} as BatchExportLogsProps, - key: ({ batchExportId }: BatchExportLogsProps) => batchExportId, - - connect: { +export const batchExportLogsLogic = kea([ + props({} as BatchExportLogsProps), + key(({ batchExportId }: BatchExportLogsProps) => batchExportId), + path((batchExportId) => ['scenes', 'batch_exports', 'batchExportLogsLogic', batchExportId]), + connect({ values: [teamLogic, ['currentTeamId']], - }, - - actions: { + }), + actions({ clearBatchExportLogsBackground: true, markLogsEnd: true, setBatchExportLogsTypes: (typeFilters: CheckboxValueType[]) => ({ typeFilters, }), setSearchTerm: (searchTerm: string) => ({ searchTerm }), - }, - - loaders: ({ props: { batchExportId }, values, actions, cache }) => ({ + }), + loaders(({ props: { batchExportId }, values, actions, cache }) => ({ batchExportLogs: { __default: [] as BatchExportLogEntry[], loadBatchExportLogs: async () => { @@ -86,21 +84,8 @@ export const batchExportLogsLogic = kea({ return [...results, ...values.batchExportLogsBackground] }, }, - }), - - listeners: ({ actions }) => ({ - setBatchExportLogsTypes: () => { - actions.loadBatchExportLogs() - }, - setSearchTerm: async ({ searchTerm }, breakpoint) => { - if (searchTerm) { - await breakpoint(1000) - } - actions.loadBatchExportLogs() - }, - }), - - reducers: { + })), + reducers({ batchExportLogsTypes: [ Object.values(BatchExportLogEntryLevel).filter((type) => type !== 'DEBUG'), { @@ -133,9 +118,8 @@ export const batchExportLogsLogic = kea({ markLogsEnd: () => false, }, ], - }, - - selectors: ({ selectors }) => ({ + }), + selectors(({ selectors }) => ({ leadingEntry: [ () => [selectors.batchExportLogs, selectors.batchExportLogsBackground], ( @@ -166,14 +150,24 @@ export const batchExportLogsLogic = kea({ return null }, ], - }), - - events: ({ actions, cache }) => ({ + })), + listeners(({ actions }) => ({ + setBatchExportLogsTypes: () => { + actions.loadBatchExportLogs() + }, + setSearchTerm: async ({ searchTerm }, breakpoint) => { + if (searchTerm) { + await breakpoint(1000) + } + actions.loadBatchExportLogs() + }, + })), + events(({ actions, cache }) => ({ afterMount: () => { actions.loadBatchExportLogs() }, beforeUnmount: () => { clearInterval(cache.pollingInterval) }, - }), -}) + })), +]) diff --git a/frontend/src/scenes/dashboard/dashboardCollaboratorsLogic.ts b/frontend/src/scenes/dashboard/dashboardCollaboratorsLogic.ts index 53dfa4c947b46..cc932e0138c12 100644 --- a/frontend/src/scenes/dashboard/dashboardCollaboratorsLogic.ts +++ b/frontend/src/scenes/dashboard/dashboardCollaboratorsLogic.ts @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, props, key, path, connect, actions, reducers, selectors, events } from 'kea' import api from 'lib/api' import { DashboardPrivilegeLevel, DashboardRestrictionLevel } from 'lib/constants' import { teamMembersLogic } from 'scenes/project/Settings/teamMembersLogic' @@ -16,32 +17,24 @@ export interface DashboardCollaboratorsLogicProps { dashboardId: DashboardType['id'] } -export const dashboardCollaboratorsLogic = kea({ - path: (key) => ['scenes', 'dashboard', 'dashboardCollaboratorsLogic', key], - props: {} as DashboardCollaboratorsLogicProps, - key: (props) => props.dashboardId, - connect: (props: DashboardCollaboratorsLogicProps) => ({ +export const dashboardCollaboratorsLogic = kea([ + props({} as DashboardCollaboratorsLogicProps), + key((props) => props.dashboardId), + path((key) => ['scenes', 'dashboard', 'dashboardCollaboratorsLogic', key]), + connect((props: DashboardCollaboratorsLogicProps) => ({ values: [ teamMembersLogic, ['admins', 'plainMembers', 'allMembers', 'allMembersLoading'], dashboardLogic({ id: props.dashboardId }), ['dashboard'], ], - }), - actions: { + })), + actions({ deleteExplicitCollaborator: (userUuid: UserType['uuid']) => ({ userUuid }), setExplicitCollaboratorsToBeAdded: (userUuids: string[]) => ({ userUuids }), addExplicitCollaborators: true, - }, - reducers: { - explicitCollaboratorsToBeAdded: [ - [] as string[], - { - setExplicitCollaboratorsToBeAdded: (_, { userUuids }) => userUuids, - }, - ], - }, - loaders: ({ values, props, actions }) => ({ + }), + loaders(({ values, props, actions }) => ({ explicitCollaborators: [ [] as DashboardCollaboratorType[], { @@ -74,8 +67,16 @@ export const dashboardCollaboratorsLogic = kea( }, }, ], + })), + reducers({ + explicitCollaboratorsToBeAdded: [ + [] as string[], + { + setExplicitCollaboratorsToBeAdded: (_, { userUuids }) => userUuids, + }, + ], }), - selectors: { + selectors({ allCollaborators: [ (s) => [s.explicitCollaborators, s.admins, s.allMembers, s.dashboard], (explicitCollaborators, admins, allMembers, dashboard): FusedDashboardCollaboratorType[] => { @@ -134,10 +135,10 @@ export const dashboardCollaboratorsLogic = kea( (explicitCollaboratorsLoading, allMembersLoading): boolean => explicitCollaboratorsLoading || allMembersLoading, ], - }, - events: ({ actions }) => ({ + }), + events(({ actions }) => ({ afterMount: () => { actions.loadExplicitCollaborators() }, - }), -}) + })), +]) diff --git a/frontend/src/scenes/data-management/DataManagementPageTabs.tsx b/frontend/src/scenes/data-management/DataManagementPageTabs.tsx index 887d9bcce254d..f78c851a7f43b 100644 --- a/frontend/src/scenes/data-management/DataManagementPageTabs.tsx +++ b/frontend/src/scenes/data-management/DataManagementPageTabs.tsx @@ -1,4 +1,5 @@ -import { kea, useActions, useValues } from 'kea' +import { actionToUrl, urlToAction } from 'kea-router' +import { kea, useActions, useValues, path, connect, actions, reducers, selectors } from 'kea' import { urls } from 'scenes/urls' import type { eventsTabsLogicType } from './DataManagementPageTabsType' import { Tooltip } from 'lib/lemon-ui/Tooltip' @@ -27,32 +28,32 @@ const tabUrls = { [DataManagementTab.Database]: urls.database(), } -const eventsTabsLogic = kea({ - path: ['scenes', 'events', 'eventsTabsLogic'], - connect: { +const eventsTabsLogic = kea([ + path(['scenes', 'events', 'eventsTabsLogic']), + connect({ values: [featureFlagLogic, ['featureFlags']], - }, - actions: { + }), + actions({ setTab: (tab: DataManagementTab) => ({ tab }), - }, - reducers: { + }), + reducers({ tab: [ DataManagementTab.EventDefinitions as DataManagementTab, { setTab: (_, { tab }) => tab, }, ], - }, - selectors: { + }), + selectors({ showWarningsTab: [ (s) => [s.featureFlags], (featureFlags): boolean => !!featureFlags[FEATURE_FLAGS.INGESTION_WARNINGS_ENABLED], ], - }, - actionToUrl: () => ({ - setTab: ({ tab }) => tabUrls[tab as DataManagementTab] || urls.events(), }), - urlToAction: ({ actions, values }) => { + actionToUrl(() => ({ + setTab: ({ tab }) => tabUrls[tab as DataManagementTab] || urls.events(), + })), + urlToAction(({ actions, values }) => { return Object.fromEntries( Object.entries(tabUrls).map(([key, url]) => [ url, @@ -63,8 +64,8 @@ const eventsTabsLogic = kea({ }, ]) ) - }, -}) + }), +]) export function DataManagementPageTabs({ tab }: { tab: DataManagementTab }): JSX.Element { const { showWarningsTab } = useValues(eventsTabsLogic) diff --git a/frontend/src/scenes/data-warehouse/DataWarehousePageTabs.tsx b/frontend/src/scenes/data-warehouse/DataWarehousePageTabs.tsx index 821746f7f59e6..06aaec8915831 100644 --- a/frontend/src/scenes/data-warehouse/DataWarehousePageTabs.tsx +++ b/frontend/src/scenes/data-warehouse/DataWarehousePageTabs.tsx @@ -1,4 +1,5 @@ -import { kea, useActions, useValues } from 'kea' +import { actionToUrl, urlToAction } from 'kea-router' +import { kea, useActions, useValues, path, actions, reducers } from 'kea' import { urls } from 'scenes/urls' import { LemonTabs } from 'lib/lemon-ui/LemonTabs' @@ -18,23 +19,23 @@ const tabUrls = { [DataWarehouseTab.Views]: urls.dataWarehouseSavedQueries(), } -const dataWarehouseTabsLogic = kea({ - path: ['scenes', 'warehouse', 'dataWarehouseTabsLogic'], - actions: { +const dataWarehouseTabsLogic = kea([ + path(['scenes', 'warehouse', 'dataWarehouseTabsLogic']), + actions({ setTab: (tab: DataWarehouseTab) => ({ tab }), - }, - reducers: { + }), + reducers({ tab: [ DataWarehouseTab.External as DataWarehouseTab, { setTab: (_, { tab }) => tab, }, ], - }, - actionToUrl: () => ({ - setTab: ({ tab }) => tabUrls[tab as DataWarehouseTab] || urls.dataWarehousePosthog(), }), - urlToAction: ({ actions, values }) => { + actionToUrl(() => ({ + setTab: ({ tab }) => tabUrls[tab as DataWarehouseTab] || urls.dataWarehousePosthog(), + })), + urlToAction(({ actions, values }) => { return Object.fromEntries( Object.entries(tabUrls).map(([key, url]) => [ url, @@ -45,8 +46,8 @@ const dataWarehouseTabsLogic = kea({ }, ]) ) - }, -}) + }), +]) export function DataWarehousePageTabs({ tab }: { tab: DataWarehouseTab }): JSX.Element { const { setTab } = useActions(dataWarehouseTabsLogic) diff --git a/frontend/src/scenes/feature-flags/featureFlagsLogic.ts b/frontend/src/scenes/feature-flags/featureFlagsLogic.ts index dad83547a4ec2..3023d747a13c6 100644 --- a/frontend/src/scenes/feature-flags/featureFlagsLogic.ts +++ b/frontend/src/scenes/feature-flags/featureFlagsLogic.ts @@ -1,11 +1,12 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, props, path, connect, actions, reducers, selectors, listeners, events } from 'kea' import api from 'lib/api' import Fuse from 'fuse.js' import type { featureFlagsLogicType } from './featureFlagsLogicType' import { Breadcrumb, FeatureFlagType } from '~/types' import { teamLogic } from '../teamLogic' import { urls } from 'scenes/urls' -import { router } from 'kea-router' +import { router, actionToUrl, urlToAction } from 'kea-router' import { LemonSelectOption } from 'lib/lemon-ui/LemonSelect' export enum FeatureFlagsTab { @@ -31,21 +32,21 @@ export interface FlagLogicProps { flagPrefix?: string // used to filter flags by prefix e.g. for the user interview flags } -export const featureFlagsLogic = kea({ - props: {} as FlagLogicProps, - path: ['scenes', 'feature-flags', 'featureFlagsLogic'], - connect: { +export const featureFlagsLogic = kea([ + props({} as FlagLogicProps), + path(['scenes', 'feature-flags', 'featureFlagsLogic']), + connect({ values: [teamLogic, ['currentTeamId']], - }, - actions: { + }), + actions({ updateFlag: (flag: FeatureFlagType) => ({ flag }), deleteFlag: (id: number) => ({ id }), setSearchTerm: (searchTerm: string) => ({ searchTerm }), setActiveTab: (tabKey: FeatureFlagsTab) => ({ tabKey }), setFeatureFlagsFilters: (filters: Partial, replace?: boolean) => ({ filters, replace }), closeEnrichAnalyticsNotice: true, - }, - loaders: ({ values }) => ({ + }), + loaders(({ values }) => ({ featureFlags: { __default: [] as FeatureFlagType[], loadFeatureFlags: async () => { @@ -57,8 +58,48 @@ export const featureFlagsLogic = kea({ return [...values.featureFlags].map((flag) => (flag.id === response.id ? response : flag)) }, }, + })), + reducers({ + searchTerm: { + setSearchTerm: (_, { searchTerm }) => searchTerm, + }, + featureFlags: { + updateFlag: (state, { flag }) => { + if (state.find(({ id }) => id === flag.id)) { + return state.map((stateFlag) => (stateFlag.id === flag.id ? flag : stateFlag)) + } else { + return [flag, ...state] + } + }, + deleteFlag: (state, { id }) => state.filter((flag) => flag.id !== id), + }, + activeTab: [ + FeatureFlagsTab.OVERVIEW as FeatureFlagsTab, + { + setActiveTab: (state, { tabKey }) => + Object.values(FeatureFlagsTab).includes(tabKey) ? tabKey : state, + }, + ], + filters: [ + {} as Partial, + { + setFeatureFlagsFilters: (state, { filters, replace }) => { + if (replace) { + return { ...filters } + } + return { ...state, ...filters } + }, + }, + ], + enrichAnalyticsNoticeAcknowledged: [ + false, + { persist: true }, + { + closeEnrichAnalyticsNotice: () => true, + }, + ], }), - selectors: { + selectors({ searchedFeatureFlags: [ (selectors) => [ selectors.featureFlags, @@ -141,53 +182,13 @@ export const featureFlagsLogic = kea({ return !featureFlagsLoading && featureFlags.length <= 0 }, ], - }, - reducers: { - searchTerm: { - setSearchTerm: (_, { searchTerm }) => searchTerm, - }, - featureFlags: { - updateFlag: (state, { flag }) => { - if (state.find(({ id }) => id === flag.id)) { - return state.map((stateFlag) => (stateFlag.id === flag.id ? flag : stateFlag)) - } else { - return [flag, ...state] - } - }, - deleteFlag: (state, { id }) => state.filter((flag) => flag.id !== id), - }, - activeTab: [ - FeatureFlagsTab.OVERVIEW as FeatureFlagsTab, - { - setActiveTab: (state, { tabKey }) => - Object.values(FeatureFlagsTab).includes(tabKey) ? tabKey : state, - }, - ], - filters: [ - {} as Partial, - { - setFeatureFlagsFilters: (state, { filters, replace }) => { - if (replace) { - return { ...filters } - } - return { ...state, ...filters } - }, - }, - ], - enrichAnalyticsNoticeAcknowledged: [ - false, - { persist: true }, - { - closeEnrichAnalyticsNotice: () => true, - }, - ], - }, - listeners: ({ actions }) => ({ + }), + listeners(({ actions }) => ({ setFeatureFlagsFilters: () => { actions.loadFeatureFlags() }, - }), - actionToUrl: ({ values }) => ({ + })), + actionToUrl(({ values }) => ({ setActiveTab: () => { const searchParams = { ...router.values.searchParams, @@ -202,8 +203,8 @@ export const featureFlagsLogic = kea({ return [router.values.location.pathname, searchParams, router.values.hashParams, { replace }] }, - }), - urlToAction: ({ actions, values }) => ({ + })), + urlToAction(({ actions, values }) => ({ [urls.featureFlags()]: async (_, searchParams) => { const tabInURL = searchParams['tab'] @@ -215,10 +216,10 @@ export const featureFlagsLogic = kea({ actions.setActiveTab(tabInURL) } }, - }), - events: ({ actions }) => ({ + })), + events(({ actions }) => ({ afterMount: () => { actions.loadFeatureFlags() }, - }), -}) + })), +]) diff --git a/frontend/src/scenes/groups/groupsListLogic.ts b/frontend/src/scenes/groups/groupsListLogic.ts index d57520f9c0966..07a7d0c4b77b9 100644 --- a/frontend/src/scenes/groups/groupsListLogic.ts +++ b/frontend/src/scenes/groups/groupsListLogic.ts @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, props, key, path, connect, actions, reducers, selectors, listeners, events } from 'kea' import api from 'lib/api' import { groupsAccessLogic } from 'lib/introductions/groupsAccessLogic' import { teamLogic } from 'scenes/teamLogic' @@ -18,11 +19,11 @@ export interface GroupsListLogicProps { groupTypeIndex: number } -export const groupsListLogic = kea({ - props: {} as GroupsListLogicProps, - key: (props: GroupsListLogicProps) => props.groupTypeIndex, - path: ['groups', 'groupsListLogic'], - connect: { +export const groupsListLogic = kea([ + props({} as GroupsListLogicProps), + key((props: GroupsListLogicProps) => props.groupTypeIndex), + path(['groups', 'groupsListLogic']), + connect({ values: [ teamLogic, ['currentTeamId'], @@ -31,12 +32,12 @@ export const groupsListLogic = kea({ groupsAccessLogic, ['groupsEnabled'], ], - }, - actions: () => ({ + }), + actions(() => ({ loadGroups: (url?: string | null) => ({ url }), setSearch: (search: string, debounce: boolean = true) => ({ search, debounce }), - }), - loaders: ({ props, values }) => ({ + })), + loaders(({ props, values }) => ({ groups: [ { next: null, previous: null, results: [] } as GroupsPaginatedResponse, { @@ -53,16 +54,16 @@ export const groupsListLogic = kea({ }, }, ], - }), - reducers: { + })), + reducers({ search: [ '', { setSearch: (_, { search }) => search, }, ], - }, - selectors: { + }), + selectors({ groupTypeName: [ (s, p) => [p.groupTypeIndex, s.aggregationLabel], (groupTypeIndex, aggregationLabel): Noun => @@ -77,18 +78,18 @@ export const groupsListLogic = kea({ }, ], ], - }, - listeners: ({ actions }) => ({ + }), + listeners(({ actions }) => ({ setSearch: async ({ debounce }, breakpoint) => { if (debounce) { await breakpoint(300) } actions.loadGroups() }, - }), - events: ({ actions }) => ({ + })), + events(({ actions }) => ({ afterMount: () => { actions.loadGroups() }, - }), -}) + })), +]) diff --git a/frontend/src/scenes/groups/relatedGroupsLogic.ts b/frontend/src/scenes/groups/relatedGroupsLogic.ts index bdee5dc859e41..60c9d224d80be 100644 --- a/frontend/src/scenes/groups/relatedGroupsLogic.ts +++ b/frontend/src/scenes/groups/relatedGroupsLogic.ts @@ -1,24 +1,25 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, props, key, path, connect, actions, events } from 'kea' import api from 'lib/api' import { toParams } from 'lib/utils' import { teamLogic } from 'scenes/teamLogic' import { ActorType } from '~/types' import type { relatedGroupsLogicType } from './relatedGroupsLogicType' -export const relatedGroupsLogic = kea({ - path: ['scenes', 'groups', 'relatedGroupsLogic'], - connect: { values: [teamLogic, ['currentTeamId']] }, - - props: {} as { - groupTypeIndex: number | null - id: string - }, - key: (props) => `${props.groupTypeIndex ?? 'person'}-${props.id}`, - - actions: () => ({ +export const relatedGroupsLogic = kea([ + props( + {} as { + groupTypeIndex: number | null + id: string + } + ), + key((props) => `${props.groupTypeIndex ?? 'person'}-${props.id}`), + path(['scenes', 'groups', 'relatedGroupsLogic']), + connect({ values: [teamLogic, ['currentTeamId']] }), + actions(() => ({ loadRelatedActors: true, - }), - loaders: ({ values, props }) => ({ + })), + loaders(({ values, props }) => ({ relatedActors: [ [] as ActorType[], { @@ -32,8 +33,8 @@ export const relatedGroupsLogic = kea({ setGroup: () => [], }, ], - }), - events: ({ actions }) => ({ + })), + events(({ actions }) => ({ afterMount: actions.loadRelatedActors, - }), -}) + })), +]) diff --git a/frontend/src/scenes/insights/views/Histogram/histogramLogic.ts b/frontend/src/scenes/insights/views/Histogram/histogramLogic.ts index 45fb7b27e393e..a3bcd68aeaca9 100644 --- a/frontend/src/scenes/insights/views/Histogram/histogramLogic.ts +++ b/frontend/src/scenes/insights/views/Histogram/histogramLogic.ts @@ -1,19 +1,19 @@ -import { kea } from 'kea' +import { kea, path, actions, reducers } from 'kea' import { getConfig, HistogramConfig } from 'scenes/insights/views/Histogram/histogramUtils' import type { histogramLogicType } from './histogramLogicType' import { FunnelLayout } from 'lib/constants' -export const histogramLogic = kea({ - path: ['scenes', 'insights', 'Histogram', 'histogramLogic'], - actions: { +export const histogramLogic = kea([ + path(['scenes', 'insights', 'Histogram', 'histogramLogic']), + actions({ setConfig: (config: HistogramConfig) => ({ config }), - }, - reducers: { + }), + reducers({ config: [ getConfig(FunnelLayout.vertical), { setConfig: (state, { config }) => ({ ...state, ...config }), }, ], - }, -}) + }), +]) diff --git a/frontend/src/scenes/insights/views/InsightsTable/insightsTableLogic.ts b/frontend/src/scenes/insights/views/InsightsTable/insightsTableLogic.ts index af44a9361055b..a4c1c8962a0f3 100644 --- a/frontend/src/scenes/insights/views/InsightsTable/insightsTableLogic.ts +++ b/frontend/src/scenes/insights/views/InsightsTable/insightsTableLogic.ts @@ -1,28 +1,30 @@ -import { kea } from 'kea' +import { kea, props, path, actions, reducers, selectors } from 'kea' import { ChartDisplayType, FilterType } from '~/types' import type { insightsTableLogicType } from './insightsTableLogicType' import { isTrendsFilter } from 'scenes/insights/sharedUtils' export type CalcColumnState = 'total' | 'average' | 'median' -export const insightsTableLogic = kea({ - path: ['scenes', 'insights', 'InsightsTable', 'insightsTableLogic'], - props: {} as { - hasMathUniqueFilter: boolean - filters: Partial - }, - actions: { +export const insightsTableLogic = kea([ + props( + {} as { + hasMathUniqueFilter: boolean + filters: Partial + } + ), + path(['scenes', 'insights', 'InsightsTable', 'insightsTableLogic']), + actions({ setCalcColumnState: (state: CalcColumnState) => ({ state }), - }, - reducers: ({ props }) => ({ + }), + reducers(({ props }) => ({ calcColumnState: [ (props.hasMathUniqueFilter ? 'average' : 'total') as CalcColumnState, { setCalcColumnState: (_, { state }) => state, }, ], - }), - selectors: () => ({ + })), + selectors(() => ({ // Only allow table aggregation options when the math is total volume otherwise double counting will happen when the math is set to uniques // Except when view type is Table showTotalCount: [ @@ -39,5 +41,5 @@ export const insightsTableLogic = kea({ ) }, ], - }), -}) + })), +]) diff --git a/frontend/src/scenes/insights/views/LineGraph/lineGraphLogic.ts b/frontend/src/scenes/insights/views/LineGraph/lineGraphLogic.ts index 5dcb83d958fb1..e7473601a28b7 100644 --- a/frontend/src/scenes/insights/views/LineGraph/lineGraphLogic.ts +++ b/frontend/src/scenes/insights/views/LineGraph/lineGraphLogic.ts @@ -1,13 +1,13 @@ -import { kea } from 'kea' +import { kea, path, selectors } from 'kea' import { TooltipItem } from 'chart.js' import { GraphDataset } from '~/types' import { SeriesDatum } from 'scenes/insights/InsightTooltip/insightTooltipUtils' import type { lineGraphLogicType } from './lineGraphLogicType' // TODO: Eventually we should move all state from LineGraph into this logic -export const lineGraphLogic = kea({ - path: ['scenes', 'insights', 'LineGraph', 'lineGraphLogic'], - selectors: { +export const lineGraphLogic = kea([ + path(['scenes', 'insights', 'LineGraph', 'lineGraphLogic']), + selectors({ createTooltipData: [ () => [], () => @@ -51,5 +51,5 @@ export const lineGraphLogic = kea({ })) }, ], - }, -}) + }), +]) diff --git a/frontend/src/scenes/instance/SystemStatus/staffUsersLogic.ts b/frontend/src/scenes/instance/SystemStatus/staffUsersLogic.ts index 84f601661871b..1f59748f42345 100644 --- a/frontend/src/scenes/instance/SystemStatus/staffUsersLogic.ts +++ b/frontend/src/scenes/instance/SystemStatus/staffUsersLogic.ts @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, path, connect, actions, reducers, selectors, events } from 'kea' import { router } from 'kea-router' import api from 'lib/api' import { urls } from 'scenes/urls' @@ -6,26 +7,18 @@ import { userLogic } from 'scenes/userLogic' import { UserType } from '~/types' import type { staffUsersLogicType } from './staffUsersLogicType' -export const staffUsersLogic = kea({ - path: ['scenes', 'instance', 'SystemStatus', 'staffUsersLogic'], - connect: { +export const staffUsersLogic = kea([ + path(['scenes', 'instance', 'SystemStatus', 'staffUsersLogic']), + connect({ values: [userLogic, ['user']], actions: [userLogic, ['loadUser']], - }, - actions: { + }), + actions({ setStaffUsersToBeAdded: (userUuids: string[]) => ({ userUuids }), addStaffUsers: true, deleteStaffUser: (userUuid: string) => ({ userUuid }), - }, - reducers: { - staffUsersToBeAdded: [ - [] as string[], - { - setStaffUsersToBeAdded: (_, { userUuids }) => userUuids, - }, - ], - }, - loaders: ({ actions, values }) => ({ + }), + loaders(({ actions, values }) => ({ allUsers: [ [] as UserType[], { @@ -64,12 +57,20 @@ export const staffUsersLogic = kea({ }, }, ], + })), + reducers({ + staffUsersToBeAdded: [ + [] as string[], + { + setStaffUsersToBeAdded: (_, { userUuids }) => userUuids, + }, + ], }), - selectors: { + selectors({ staffUsers: [(s) => [s.allUsers], (allUsers): UserType[] => allUsers.filter((user) => user.is_staff)], nonStaffUsers: [(s) => [s.allUsers], (allUsers): UserType[] => allUsers.filter((user) => !user.is_staff)], - }, - events: ({ actions }) => ({ - afterMount: [actions.loadAllUsers], }), -}) + events(({ actions }) => ({ + afterMount: [actions.loadAllUsers], + })), +]) diff --git a/frontend/src/scenes/instance/SystemStatus/systemStatusLogic.ts b/frontend/src/scenes/instance/SystemStatus/systemStatusLogic.ts index b4a6ccc2f365d..e17cb621a35cb 100644 --- a/frontend/src/scenes/instance/SystemStatus/systemStatusLogic.ts +++ b/frontend/src/scenes/instance/SystemStatus/systemStatusLogic.ts @@ -1,5 +1,7 @@ +import { actionToUrl, urlToAction } from 'kea-router' +import { loaders } from 'kea-loaders' import api from 'lib/api' -import { kea } from 'kea' +import { kea, path, actions, reducers, selectors, listeners, events } from 'kea' import type { systemStatusLogicType } from './systemStatusLogicType' import { userLogic } from 'scenes/userLogic' import { SystemStatus, SystemStatusRow, SystemStatusQueriesResult, InstanceSetting } from '~/types' @@ -49,9 +51,9 @@ const EDITABLE_INSTANCE_SETTINGS = [ ] // Note: This logic does some heavy calculations - avoid connecting it outside of system status pages! -export const systemStatusLogic = kea({ - path: ['scenes', 'instance', 'SystemStatus', 'systemStatusLogic'], - actions: { +export const systemStatusLogic = kea([ + path(['scenes', 'instance', 'SystemStatus', 'systemStatusLogic']), + actions({ setTab: (tab: InstanceStatusTabName) => ({ tab }), setOpenSections: (sections: string[]) => ({ sections }), setInstanceConfigMode: (mode: ConfigMode) => ({ mode }), @@ -60,8 +62,8 @@ export const systemStatusLogic = kea({ saveInstanceConfig: true, setUpdatedInstanceConfigCount: (count: number | null) => ({ count }), increaseUpdatedInstanceConfigCount: true, - }, - loaders: () => ({ + }), + loaders(() => ({ systemStatus: [ null as SystemStatus | null, { @@ -93,8 +95,8 @@ export const systemStatusLogic = kea({ loadQueries: async () => (await api.get('api/instance_status/queries')).results, }, ], - }), - reducers: { + })), + reducers({ tab: [ 'overview' as InstanceStatusTabName, { @@ -143,9 +145,8 @@ export const systemStatusLogic = kea({ increaseUpdatedInstanceConfigCount: (state) => (state ?? 0) + 1, }, ], - }, - - selectors: () => ({ + }), + selectors(() => ({ overview: [ (s) => [s.systemStatus], (status: SystemStatus | null): SystemStatusRow[] => (status ? status.overview : []), @@ -155,9 +156,8 @@ export const systemStatusLogic = kea({ (instanceSettings): InstanceSetting[] => instanceSettings.filter((item) => item.editable && EDITABLE_INSTANCE_SETTINGS.includes(item.key)), ], - }), - - listeners: ({ actions, values }) => ({ + })), + listeners(({ actions, values }) => ({ setTab: ({ tab }: { tab: InstanceStatusTabName }) => { if (tab === 'metrics') { actions.loadQueries() @@ -193,19 +193,11 @@ export const systemStatusLogic = kea({ lemonToast.success('Instance settings updated') } }, - }), - - events: ({ actions }) => ({ - afterMount: () => { - actions.loadSystemStatus() - }, - }), - - actionToUrl: ({ values }) => ({ + })), + actionToUrl(({ values }) => ({ setTab: () => '/instance/' + (values.tab === 'overview' ? 'status' : values.tab), - }), - - urlToAction: ({ actions, values }) => ({ + })), + urlToAction(({ actions, values }) => ({ '/instance(/:tab)': ({ tab }: { tab?: InstanceStatusTabName }) => { const currentTab = tab && ['metrics', 'settings', 'staff_users', 'kafka_inspector'].includes(tab) ? tab : 'overview' @@ -213,5 +205,10 @@ export const systemStatusLogic = kea({ actions.setTab(currentTab) } }, - }), -}) + })), + events(({ actions }) => ({ + afterMount: () => { + actions.loadSystemStatus() + }, + })), +]) diff --git a/frontend/src/scenes/onboarding/onboardingLogic.tsx b/frontend/src/scenes/onboarding/onboardingLogic.tsx index dfaff04773bf8..c484105b60e7a 100644 --- a/frontend/src/scenes/onboarding/onboardingLogic.tsx +++ b/frontend/src/scenes/onboarding/onboardingLogic.tsx @@ -1,10 +1,10 @@ -import { kea } from 'kea' +import { kea, props, path, connect, actions, reducers, selectors, listeners } from 'kea' import { BillingProductV2Type, ProductKey } from '~/types' import { urls } from 'scenes/urls' import { billingLogic } from 'scenes/billing/billingLogic' import { teamLogic } from 'scenes/teamLogic' -import { combineUrl, router } from 'kea-router' +import { combineUrl, router, actionToUrl, urlToAction } from 'kea-router' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' import type { onboardingLogicType } from './onboardingLogicType' @@ -40,14 +40,14 @@ export const getProductUri = (productKey: ProductKey): string => { } } -export const onboardingLogic = kea({ - props: {} as OnboardingLogicProps, - path: ['scenes', 'onboarding', 'onboardingLogic'], - connect: { +export const onboardingLogic = kea([ + props({} as OnboardingLogicProps), + path(['scenes', 'onboarding', 'onboardingLogic']), + connect({ values: [billingLogic, ['billing'], teamLogic, ['currentTeam']], actions: [billingLogic, ['loadBillingSuccess'], teamLogic, ['updateCurrentTeamSuccess']], - }, - actions: { + }), + actions({ setProduct: (product: BillingProductV2Type | null) => ({ product }), setProductKey: (productKey: string | null) => ({ productKey }), completeOnboarding: (nextProductKey?: string) => ({ nextProductKey }), @@ -57,8 +57,8 @@ export const onboardingLogic = kea({ goToNextStep: true, goToPreviousStep: true, resetStepKey: true, - }, - reducers: () => ({ + }), + reducers(() => ({ productKey: [ null as string | null, { @@ -97,8 +97,8 @@ export const onboardingLogic = kea({ setSubscribedDuringOnboarding: (_, { subscribedDuringOnboarding }) => subscribedDuringOnboarding, }, ], - }), - selectors: { + })), + selectors({ totalOnboardingSteps: [ (s) => [s.allOnboardingSteps], (allOnboardingSteps: AllOnboardingSteps) => allOnboardingSteps.length, @@ -150,8 +150,8 @@ export const onboardingLogic = kea({ (stepKey && allOnboardingSteps.length > 0 && !currentOnboardingStep) || (!stepKey && allOnboardingSteps.length > 0), ], - }, - listeners: ({ actions, values }) => ({ + }), + listeners(({ actions, values }) => ({ loadBillingSuccess: () => { actions.setProduct(values.billing?.products.find((p) => p.type === values.productKey) || null) }, @@ -207,8 +207,8 @@ export const onboardingLogic = kea({ resetStepKey: () => { actions.setStepKey(values.allOnboardingSteps[0].props.stepKey) }, - }), - actionToUrl: ({ values }) => ({ + })), + actionToUrl(({ values }) => ({ setStepKey: ({ stepKey }) => { if (stepKey) { return [`/onboarding/${values.productKey}`, { step: stepKey }] @@ -243,8 +243,8 @@ export const onboardingLogic = kea({ return [values.onCompleteOnboardingRedirectUrl] } }, - }), - urlToAction: ({ actions, values }) => ({ + })), + urlToAction(({ actions, values }) => ({ '/onboarding/:productKey': ({ productKey }, { success, upgraded, step }) => { if (!productKey) { window.location.href = urls.default() @@ -262,5 +262,5 @@ export const onboardingLogic = kea({ actions.resetStepKey() } }, - }), -}) + })), +]) diff --git a/frontend/src/scenes/onboarding/sdks/sdksLogic.tsx b/frontend/src/scenes/onboarding/sdks/sdksLogic.tsx index 80cc121d8bd7d..83203a1ef7bc4 100644 --- a/frontend/src/scenes/onboarding/sdks/sdksLogic.tsx +++ b/frontend/src/scenes/onboarding/sdks/sdksLogic.tsx @@ -1,4 +1,4 @@ -import { kea } from 'kea' +import { kea, path, connect, actions, reducers, selectors, listeners, events } from 'kea' import type { sdksLogicType } from './sdksLogicType' import { SDK, SDKInstructionsMap } from '~/types' @@ -27,12 +27,12 @@ const getSourceOptions = (availableSDKInstructionsMap: SDKInstructionsMap): Lemo return selectOptions } -export const sdksLogic = kea({ - path: ['scenes', 'onboarding', 'sdks', 'sdksLogic'], - connect: { +export const sdksLogic = kea([ + path(['scenes', 'onboarding', 'sdks', 'sdksLogic']), + connect({ values: [onboardingLogic, ['productKey']], - }, - actions: { + }), + actions({ setSourceFilter: (sourceFilter: string | null) => ({ sourceFilter }), filterSDKs: true, setSDKs: (sdks: SDK[]) => ({ sdks }), @@ -40,9 +40,8 @@ export const sdksLogic = kea({ setSourceOptions: (sourceOptions: LemonSelectOptions) => ({ sourceOptions }), resetSDKs: true, setAvailableSDKInstructionsMap: (sdkInstructionMap: SDKInstructionsMap) => ({ sdkInstructionMap }), - }, - - reducers: { + }), + reducers({ sourceFilter: [ null as string | null, { @@ -73,8 +72,8 @@ export const sdksLogic = kea({ setAvailableSDKInstructionsMap: (_, { sdkInstructionMap }) => sdkInstructionMap, }, ], - }, - selectors: { + }), + selectors({ showSourceOptionsSelect: [ (selectors) => [selectors.sourceOptions, selectors.availableSDKInstructionsMap], (sourceOptions: LemonSelectOptions, availableSDKInstructionsMap: SDKInstructionsMap): boolean => { @@ -83,8 +82,8 @@ export const sdksLogic = kea({ return Object.keys(availableSDKInstructionsMap).length > 5 && sourceOptions.length > 2 }, ], - }, - listeners: ({ actions, values }) => ({ + }), + listeners(({ actions, values }) => ({ filterSDKs: () => { const filteredSDks: SDK[] = allSDKs .filter((sdk) => { @@ -119,10 +118,10 @@ export const sdksLogic = kea({ actions.setSourceFilter(null) actions.setSourceOptions(getSourceOptions(values.availableSDKInstructionsMap)) }, - }), - events: ({ actions }) => ({ + })), + events(({ actions }) => ({ afterMount: () => { actions.filterSDKs() }, - }), -}) + })), +]) diff --git a/frontend/src/scenes/organization/Settings/index.tsx b/frontend/src/scenes/organization/Settings/index.tsx index 80b8d81a1f5b7..f37152a173da0 100644 --- a/frontend/src/scenes/organization/Settings/index.tsx +++ b/frontend/src/scenes/organization/Settings/index.tsx @@ -1,9 +1,10 @@ +import { actionToUrl, urlToAction } from 'kea-router' import { useState } from 'react' import { PageHeader } from 'lib/components/PageHeader' import { Invites } from './Invites' import { Members } from './Members' import { organizationLogic } from '../../organizationLogic' -import { kea, useActions, useValues } from 'kea' +import { kea, useActions, useValues, path, actions, reducers } from 'kea' import { DangerZone } from './DangerZone' import { RestrictedArea, RestrictedComponentProps } from 'lib/components/RestrictedArea' import { FEATURE_FLAGS, OrganizationMembershipLevel } from 'lib/constants' @@ -82,30 +83,30 @@ function EmailPreferences({ isRestricted }: RestrictedComponentProps): JSX.Eleme ) } -const organizationSettingsTabsLogic = kea({ - path: ['scenes', 'organization', 'Settings', 'index'], - actions: { +const organizationSettingsTabsLogic = kea([ + path(['scenes', 'organization', 'Settings', 'index']), + actions({ setTab: (tab: OrganizationSettingsTabs) => ({ tab }), - }, - reducers: { + }), + reducers({ tab: [ OrganizationSettingsTabs.GENERAL as OrganizationSettingsTabs, { setTab: (_, { tab }) => tab, }, ], - }, - actionToUrl: () => ({ - setTab: ({ tab }) => `${urls.organizationSettings()}?tab=${tab}`, }), - urlToAction: ({ values, actions }) => ({ + actionToUrl(() => ({ + setTab: ({ tab }) => `${urls.organizationSettings()}?tab=${tab}`, + })), + urlToAction(({ values, actions }) => ({ [urls.organizationSettings()]: (_, searchParams) => { if (searchParams['tab'] && values.tab !== searchParams['tab']) { actions.setTab(searchParams['tab']) } }, - }), -}) + })), +]) export function OrganizationSettings(): JSX.Element { const { user } = useValues(userLogic) diff --git a/frontend/src/scenes/organization/Settings/inviteLogic.ts b/frontend/src/scenes/organization/Settings/inviteLogic.ts index cfb7811a457e6..8ebc6a1a72ef3 100644 --- a/frontend/src/scenes/organization/Settings/inviteLogic.ts +++ b/frontend/src/scenes/organization/Settings/inviteLogic.ts @@ -1,11 +1,12 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, path, connect, actions, reducers, selectors, listeners, events } from 'kea' import { OrganizationInviteType } from '~/types' import api from 'lib/api' import { organizationLogic } from 'scenes/organizationLogic' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' import type { inviteLogicType } from './inviteLogicType' import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic' -import { router } from 'kea-router' +import { router, urlToAction } from 'kea-router' import { lemonToast } from 'lib/lemon-ui/lemonToast' /** State of a single invite row (with input data) in bulk invite creation. */ @@ -18,9 +19,13 @@ export interface InviteRowState { const EMPTY_INVITE: InviteRowState = { target_email: '', first_name: '', isValid: true } -export const inviteLogic = kea({ - path: ['scenes', 'organization', 'Settings', 'inviteLogic'], - actions: { +export const inviteLogic = kea([ + path(['scenes', 'organization', 'Settings', 'inviteLogic']), + connect({ + values: [preflightLogic, ['preflight']], + actions: [router, ['locationChanged']], + }), + actions({ showInviteModal: true, hideInviteModal: true, updateInviteAtIndex: (payload, index: number) => ({ payload, index }), @@ -28,12 +33,45 @@ export const inviteLogic = kea({ updateMessage: (message: string) => ({ message }), appendInviteRow: true, resetInviteRows: true, - }, - connect: { - values: [preflightLogic, ['preflight']], - actions: [router, ['locationChanged']], - }, - reducers: () => ({ + }), + loaders(({ values }) => ({ + invitedTeamMembersInternal: [ + [] as OrganizationInviteType[], + { + inviteTeamMembers: async () => { + if (!values.canSubmit) { + return { invites: [] } + } + + const payload: Pick[] = + values.invitesToSend.filter((invite) => invite.target_email) + eventUsageLogic.actions.reportBulkInviteAttempted( + payload.length, + payload.filter((invite) => !!invite.first_name).length + ) + if (values.message) { + payload.forEach((payload) => (payload.message = values.message)) + } + return await api.create('api/organizations/@current/invites/bulk/', payload) + }, + }, + ], + invites: [ + [] as OrganizationInviteType[], + { + loadInvites: async () => { + return (await api.get('api/organizations/@current/invites/')).results + }, + deleteInvite: async (invite: OrganizationInviteType) => { + await api.delete(`api/organizations/@current/invites/${invite.id}/`) + preflightLogic.actions.loadPreflight() // Make sure licensed_users_available is updated + lemonToast.success(`Invite for ${invite.target_email} has been canceled`) + return values.invites.filter((thisInvite) => thisInvite.id !== invite.id) + }, + }, + ], + })), + reducers(() => ({ isInviteModalShown: [ false, { @@ -66,53 +104,16 @@ export const inviteLogic = kea({ updateMessage: (_, { message }) => message, }, ], - }), - selectors: { + })), + selectors({ canSubmit: [ (selectors) => [selectors.invitesToSend], (invites: InviteRowState[]) => invites.filter(({ target_email }) => !!target_email).length > 0 && invites.filter(({ isValid }) => !isValid).length == 0, ], - }, - loaders: ({ values }) => ({ - invitedTeamMembersInternal: [ - [] as OrganizationInviteType[], - { - inviteTeamMembers: async () => { - if (!values.canSubmit) { - return { invites: [] } - } - - const payload: Pick[] = - values.invitesToSend.filter((invite) => invite.target_email) - eventUsageLogic.actions.reportBulkInviteAttempted( - payload.length, - payload.filter((invite) => !!invite.first_name).length - ) - if (values.message) { - payload.forEach((payload) => (payload.message = values.message)) - } - return await api.create('api/organizations/@current/invites/bulk/', payload) - }, - }, - ], - invites: [ - [] as OrganizationInviteType[], - { - loadInvites: async () => { - return (await api.get('api/organizations/@current/invites/')).results - }, - deleteInvite: async (invite: OrganizationInviteType) => { - await api.delete(`api/organizations/@current/invites/${invite.id}/`) - preflightLogic.actions.loadPreflight() // Make sure licensed_users_available is updated - lemonToast.success(`Invite for ${invite.target_email} has been canceled`) - return values.invites.filter((thisInvite) => thisInvite.id !== invite.id) - }, - }, - ], }), - listeners: ({ values, actions }) => ({ + listeners(({ values, actions }) => ({ inviteTeamMembersSuccess: (): void => { const inviteCount = values.invitedTeamMembersInternal.length if (values.preflight?.email_service_available) { @@ -128,15 +129,15 @@ export const inviteLogic = kea({ actions.hideInviteModal() } }, - }), - events: ({ actions }) => ({ - afterMount: [actions.loadInvites], - }), - urlToAction: ({ actions }) => ({ + })), + urlToAction(({ actions }) => ({ '*': (_, searchParams) => { if (searchParams.invite_modal) { actions.showInviteModal() } }, - }), -}) + })), + events(({ actions }) => ({ + afterMount: [actions.loadInvites], + })), +]) diff --git a/frontend/src/scenes/organization/Settings/invitesLogic.tsx b/frontend/src/scenes/organization/Settings/invitesLogic.tsx index f935bb9c68164..1b119f9a52da9 100644 --- a/frontend/src/scenes/organization/Settings/invitesLogic.tsx +++ b/frontend/src/scenes/organization/Settings/invitesLogic.tsx @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, path, listeners, events } from 'kea' import api from 'lib/api' import { OrganizationInviteType } from '~/types' import type { invitesLogicType } from './invitesLogicType' @@ -6,9 +7,9 @@ import { eventUsageLogic } from 'lib/utils/eventUsageLogic' import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic' import { lemonToast } from 'lib/lemon-ui/lemonToast' -export const invitesLogic = kea({ - path: ['scenes', 'organization', 'Settings', 'invitesLogic'], - loaders: ({ values }) => ({ +export const invitesLogic = kea([ + path(['scenes', 'organization', 'Settings', 'invitesLogic']), + loaders(({ values }) => ({ invites: { __default: [] as OrganizationInviteType[], loadInvites: async () => { @@ -41,8 +42,8 @@ export const invitesLogic = kea({ return values.invites.filter((thisInvite) => thisInvite.id !== invite.id) }, }, - }), - listeners: { + })), + listeners({ createInviteSuccess: async () => { const nameProvided = false // TODO: Change when adding support for names on invites eventUsageLogic.actions.reportInviteAttempted( @@ -50,8 +51,8 @@ export const invitesLogic = kea({ !!preflightLogic.values.preflight?.email_service_available ) }, - }, - events: ({ actions }) => ({ - afterMount: actions.loadInvites, }), -}) + events(({ actions }) => ({ + afterMount: actions.loadInvites, + })), +]) diff --git a/frontend/src/scenes/organization/Settings/membersLogic.tsx b/frontend/src/scenes/organization/Settings/membersLogic.tsx index eb82867e3d3d8..ebf874aaa5063 100644 --- a/frontend/src/scenes/organization/Settings/membersLogic.tsx +++ b/frontend/src/scenes/organization/Settings/membersLogic.tsx @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, path, connect, actions, reducers, selectors, listeners, events } from 'kea' import api from 'lib/api' import type { membersLogicType } from './membersLogicType' import { OrganizationMembershipLevel } from 'lib/constants' @@ -9,26 +10,22 @@ import { membershipLevelToName } from 'lib/utils/permissioning' import { lemonToast } from 'lib/lemon-ui/lemonToast' import Fuse from 'fuse.js' -// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface MembersFuse extends Fuse {} -export const membersLogic = kea({ - path: ['scenes', 'organization', 'Settings', 'membersLogic'], - connect: { +export const membersLogic = kea([ + path(['scenes', 'organization', 'Settings', 'membersLogic']), + connect({ values: [userLogic, ['user']], - }, - actions: { + }), + actions({ setSearch: (search) => ({ search }), changeMemberAccessLevel: (member: OrganizationMemberType, level: OrganizationMembershipLevel) => ({ member, level, }), postRemoveMember: (userUuid: string) => ({ userUuid }), - }, - reducers: { - search: ['', { setSearch: (_, { search }) => search }], - }, - loaders: ({ values, actions }) => ({ + }), + loaders(({ values, actions }) => ({ members: { __default: [] as OrganizationMemberType[], loadMembers: async () => { @@ -45,8 +42,11 @@ export const membersLogic = kea({ return values.members.filter((thisMember) => thisMember.user.id !== member.user.id) }, }, + })), + reducers({ + search: ['', { setSearch: (_, { search }) => search }], }), - selectors: { + selectors({ meFirstMembers: [ (s) => [s.members, s.user], (members, user) => { @@ -73,8 +73,8 @@ export const membersLogic = kea({ (members, membersFuse, search) => search ? membersFuse.search(search).map((result) => result.item) : members, ], - }, - listeners: ({ actions }) => ({ + }), + listeners(({ actions }) => ({ changeMemberAccessLevel: async ({ member, level }) => { await api.update(`api/organizations/@current/members/${member.user.uuid}/`, { level }) lemonToast.success( @@ -93,8 +93,8 @@ export const membersLogic = kea({ location.reload() } }, - }), - events: ({ actions }) => ({ + })), + events(({ actions }) => ({ afterMount: actions.loadMembers, - }), -}) + })), +]) diff --git a/frontend/src/scenes/persons/mergeSplitPersonLogic.ts b/frontend/src/scenes/persons/mergeSplitPersonLogic.ts index b695f4f36112c..3e5c4dae2417f 100644 --- a/frontend/src/scenes/persons/mergeSplitPersonLogic.ts +++ b/frontend/src/scenes/persons/mergeSplitPersonLogic.ts @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, props, key, path, connect, actions, reducers, listeners, events } from 'kea' import { router } from 'kea-router' import api from 'lib/api' import { lemonToast } from 'lib/lemon-ui/lemonToast' @@ -13,41 +14,22 @@ export interface SplitPersonLogicProps { export type PersonUuids = NonNullable[] -export const mergeSplitPersonLogic = kea({ - props: {} as SplitPersonLogicProps, - key: (props) => props.person.id ?? 'new', - path: (key) => ['scenes', 'persons', 'mergeSplitPersonLogic', key], - connect: () => ({ +export const mergeSplitPersonLogic = kea([ + props({} as SplitPersonLogicProps), + key((props) => props.person.id ?? 'new'), + path((key) => ['scenes', 'persons', 'mergeSplitPersonLogic', key]), + connect(() => ({ actions: [ personsLogic({ syncWithUrl: true }), ['setListFilters', 'loadPersons', 'setPerson', 'setSplitMergeModalShown'], ], values: [personsLogic({ syncWithUrl: true }), ['persons']], - }), - actions: { + })), + actions({ setSelectedPersonToAssignSplit: (id: string) => ({ id }), cancel: true, - }, - reducers: ({ props }) => ({ - person: [props.person, {}], - selectedPersonsToAssignSplit: [ - null as null | string, - { - setSelectedPersonToAssignSplit: (_, { id }) => id, - }, - ], - }), - listeners: ({ actions, values }) => ({ - setListFilters: () => { - actions.loadPersons() - }, - cancel: () => { - if (!values.executedLoading) { - actions.setSplitMergeModalShown(false) - } - }, }), - loaders: ({ values, actions }) => ({ + loaders(({ values, actions }) => ({ executed: [ false, { @@ -70,8 +52,27 @@ export const mergeSplitPersonLogic = kea({ }, }, ], - }), - events: ({ actions }) => ({ + })), + reducers(({ props }) => ({ + person: [props.person, {}], + selectedPersonsToAssignSplit: [ + null as null | string, + { + setSelectedPersonToAssignSplit: (_, { id }) => id, + }, + ], + })), + listeners(({ actions, values }) => ({ + setListFilters: () => { + actions.loadPersons() + }, + cancel: () => { + if (!values.executedLoading) { + actions.setSplitMergeModalShown(false) + } + }, + })), + events(({ actions }) => ({ afterMount: [actions.loadPersons], - }), -}) + })), +]) diff --git a/frontend/src/scenes/persons/personsLogic.tsx b/frontend/src/scenes/persons/personsLogic.tsx index eb1c15fd49a11..136d69f317baf 100644 --- a/frontend/src/scenes/persons/personsLogic.tsx +++ b/frontend/src/scenes/persons/personsLogic.tsx @@ -1,5 +1,6 @@ -import { kea } from 'kea' -import { decodeParams, router } from 'kea-router' +import { loaders } from 'kea-loaders' +import { kea, props, key, path, connect, actions, reducers, selectors, listeners, events } from 'kea' +import { decodeParams, router, actionToUrl, urlToAction } from 'kea-router' import api, { CountedPaginatedResponse } from 'lib/api' import type { personsLogicType } from './personsLogicType' import { @@ -31,21 +32,21 @@ export interface PersonsLogicProps { fixedProperties?: PersonPropertyFilter[] } -export const personsLogic = kea({ - props: {} as PersonsLogicProps, - key: (props) => { +export const personsLogic = kea([ + props({} as PersonsLogicProps), + key((props) => { if (props.fixedProperties) { return JSON.stringify(props.fixedProperties) } return props.cohort ? `cohort_${props.cohort}` : 'scene' - }, - path: (key) => ['scenes', 'persons', 'personsLogic', key], - connect: () => ({ + }), + path((key) => ['scenes', 'persons', 'personsLogic', key]), + connect(() => ({ actions: [eventUsageLogic, ['reportPersonDetailViewed']], values: [teamLogic, ['currentTeam'], featureFlagLogic, ['featureFlags']], - }), - actions: { + })), + actions({ setPerson: (person: PersonType | null) => ({ person }), setPersons: (persons: PersonType[]) => ({ persons }), loadPerson: (id: string) => ({ id }), @@ -60,8 +61,106 @@ export const personsLogic = kea({ setActiveTab: (tab: PersonsTabType) => ({ tab }), setSplitMergeModalShown: (shown: boolean) => ({ shown }), setDistinctId: (distinctId: string) => ({ distinctId }), - }, - reducers: () => ({ + }), + loaders(({ values, actions, props }) => ({ + persons: [ + { next: null, previous: null, count: 0, results: [], offset: 0 } as CountedPaginatedResponse & { + offset: number + }, + { + loadPersons: async ({ url }) => { + let result: CountedPaginatedResponse & { offset: number } + if (!url) { + const newFilters: PersonListParams = { ...values.listFilters } + newFilters.properties = [ + ...(values.listFilters.properties || []), + ...values.hiddenListProperties, + ] + if (values.featureFlags[FEATURE_FLAGS.POSTHOG_3000]) { + newFilters.include_total = true // The total count is slow, but needed for infinite loading + } + if (props.cohort) { + result = { + ...(await api.get(`api/cohort/${props.cohort}/persons/?${toParams(newFilters)}`)), + offset: 0, + } + } else { + result = { ...(await api.persons.list(newFilters)), offset: 0 } + } + } else { + result = { ...(await api.get(url)), offset: parseInt(decodeParams(url).offset) } + } + return result + }, + }, + ], + person: [ + null as PersonType | null, + { + loadPerson: async ({ id }): Promise => { + if (values.featureFlags[FEATURE_FLAGS.PERSONS_HOGQL_QUERY]) { + const response = await hogqlQuery( + 'select id, groupArray(pdi.distinct_id) as distinct_ids, properties, is_identified, created_at from persons where pdi.distinct_id={distinct_id} group by id, properties, is_identified, created_at', + { distinct_id: id } + ) + const row = response?.results?.[0] + if (row) { + const person: PersonType = { + id: row[0], + uuid: row[0], + distinct_ids: row[1], + properties: JSON.parse(row[2] || '{}'), + is_identified: !!row[3], + created_at: row[4], + } + actions.reportPersonDetailViewed(person) + return person + } + } + + const response = await api.persons.list({ distinct_id: id }) + const person = response.results[0] + if (person) { + actions.reportPersonDetailViewed(person) + } + return person + }, + loadPersonUUID: async ({ uuid }): Promise => { + const response = await hogqlQuery( + 'select id, groupArray(pdi.distinct_id) as distinct_ids, properties, is_identified, created_at from persons where id={id} group by id, properties, is_identified, created_at', + { id: uuid } + ) + const row = response?.results?.[0] + if (row) { + const person: PersonType = { + id: row[0], + uuid: row[0], + distinct_ids: row[1], + properties: JSON.parse(row[2] || '{}'), + is_identified: !!row[3], + created_at: row[4], + } + actions.reportPersonDetailViewed(person) + return person + } + return null + }, + }, + ], + cohorts: [ + null as CohortType[] | null, + { + loadCohorts: async (): Promise => { + if (!values.person?.id) { + return null + } + const response = await api.get(`api/person/cohorts/?person_id=${values.person?.id}`) + return response.results + }, + }, + ], + })), + reducers(() => ({ listFilters: [ {} as PersonListParams, { @@ -126,8 +225,8 @@ export const personsLogic = kea({ setDistinctId: (_, { distinctId }) => distinctId, }, ], - }), - selectors: () => ({ + })), + selectors(() => ({ apiDocsURL: [ () => [(_, props) => props.cohort], (cohort: PersonsLogicProps['cohort']) => @@ -179,8 +278,8 @@ export const personsLogic = kea({ (featureFlags) => featureFlags[FEATURE_FLAGS.CS_DASHBOARDS], ], feedEnabled: [(s) => [s.featureFlags], (featureFlags) => !!featureFlags[FEATURE_FLAGS.PERSON_FEED_CANVAS]], - }), - listeners: ({ actions, values }) => ({ + })), + listeners(({ actions, values }) => ({ editProperty: async ({ key, newValue }) => { const person = values.person @@ -245,106 +344,8 @@ export const personsLogic = kea({ navigateToCohort: ({ cohort }) => { router.actions.push(urls.cohort(cohort.id)) }, - }), - loaders: ({ values, actions, props }) => ({ - persons: [ - { next: null, previous: null, count: 0, results: [], offset: 0 } as CountedPaginatedResponse & { - offset: number - }, - { - loadPersons: async ({ url }) => { - let result: CountedPaginatedResponse & { offset: number } - if (!url) { - const newFilters: PersonListParams = { ...values.listFilters } - newFilters.properties = [ - ...(values.listFilters.properties || []), - ...values.hiddenListProperties, - ] - if (values.featureFlags[FEATURE_FLAGS.POSTHOG_3000]) { - newFilters.include_total = true // The total count is slow, but needed for infinite loading - } - if (props.cohort) { - result = { - ...(await api.get(`api/cohort/${props.cohort}/persons/?${toParams(newFilters)}`)), - offset: 0, - } - } else { - result = { ...(await api.persons.list(newFilters)), offset: 0 } - } - } else { - result = { ...(await api.get(url)), offset: parseInt(decodeParams(url).offset) } - } - return result - }, - }, - ], - person: [ - null as PersonType | null, - { - loadPerson: async ({ id }): Promise => { - if (values.featureFlags[FEATURE_FLAGS.PERSONS_HOGQL_QUERY]) { - const response = await hogqlQuery( - 'select id, groupArray(pdi.distinct_id) as distinct_ids, properties, is_identified, created_at from persons where pdi.distinct_id={distinct_id} group by id, properties, is_identified, created_at', - { distinct_id: id } - ) - const row = response?.results?.[0] - if (row) { - const person: PersonType = { - id: row[0], - uuid: row[0], - distinct_ids: row[1], - properties: JSON.parse(row[2] || '{}'), - is_identified: !!row[3], - created_at: row[4], - } - actions.reportPersonDetailViewed(person) - return person - } - } - - const response = await api.persons.list({ distinct_id: id }) - const person = response.results[0] - if (person) { - actions.reportPersonDetailViewed(person) - } - return person - }, - loadPersonUUID: async ({ uuid }): Promise => { - const response = await hogqlQuery( - 'select id, groupArray(pdi.distinct_id) as distinct_ids, properties, is_identified, created_at from persons where id={id} group by id, properties, is_identified, created_at', - { id: uuid } - ) - const row = response?.results?.[0] - if (row) { - const person: PersonType = { - id: row[0], - uuid: row[0], - distinct_ids: row[1], - properties: JSON.parse(row[2] || '{}'), - is_identified: !!row[3], - created_at: row[4], - } - actions.reportPersonDetailViewed(person) - return person - } - return null - }, - }, - ], - cohorts: [ - null as CohortType[] | null, - { - loadCohorts: async (): Promise => { - if (!values.person?.id) { - return null - } - const response = await api.get(`api/person/cohorts/?person_id=${values.person?.id}`) - return response.results - }, - }, - ], - }), - actionToUrl: ({ values, props }) => ({ + })), + actionToUrl(({ values, props }) => ({ setListFilters: () => { if (props.syncWithUrl && router.values.location.pathname.indexOf('/persons') > -1) { return ['/persons', values.listFilters, undefined, { replace: true }] @@ -364,8 +365,8 @@ export const personsLogic = kea({ ] } }, - }), - urlToAction: ({ actions, values, props }) => ({ + })), + urlToAction(({ actions, values, props }) => ({ '/person/*': ({ _: rawPersonDistinctId }, { sessionRecordingId }, { activeTab }) => { if (props.syncWithUrl) { if (sessionRecordingId && values.activeTab !== PersonsTabType.SESSION_RECORDINGS) { @@ -408,8 +409,8 @@ export const personsLogic = kea({ } } }, - }), - events: ({ props, actions }) => ({ + })), + events(({ props, actions }) => ({ afterMount: () => { if (props.cohort && typeof props.cohort === 'number') { actions.setListFilters({ cohort: props.cohort }) @@ -421,5 +422,5 @@ export const personsLogic = kea({ actions.loadPersons() } }, - }), -}) + })), +]) diff --git a/frontend/src/scenes/plugins/plugin/pluginLogsLogic.ts b/frontend/src/scenes/plugins/plugin/pluginLogsLogic.ts index f9d3d13550bdf..437393efcb4cf 100644 --- a/frontend/src/scenes/plugins/plugin/pluginLogsLogic.ts +++ b/frontend/src/scenes/plugins/plugin/pluginLogsLogic.ts @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, props, key, path, connect, actions, reducers, selectors, listeners, events } from 'kea' import api from '~/lib/api' import { PluginLogEntry, PluginLogEntryType } from '~/types' import { teamLogic } from '../../teamLogic' @@ -11,24 +12,22 @@ export interface PluginLogsProps { export const LOGS_PORTION_LIMIT = 50 -export const pluginLogsLogic = kea({ - props: {} as PluginLogsProps, - key: ({ pluginConfigId }: PluginLogsProps) => pluginConfigId, - path: (key) => ['scenes', 'plugins', 'plugin', 'pluginLogsLogic', key], - connect: { +export const pluginLogsLogic = kea([ + props({} as PluginLogsProps), + key(({ pluginConfigId }: PluginLogsProps) => pluginConfigId), + path((key) => ['scenes', 'plugins', 'plugin', 'pluginLogsLogic', key]), + connect({ values: [teamLogic, ['currentTeamId']], - }, - - actions: { + }), + actions({ clearPluginLogsBackground: true, markLogsEnd: true, setPluginLogsTypes: (typeFilters: CheckboxValueType[]) => ({ typeFilters, }), setSearchTerm: (searchTerm: string) => ({ searchTerm }), - }, - - loaders: ({ props: { pluginConfigId }, values, actions, cache }) => ({ + }), + loaders(({ props: { pluginConfigId }, values, actions, cache }) => ({ pluginLogs: { __default: [] as PluginLogEntry[], loadPluginLogs: async () => { @@ -83,19 +82,8 @@ export const pluginLogsLogic = kea({ return [...results, ...values.pluginLogsBackground] }, }, - }), - listeners: ({ actions }) => ({ - setPluginLogsTypes: () => { - actions.loadPluginLogs() - }, - setSearchTerm: async ({ searchTerm }, breakpoint) => { - if (searchTerm) { - await breakpoint(1000) - } - actions.loadPluginLogs() - }, - }), - reducers: { + })), + reducers({ pluginLogsTypes: [ Object.values(PluginLogEntryType).filter((type) => type !== 'DEBUG'), { @@ -127,9 +115,8 @@ export const pluginLogsLogic = kea({ markLogsEnd: () => false, }, ], - }, - - selectors: ({ selectors }) => ({ + }), + selectors(({ selectors }) => ({ leadingEntry: [ () => [selectors.pluginLogs, selectors.pluginLogsBackground], (pluginLogs: PluginLogEntry[], pluginLogsBackground: PluginLogEntry[]): PluginLogEntry | null => { @@ -154,14 +141,24 @@ export const pluginLogsLogic = kea({ return null }, ], - }), - - events: ({ actions, cache }) => ({ + })), + listeners(({ actions }) => ({ + setPluginLogsTypes: () => { + actions.loadPluginLogs() + }, + setSearchTerm: async ({ searchTerm }, breakpoint) => { + if (searchTerm) { + await breakpoint(1000) + } + actions.loadPluginLogs() + }, + })), + events(({ actions, cache }) => ({ afterMount: () => { actions.loadPluginLogs() }, beforeUnmount: () => { clearInterval(cache.pollingInterval) }, - }), -}) + })), +]) diff --git a/frontend/src/scenes/products/productsLogic.tsx b/frontend/src/scenes/products/productsLogic.tsx index 5c199fd3f3fc1..48a17171bdc8a 100644 --- a/frontend/src/scenes/products/productsLogic.tsx +++ b/frontend/src/scenes/products/productsLogic.tsx @@ -1,16 +1,16 @@ -import { kea } from 'kea' +import { kea, path, actions, listeners } from 'kea' import { teamLogic } from 'scenes/teamLogic' import { ProductKey } from '~/types' import type { productsLogicType } from './productsLogicType' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' -export const productsLogic = kea({ - path: () => ['scenes', 'products', 'productsLogic'], - actions: () => ({ +export const productsLogic = kea([ + path(() => ['scenes', 'products', 'productsLogic']), + actions(() => ({ onSelectProduct: (product: ProductKey) => ({ product }), - }), - listeners: () => ({ + })), + listeners(() => ({ onSelectProduct: ({ product }) => { eventUsageLogic.actions.reportOnboardingProductSelected(product) @@ -30,5 +30,5 @@ export const productsLogic = kea({ return } }, - }), -}) + })), +]) diff --git a/frontend/src/scenes/project/Settings/groupAnalyticsConfigLogic.ts b/frontend/src/scenes/project/Settings/groupAnalyticsConfigLogic.ts index 22c9aed1ffafc..a4b41133b80ee 100644 --- a/frontend/src/scenes/project/Settings/groupAnalyticsConfigLogic.ts +++ b/frontend/src/scenes/project/Settings/groupAnalyticsConfigLogic.ts @@ -1,20 +1,20 @@ -import { kea } from 'kea' +import { kea, path, connect, actions, reducers, selectors, listeners } from 'kea' import { groupsModel } from '~/models/groupsModel' import type { groupAnalyticsConfigLogicType } from './groupAnalyticsConfigLogicType' -export const groupAnalyticsConfigLogic = kea({ - path: ['scenes', 'project', 'Settings', 'groupAnalyticsConfigLogic'], - connect: { +export const groupAnalyticsConfigLogic = kea([ + path(['scenes', 'project', 'Settings', 'groupAnalyticsConfigLogic']), + connect({ values: [groupsModel, ['groupTypes', 'groupTypesLoading']], actions: [groupsModel, ['updateGroupTypesMetadata']], - }, - actions: { + }), + actions({ setSingular: (groupTypeIndex: number, value: string) => ({ groupTypeIndex, value }), setPlural: (groupTypeIndex: number, value: string) => ({ groupTypeIndex, value }), reset: true, save: true, - }, - reducers: { + }), + reducers({ singularChanges: [ {} as Record, { @@ -31,15 +31,15 @@ export const groupAnalyticsConfigLogic = kea({ updateGroupTypesMetadataSuccess: () => ({}), }, ], - }, - selectors: { + }), + selectors({ hasChanges: [ (s) => [s.singularChanges, s.pluralChanges], (singularChanges, pluralChanges) => Object.keys(singularChanges).length > 0 || Object.keys(pluralChanges).length > 0, ], - }, - listeners: ({ values, actions }) => ({ + }), + listeners(({ values, actions }) => ({ save: async () => { const { groupTypes, singularChanges, pluralChanges } = values const payload = groupTypes.map((groupType) => { @@ -55,5 +55,5 @@ export const groupAnalyticsConfigLogic = kea({ actions.updateGroupTypesMetadata(payload) }, - }), -}) + })), +]) diff --git a/frontend/src/scenes/project/Settings/webhookIntegrationLogic.ts b/frontend/src/scenes/project/Settings/webhookIntegrationLogic.ts index c08d74ca651bf..77cf7e1a69180 100644 --- a/frontend/src/scenes/project/Settings/webhookIntegrationLogic.ts +++ b/frontend/src/scenes/project/Settings/webhookIntegrationLogic.ts @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, path, selectors, listeners } from 'kea' import api from 'lib/api' import { lemonToast } from 'lib/lemon-ui/lemonToast' import { capitalizeFirstLetter } from 'lib/utils' @@ -10,9 +11,9 @@ function adjustDiscordWebhook(webhookUrl: string): string { return webhookUrl.replace(/\/*(?:posthog|slack)?\/?$/, '/slack') } -export const webhookIntegrationLogic = kea({ - path: ['scenes', 'project', 'Settings', 'webhookIntegrationLogic'], - loaders: ({ actions }) => ({ +export const webhookIntegrationLogic = kea([ + path(['scenes', 'project', 'Settings', 'webhookIntegrationLogic']), + loaders(({ actions }) => ({ testedWebhook: [ null as string | null, { @@ -46,8 +47,14 @@ export const webhookIntegrationLogic = kea({ }, }, ], + })), + selectors({ + loading: [ + (s) => [s.testedWebhookLoading, teamLogic.selectors.currentTeamLoading], + (testedWebhookLoading: boolean, currentTeamLoading: boolean) => testedWebhookLoading || currentTeamLoading, + ], }), - listeners: () => ({ + listeners(() => ({ testWebhookSuccess: async ({ testedWebhook }) => { if (testedWebhook) { teamLogic.actions.updateCurrentTeam({ slack_incoming_webhook: testedWebhook }) @@ -56,11 +63,5 @@ export const webhookIntegrationLogic = kea({ testWebhookFailure: ({ error }) => { lemonToast.error(capitalizeFirstLetter(error)) }, - }), - selectors: { - loading: [ - (s) => [s.testedWebhookLoading, teamLogic.selectors.currentTeamLoading], - (testedWebhookLoading: boolean, currentTeamLoading: boolean) => testedWebhookLoading || currentTeamLoading, - ], - }, -}) + })), +]) diff --git a/frontend/src/scenes/retention/retentionLineGraphLogic.ts b/frontend/src/scenes/retention/retentionLineGraphLogic.ts index 2926ada9e6255..127ff04440385 100644 --- a/frontend/src/scenes/retention/retentionLineGraphLogic.ts +++ b/frontend/src/scenes/retention/retentionLineGraphLogic.ts @@ -1,5 +1,5 @@ import { dayjs, QUnitType } from 'lib/dayjs' -import { kea } from 'kea' +import { kea, props, key, path, connect, selectors } from 'kea' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' import { RetentionTrendPayload } from 'scenes/retention/types' import { InsightLogicProps, RetentionPeriod } from '~/types' @@ -12,20 +12,19 @@ import type { retentionLineGraphLogicType } from './retentionLineGraphLogicType' const DEFAULT_RETENTION_LOGIC_KEY = 'default_retention_key' -export const retentionLineGraphLogic = kea({ - props: {} as InsightLogicProps, - key: keyForInsightLogicProps(DEFAULT_RETENTION_LOGIC_KEY), - path: (key) => ['scenes', 'retention', 'retentionLineGraphLogic', key], - connect: (props: InsightLogicProps) => ({ +export const retentionLineGraphLogic = kea([ + props({} as InsightLogicProps), + key(keyForInsightLogicProps(DEFAULT_RETENTION_LOGIC_KEY)), + path((key) => ['scenes', 'retention', 'retentionLineGraphLogic', key]), + connect((props: InsightLogicProps) => ({ values: [ insightVizDataLogic(props), ['querySource', 'dateRange', 'retentionFilter'], retentionLogic(props), ['results'], ], - }), - - selectors: { + })), + selectors({ trendSeries: [ (s) => [s.results, s.retentionFilter], (results, retentionFilter): RetentionTrendPayload[] => { @@ -121,5 +120,5 @@ export const retentionLineGraphLogic = kea({ return querySource?.aggregation_group_type_index ?? 'people' }, ], - }, -}) + }), +]) diff --git a/frontend/src/scenes/retention/retentionLogic.ts b/frontend/src/scenes/retention/retentionLogic.ts index 75fd4ff8f389c..bf97e36dfdbb6 100644 --- a/frontend/src/scenes/retention/retentionLogic.ts +++ b/frontend/src/scenes/retention/retentionLogic.ts @@ -1,4 +1,4 @@ -import { kea } from 'kea' +import { kea, props, key, path, connect, selectors } from 'kea' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' import { RetentionTablePayload } from 'scenes/retention/types' @@ -9,19 +9,19 @@ import type { retentionLogicType } from './retentionLogicType' const DEFAULT_RETENTION_LOGIC_KEY = 'default_retention_key' -export const retentionLogic = kea({ - props: {} as InsightLogicProps, - key: keyForInsightLogicProps(DEFAULT_RETENTION_LOGIC_KEY), - path: (key) => ['scenes', 'retention', 'retentionLogic', key], - connect: (props: InsightLogicProps) => ({ +export const retentionLogic = kea([ + props({} as InsightLogicProps), + key(keyForInsightLogicProps(DEFAULT_RETENTION_LOGIC_KEY)), + path((key) => ['scenes', 'retention', 'retentionLogic', key]), + connect((props: InsightLogicProps) => ({ values: [insightVizDataLogic(props), ['insightQuery', 'insightData', 'querySource']], - }), - selectors: { + })), + selectors({ results: [ (s) => [s.insightQuery, s.insightData], (insightQuery, insightData): RetentionTablePayload[] => { return isRetentionQuery(insightQuery) ? insightData?.result ?? [] : [] }, ], - }, -}) + }), +]) diff --git a/frontend/src/scenes/retention/retentionModalLogic.ts b/frontend/src/scenes/retention/retentionModalLogic.ts index 5301b0e52e965..ebd464a94f4b0 100644 --- a/frontend/src/scenes/retention/retentionModalLogic.ts +++ b/frontend/src/scenes/retention/retentionModalLogic.ts @@ -1,4 +1,4 @@ -import { kea } from 'kea' +import { kea, props, key, path, connect, actions, reducers, selectors, listeners } from 'kea' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' import { Noun, groupsModel } from '~/models/groupsModel' import { InsightLogicProps } from '~/types' @@ -10,19 +10,19 @@ import type { retentionModalLogicType } from './retentionModalLogicType' const DEFAULT_RETENTION_LOGIC_KEY = 'default_retention_key' -export const retentionModalLogic = kea({ - props: {} as InsightLogicProps, - key: keyForInsightLogicProps(DEFAULT_RETENTION_LOGIC_KEY), - path: (key) => ['scenes', 'retention', 'retentionModalLogic', key], - connect: (props: InsightLogicProps) => ({ +export const retentionModalLogic = kea([ + props({} as InsightLogicProps), + key(keyForInsightLogicProps(DEFAULT_RETENTION_LOGIC_KEY)), + path((key) => ['scenes', 'retention', 'retentionModalLogic', key]), + connect((props: InsightLogicProps) => ({ values: [insightVizDataLogic(props), ['querySource'], groupsModel, ['aggregationLabel']], actions: [retentionPeopleLogic(props), ['loadPeople']], - }), - actions: () => ({ + })), + actions(() => ({ openModal: (rowIndex: number) => ({ rowIndex }), closeModal: true, - }), - reducers: { + })), + reducers({ selectedRow: [ null as number | null, { @@ -30,8 +30,8 @@ export const retentionModalLogic = kea({ closeModal: () => null, }, ], - }, - selectors: { + }), + selectors({ aggregationTargetLabel: [ (s) => [s.querySource, s.aggregationLabel], (querySource, aggregationLabel): Noun => { @@ -39,10 +39,10 @@ export const retentionModalLogic = kea({ return aggregationLabel(aggregation_group_type_index) }, ], - }, - listeners: ({ actions }) => ({ + }), + listeners(({ actions }) => ({ openModal: ({ rowIndex }) => { actions.loadPeople(rowIndex) }, - }), -}) + })), +]) diff --git a/frontend/src/scenes/retention/retentionPeopleLogic.ts b/frontend/src/scenes/retention/retentionPeopleLogic.ts index 886795974a813..3268a44c228fd 100644 --- a/frontend/src/scenes/retention/retentionPeopleLogic.ts +++ b/frontend/src/scenes/retention/retentionPeopleLogic.ts @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, props, key, path, connect, actions, reducers, selectors, listeners } from 'kea' import api from 'lib/api' import { toParams } from 'lib/utils' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' @@ -12,23 +13,20 @@ import type { retentionPeopleLogicType } from './retentionPeopleLogicType' const DEFAULT_RETENTION_LOGIC_KEY = 'default_retention_key' -export const retentionPeopleLogic = kea({ - props: {} as InsightLogicProps, - key: keyForInsightLogicProps(DEFAULT_RETENTION_LOGIC_KEY), - path: (key) => ['scenes', 'retention', 'retentionPeopleLogic', key], - connect: (props: InsightLogicProps) => ({ +export const retentionPeopleLogic = kea([ + props({} as InsightLogicProps), + key(keyForInsightLogicProps(DEFAULT_RETENTION_LOGIC_KEY)), + path((key) => ['scenes', 'retention', 'retentionPeopleLogic', key]), + connect((props: InsightLogicProps) => ({ values: [insightVizDataLogic(props), ['querySource']], actions: [insightVizDataLogic(props), ['loadDataSuccess']], - }), - actions: () => ({ + })), + actions(() => ({ clearPeople: true, loadMorePeople: true, loadMorePeopleSuccess: (payload: RetentionTablePeoplePayload) => ({ payload }), - }), - selectors: () => ({ - apiFilters: [(s) => [s.querySource], (querySource) => (querySource ? queryNodeToFilter(querySource) : {})], - }), - loaders: ({ values }) => ({ + })), + loaders(({ values }) => ({ people: { __default: {} as RetentionTablePeoplePayload, loadPeople: async (rowIndex: number) => { @@ -36,8 +34,8 @@ export const retentionPeopleLogic = kea({ return (await api.get(`api/person/retention/?${urlParams}`)) as RetentionTablePeoplePayload }, }, - }), - reducers: { + })), + reducers({ people: { clearPeople: () => ({}), loadPeople: () => ({}), @@ -50,8 +48,11 @@ export const retentionPeopleLogic = kea({ loadMorePeopleSuccess: () => false, }, ], - }, - listeners: ({ actions, values }) => ({ + }), + selectors(() => ({ + apiFilters: [(s) => [s.querySource], (querySource) => (querySource ? queryNodeToFilter(querySource) : {})], + })), + listeners(({ actions, values }) => ({ loadDataSuccess: () => { // clear people when changing the insight filters actions.clearPeople() @@ -67,5 +68,5 @@ export const retentionPeopleLogic = kea({ actions.loadMorePeopleSuccess(newPayload) } }, - }), -}) + })), +]) diff --git a/frontend/src/scenes/retention/retentionTableLogic.ts b/frontend/src/scenes/retention/retentionTableLogic.ts index 13d512f34183f..52e442b0d125e 100644 --- a/frontend/src/scenes/retention/retentionTableLogic.ts +++ b/frontend/src/scenes/retention/retentionTableLogic.ts @@ -1,5 +1,5 @@ import { dayjs } from 'lib/dayjs' -import { kea } from 'kea' +import { kea, props, key, path, connect, selectors } from 'kea' import { range } from 'lib/utils' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' import { InsightLogicProps } from '~/types' @@ -29,19 +29,19 @@ const periodIsLatest = (date_to: string | null, period: string | null): boolean } } -export const retentionTableLogic = kea({ - props: {} as InsightLogicProps, - key: keyForInsightLogicProps(DEFAULT_RETENTION_LOGIC_KEY), - path: (key) => ['scenes', 'retention', 'retentionTableLogic', key], - connect: (props: InsightLogicProps) => ({ +export const retentionTableLogic = kea([ + props({} as InsightLogicProps), + key(keyForInsightLogicProps(DEFAULT_RETENTION_LOGIC_KEY)), + path((key) => ['scenes', 'retention', 'retentionTableLogic', key]), + connect((props: InsightLogicProps) => ({ values: [ insightVizDataLogic(props), ['dateRange', 'retentionFilter', 'breakdown'], retentionLogic(props), ['results'], ], - }), - selectors: { + })), + selectors({ isLatestPeriod: [ (s) => [s.dateRange, s.retentionFilter], (dateRange, retentionFilter) => periodIsLatest(dateRange?.date_to || null, retentionFilter?.period || null), @@ -91,5 +91,5 @@ export const retentionTableLogic = kea({ ]) }, ], - }, -}) + }), +]) diff --git a/frontend/src/scenes/saved-insights/savedInsightsLogic.ts b/frontend/src/scenes/saved-insights/savedInsightsLogic.ts index 4f2bf792c7f9c..c1bb0bfdb91a5 100644 --- a/frontend/src/scenes/saved-insights/savedInsightsLogic.ts +++ b/frontend/src/scenes/saved-insights/savedInsightsLogic.ts @@ -1,5 +1,6 @@ -import { kea } from 'kea' -import { router } from 'kea-router' +import { loaders } from 'kea-loaders' +import { kea, path, connect, actions, reducers, selectors, listeners } from 'kea' +import { router, actionToUrl, urlToAction } from 'kea-router' import api from 'lib/api' import { objectDiffShallow, objectsEqual, toParams } from 'lib/utils' import { InsightModel, LayoutView, SavedInsightsTabs } from '~/types' @@ -58,13 +59,13 @@ function cleanFilters(values: Partial): SavedInsightFilters } } -export const savedInsightsLogic = kea({ - path: ['scenes', 'saved-insights', 'savedInsightsLogic'], - connect: { +export const savedInsightsLogic = kea([ + path(['scenes', 'saved-insights', 'savedInsightsLogic']), + connect({ values: [teamLogic, ['currentTeamId'], featureFlagLogic, ['featureFlags']], logic: [eventUsageLogic], - }, - actions: { + }), + actions({ setSavedInsightsFilters: ( filters: Partial, merge: boolean = true, @@ -79,8 +80,8 @@ export const savedInsightsLogic = kea({ loadInsights: (debounce: boolean = true) => ({ debounce }), setInsight: (insight: InsightModel) => ({ insight }), addInsight: (insight: InsightModel) => ({ insight }), - }, - loaders: ({ values }) => ({ + }), + loaders(({ values }) => ({ insights: { __default: { results: [], count: 0, filters: null, offset: 0 } as InsightsResult, loadInsights: async ({ debounce }, breakpoint) => { @@ -139,8 +140,8 @@ export const savedInsightsLogic = kea({ return { ...values.insights, results: updatedInsights } }, }, - }), - reducers: { + })), + reducers({ insights: { setInsight: (state, { insight }) => ({ ...state, @@ -164,8 +165,8 @@ export const savedInsightsLogic = kea({ }), }, ], - }, - selectors: ({ actions }) => ({ + }), + selectors(({ actions }) => ({ filters: [(s) => [s.rawFilters], (rawFilters): SavedInsightFilters => cleanFilters(rawFilters || {})], count: [(s) => [s.insights], (insights) => insights.count], usingFilters: [ @@ -236,8 +237,8 @@ export const savedInsightsLogic = kea({ } }, ], - }), - listeners: ({ actions, asyncActions, values, selectors }) => ({ + })), + listeners(({ actions, asyncActions, values, selectors }) => ({ setSavedInsightsFilters: async ({ merge, debounce }, breakpoint, __, previousState) => { const oldFilters = selectors.filters(previousState) const firstLoad = selectors.rawFilters(previousState) === null @@ -307,8 +308,8 @@ export const savedInsightsLogic = kea({ actions.loadInsights() } }, - }), - actionToUrl: ({ values }) => { + })), + actionToUrl(({ values }) => { const changeUrl = (): | [ string, @@ -336,8 +337,8 @@ export const savedInsightsLogic = kea({ loadInsights: changeUrl, setLayoutView: changeUrl, } - }, - urlToAction: ({ actions, values }) => ({ + }), + urlToAction(({ actions, values }) => ({ [urls.savedInsights()]: async (_, searchParams, hashParams) => { if (hashParams.fromItem && String(hashParams.fromItem).match(/^[0-9]+$/)) { // `fromItem` for legacy /insights url redirect support @@ -367,5 +368,5 @@ export const savedInsightsLogic = kea({ actions.setSavedInsightsFilters(nextFilters, false) } }, - }), -}) + })), +]) diff --git a/frontend/src/scenes/sceneLogic.ts b/frontend/src/scenes/sceneLogic.ts index 185a733115e73..6663d51bc6ed5 100644 --- a/frontend/src/scenes/sceneLogic.ts +++ b/frontend/src/scenes/sceneLogic.ts @@ -1,5 +1,5 @@ -import { BuiltLogic, kea } from 'kea' -import { router } from 'kea-router' +import { BuiltLogic, kea, props, path, connect, actions, reducers, selectors, listeners } from 'kea' +import { router, urlToAction } from 'kea-router' import posthog from 'posthog-js' import type { sceneLogicType } from './sceneLogicType' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' @@ -44,18 +44,20 @@ const sceneNavAlias: Partial> = { [Scene.ReplayPlaylist]: Scene.ReplayPlaylist, } -export const sceneLogic = kea({ - props: {} as { - scenes?: Record any> - }, - connect: () => ({ +export const sceneLogic = kea([ + props( + {} as { + scenes?: Record any> + } + ), + path(['scenes', 'sceneLogic']), + connect(() => ({ logic: [router, userLogic, preflightLogic, appContextLogic], actions: [router, ['locationChanged']], - }), - path: ['scenes', 'sceneLogic'], - actions: { + })), + actions({ /* 1. Prepares to open the scene, as the listener may override and do something - else (e.g. redirecting if unauthenticated), then calls (2) `loadScene`*/ + else (e.g. redirecting if unauthenticated), then calls (2) `loadScene`*/ openScene: (scene: Scene, params: SceneParams, method: string) => ({ scene, params, method }), // 2. Start loading the scene's Javascript and mount any logic, then calls (3) `setScene` loadScene: (scene: Scene, params: SceneParams, method: string) => ({ scene, params, method }), @@ -83,8 +85,8 @@ export const sceneLogic = kea({ ) => ({ featureKey, featureName, featureCaption, featureAvailableCallback, guardOn, currentUsage }), hideUpgradeModal: true, reloadBrowserDueToImportError: true, - }, - reducers: { + }), + reducers({ scene: [ null as Scene | null, { @@ -128,8 +130,8 @@ export const sceneLogic = kea({ reloadBrowserDueToImportError: () => new Date().valueOf(), }, ], - }, - selectors: { + }), + selectors({ sceneConfig: [ (s) => [s.scene], (scene: Scene): SceneConfig | null => { @@ -167,38 +169,8 @@ export const sceneLogic = kea({ params: [(s) => [s.sceneParams], (sceneParams): Record => sceneParams.params || {}], searchParams: [(s) => [s.sceneParams], (sceneParams): Record => sceneParams.searchParams || {}], hashParams: [(s) => [s.sceneParams], (sceneParams): Record => sceneParams.hashParams || {}], - }, - urlToAction: ({ actions }) => { - const mapping: Record< - string, - ( - params: Params, - searchParams: Params, - hashParams: Params, - payload: { - method: string - } - ) => any - > = {} - - for (const path of Object.keys(redirects)) { - mapping[path] = (params, searchParams, hashParams) => { - const redirect = redirects[path] - router.actions.replace( - typeof redirect === 'function' ? redirect(params, searchParams, hashParams) : redirect - ) - } - } - for (const [path, scene] of Object.entries(routes)) { - mapping[path] = (params, searchParams, hashParams, { method }) => - actions.openScene(scene, { params, searchParams, hashParams }, method) - } - - mapping['/*'] = (_, __, { method }) => actions.loadScene(Scene.Error404, emptySceneParams, method) - - return mapping - }, - listeners: ({ values, actions, props, selectors }) => ({ + }), + listeners(({ values, actions, props, selectors }) => ({ showUpgradeModal: ({ featureName }) => { eventUsageLogic.actions.reportUpgradeModalShown(featureName) }, @@ -410,5 +382,35 @@ export const sceneLogic = kea({ router.actions.replace(pathname.replace(/(\/+)$/, ''), search, hash) } }, + })), + urlToAction(({ actions }) => { + const mapping: Record< + string, + ( + params: Params, + searchParams: Params, + hashParams: Params, + payload: { + method: string + } + ) => any + > = {} + + for (const path of Object.keys(redirects)) { + mapping[path] = (params, searchParams, hashParams) => { + const redirect = redirects[path] + router.actions.replace( + typeof redirect === 'function' ? redirect(params, searchParams, hashParams) : redirect + ) + } + } + for (const [path, scene] of Object.entries(routes)) { + mapping[path] = (params, searchParams, hashParams, { method }) => + actions.openScene(scene, { params, searchParams, hashParams }, method) + } + + mapping['/*'] = (_, __, { method }) => actions.loadScene(Scene.Error404, emptySceneParams, method) + + return mapping }), -}) +]) diff --git a/frontend/src/scenes/trends/mathsLogic.tsx b/frontend/src/scenes/trends/mathsLogic.tsx index e62d0a64b308f..b2eb2979b7f12 100644 --- a/frontend/src/scenes/trends/mathsLogic.tsx +++ b/frontend/src/scenes/trends/mathsLogic.tsx @@ -1,4 +1,4 @@ -import { kea } from 'kea' +import { kea, path, connect, selectors } from 'kea' import { groupsModel } from '~/models/groupsModel' import type { mathsLogicType } from './mathsLogicType' import { BaseMathType, CountPerActorMathType, HogQLMathType, PropertyMathType } from '~/types' @@ -274,17 +274,17 @@ export function apiValueToMathType(math: string | undefined, groupTypeIndex: num return assembledMath } -export const mathsLogic = kea({ - path: ['scenes', 'trends', 'mathsLogic'], - connect: { +export const mathsLogic = kea([ + path(['scenes', 'trends', 'mathsLogic']), + connect({ values: [ groupsModel, ['groupTypes', 'aggregationLabel'], groupsAccessLogic, ['needsUpgradeForGroups', 'canStartUsingGroups'], ], - }, - selectors: { + }), + selectors({ mathDefinitions: [ (s) => [s.groupsMathDefinitions], (groupsMathDefinitions): Record => { @@ -347,5 +347,5 @@ export const mathsLogic = kea({ ]) ), ], - }, -}) + }), +]) diff --git a/frontend/src/toolbar/actions/actionsLogic.ts b/frontend/src/toolbar/actions/actionsLogic.ts index 31f19569a9679..63057d6330b29 100644 --- a/frontend/src/toolbar/actions/actionsLogic.ts +++ b/frontend/src/toolbar/actions/actionsLogic.ts @@ -1,24 +1,17 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, path, actions, reducers, selectors } from 'kea' import { toolbarLogic } from '~/toolbar/toolbarLogic' import type { actionsLogicType } from './actionsLogicType' import { ActionType } from '~/types' import Fuse from 'fuse.js' import { toolbarFetch } from '~/toolbar/utils' -export const actionsLogic = kea({ - path: ['toolbar', 'actions', 'actionsLogic'], - actions: { +export const actionsLogic = kea([ + path(['toolbar', 'actions', 'actionsLogic']), + actions({ setSearchTerm: (searchTerm: string) => ({ searchTerm }), - }, - reducers: { - searchTerm: [ - '', - { - setSearchTerm: (_, { searchTerm }) => searchTerm, - }, - ], - }, - loaders: ({ values }) => ({ + }), + loaders(({ values }) => ({ allActions: [ [] as ActionType[], { @@ -48,9 +41,16 @@ export const actionsLogic = kea({ }, }, ], + })), + reducers({ + searchTerm: [ + '', + { + setSearchTerm: (_, { searchTerm }) => searchTerm, + }, + ], }), - - selectors: { + selectors({ sortedActions: [ (s) => [s.allActions, s.searchTerm], (allActions, searchTerm) => { @@ -68,5 +68,5 @@ export const actionsLogic = kea({ }, ], actionCount: [(s) => [s.allActions], (allActions) => allActions.length], - }, -}) + }), +]) diff --git a/frontend/src/toolbar/button/toolbarButtonLogic.ts b/frontend/src/toolbar/button/toolbarButtonLogic.ts index f3879e78fe40d..f2b7ed3540d61 100644 --- a/frontend/src/toolbar/button/toolbarButtonLogic.ts +++ b/frontend/src/toolbar/button/toolbarButtonLogic.ts @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { windowValues } from 'kea-windowvalues' +import { kea, path, connect, actions, reducers, selectors, listeners } from 'kea' import { inBounds } from '~/toolbar/utils' import { heatmapLogic } from '~/toolbar/elements/heatmapLogic' import { elementsLogic } from '~/toolbar/elements/elementsLogic' @@ -7,12 +8,12 @@ import type { toolbarButtonLogicType } from './toolbarButtonLogicType' import { posthog } from '~/toolbar/posthog' import { HedgehogActor } from 'lib/components/HedgehogBuddy/HedgehogBuddy' -export const toolbarButtonLogic = kea({ - path: ['toolbar', 'button', 'toolbarButtonLogic'], - connect: () => ({ +export const toolbarButtonLogic = kea([ + path(['toolbar', 'button', 'toolbarButtonLogic']), + connect(() => ({ actions: [actionsTabLogic, ['showButtonActions'], elementsLogic, ['enableInspect']], - }), - actions: () => ({ + })), + actions(() => ({ showHeatmapInfo: true, hideHeatmapInfo: true, showActionsInfo: true, @@ -27,14 +28,12 @@ export const toolbarButtonLogic = kea({ saveActionsPosition: (x: number, y: number) => ({ x, y }), saveFlagsPosition: (x: number, y: number) => ({ x, y }), setHedgehogActor: (actor: HedgehogActor) => ({ actor }), - }), - - windowValues: () => ({ + })), + windowValues(() => ({ windowHeight: (window) => window.innerHeight, windowWidth: (window) => Math.min(window.innerWidth, window.document.body.clientWidth), - }), - - reducers: () => ({ + })), + reducers(() => ({ heatmapInfoVisible: [ false, { @@ -113,9 +112,8 @@ export const toolbarButtonLogic = kea({ setHedgehogActor: (_, { actor }) => actor, }, ], - }), - - selectors: { + })), + selectors({ dragPosition: [ (s) => [s.lastDragPosition, s.windowWidth, s.windowHeight], (lastDragPosition, windowWidth, windowHeight) => { @@ -195,9 +193,8 @@ export const toolbarButtonLogic = kea({ (flagsVisible, extensionPercentage) => flagsVisible ? Math.max(extensionPercentage, 0.53) : extensionPercentage, ], - }, - - listeners: ({ actions, values }) => ({ + }), + listeners(({ actions, values }) => ({ showFlags: () => { posthog.capture('toolbar mode triggered', { mode: 'flags', enabled: true }) values.hedgehogActor?.setAnimation('flag') @@ -224,5 +221,5 @@ export const toolbarButtonLogic = kea({ y > windowHeight / 2 ? -(windowHeight - y) : y ) }, - }), -}) + })), +]) diff --git a/frontend/src/toolbar/elements/elementsLogic.ts b/frontend/src/toolbar/elements/elementsLogic.ts index b505a90fbaa4f..ede7fd0a99ae2 100644 --- a/frontend/src/toolbar/elements/elementsLogic.ts +++ b/frontend/src/toolbar/elements/elementsLogic.ts @@ -1,4 +1,4 @@ -import { kea } from 'kea' +import { kea, path, connect, actions, reducers, selectors, listeners, events } from 'kea' import { actionsLogic } from '~/toolbar/actions/actionsLogic' import { heatmapLogic } from '~/toolbar/elements/heatmapLogic' @@ -26,9 +26,13 @@ function debounce) => ReturnType>( } } -export const elementsLogic = kea({ - path: ['toolbar', 'elements', 'elementsLogic'], - actions: { +export const elementsLogic = kea([ + path(['toolbar', 'elements', 'elementsLogic']), + connect(() => ({ + values: [actionsTabLogic, ['actionForm']], + actions: [actionsTabLogic, ['selectAction']], + })), + actions({ enableInspect: true, disableInspect: true, @@ -43,14 +47,8 @@ export const elementsLogic = kea({ setSelectedElement: (element: HTMLElement | null) => ({ element }), setRelativePositionCompensation: (compensation: number) => ({ compensation }), - }, - - connect: () => ({ - values: [actionsTabLogic, ['actionForm']], - actions: [actionsTabLogic, ['selectAction']], }), - - reducers: () => ({ + reducers(() => ({ inspectEnabledRaw: [ false, { @@ -109,9 +107,8 @@ export const elementsLogic = kea({ setRelativePositionCompensation: (_, { compensation }) => compensation, }, ], - }), - - selectors: { + })), + selectors({ activeMetaIsSelected: [ (s) => [s.selectedElementMeta, s.activeMeta], (selectedElementMeta, activeMeta) => @@ -341,65 +338,8 @@ export const elementsLogic = kea({ return selectedElementMeta || hoverElementMeta }, ], - }, - - events: ({ cache, values, actions }) => ({ - afterMount: () => { - cache.updateRelativePosition = debounce(() => { - const relativePositionCompensation = - window.getComputedStyle(document.body).position === 'relative' - ? document.documentElement.getBoundingClientRect().y - document.body.getBoundingClientRect().y - : 0 - if (relativePositionCompensation !== values.relativePositionCompensation) { - actions.setRelativePositionCompensation(relativePositionCompensation) - } - }, 100) - cache.onClick = () => actions.updateRects() - cache.onScrollResize = () => { - window.clearTimeout(cache.clickDelayTimeout) - actions.updateRects() - cache.clickDelayTimeout = window.setTimeout(actions.updateRects, 100) - cache.updateRelativePosition() - } - cache.onKeyDown = (e: KeyboardEvent) => { - if (e.keyCode !== 27) { - return - } - if (values.hoverElement) { - actions.setHoverElement(null) - } - if (values.selectedElement) { - actions.setSelectedElement(null) - return - } - if (values.enabledLast === 'heatmap' && values.heatmapEnabled) { - heatmapLogic.actions.disableHeatmap() - return - } - if (values.inspectEnabled) { - actions.disableInspect() - return - } - if (values.heatmapEnabled) { - heatmapLogic.actions.disableHeatmap() - return - } - } - window.addEventListener('click', cache.onClick) - window.addEventListener('resize', cache.onScrollResize) - window.addEventListener('keydown', cache.onKeyDown) - window.document.addEventListener('scroll', cache.onScrollResize, true) - cache.updateRelativePosition() - }, - beforeUnmount: () => { - window.removeEventListener('click', cache.onClick) - window.removeEventListener('resize', cache.onScrollResize) - window.removeEventListener('keydown', cache.onKeyDown) - window.document.removeEventListener('scroll', cache.onScrollResize, true) - }, }), - - listeners: ({ actions, values }) => ({ + listeners(({ actions, values }) => ({ enableInspect: () => { posthog.capture('toolbar mode triggered', { mode: 'inspect', enabled: true }) actionsLogic.actions.getActions() @@ -458,5 +398,60 @@ export const elementsLogic = kea({ elementsLogic.actions.selectElement(null) actionsTabLogic.actions.newAction(element) }, - }), -}) + })), + events(({ cache, values, actions }) => ({ + afterMount: () => { + cache.updateRelativePosition = debounce(() => { + const relativePositionCompensation = + window.getComputedStyle(document.body).position === 'relative' + ? document.documentElement.getBoundingClientRect().y - document.body.getBoundingClientRect().y + : 0 + if (relativePositionCompensation !== values.relativePositionCompensation) { + actions.setRelativePositionCompensation(relativePositionCompensation) + } + }, 100) + cache.onClick = () => actions.updateRects() + cache.onScrollResize = () => { + window.clearTimeout(cache.clickDelayTimeout) + actions.updateRects() + cache.clickDelayTimeout = window.setTimeout(actions.updateRects, 100) + cache.updateRelativePosition() + } + cache.onKeyDown = (e: KeyboardEvent) => { + if (e.keyCode !== 27) { + return + } + if (values.hoverElement) { + actions.setHoverElement(null) + } + if (values.selectedElement) { + actions.setSelectedElement(null) + return + } + if (values.enabledLast === 'heatmap' && values.heatmapEnabled) { + heatmapLogic.actions.disableHeatmap() + return + } + if (values.inspectEnabled) { + actions.disableInspect() + return + } + if (values.heatmapEnabled) { + heatmapLogic.actions.disableHeatmap() + return + } + } + window.addEventListener('click', cache.onClick) + window.addEventListener('resize', cache.onScrollResize) + window.addEventListener('keydown', cache.onKeyDown) + window.document.addEventListener('scroll', cache.onScrollResize, true) + cache.updateRelativePosition() + }, + beforeUnmount: () => { + window.removeEventListener('click', cache.onClick) + window.removeEventListener('resize', cache.onScrollResize) + window.removeEventListener('keydown', cache.onKeyDown) + window.document.removeEventListener('scroll', cache.onScrollResize, true) + }, + })), +]) diff --git a/frontend/src/toolbar/flags/featureFlagsLogic.ts b/frontend/src/toolbar/flags/featureFlagsLogic.ts index ec634560909d6..c6a31e6b62bc9 100644 --- a/frontend/src/toolbar/flags/featureFlagsLogic.ts +++ b/frontend/src/toolbar/flags/featureFlagsLogic.ts @@ -1,4 +1,5 @@ -import { kea } from 'kea' +import { loaders } from 'kea-loaders' +import { kea, path, connect, actions, reducers, selectors, listeners, events } from 'kea' import { CombinedFeatureFlagAndValueType } from '~/types' import type { featureFlagsLogicType } from './featureFlagsLogicType' import { toolbarFetch } from '~/toolbar/utils' @@ -8,51 +9,18 @@ import type { PostHog } from 'posthog-js' import { posthog } from '~/toolbar/posthog' import { encodeParams } from 'kea-router' -export const featureFlagsLogic = kea({ - path: ['toolbar', 'flags', 'featureFlagsLogic'], - actions: { +export const featureFlagsLogic = kea([ + path(['toolbar', 'flags', 'featureFlagsLogic']), + connect(() => [toolbarLogic]), + actions({ getUserFlags: true, setOverriddenUserFlag: (flagKey: string, overrideValue: string | boolean) => ({ flagKey, overrideValue }), deleteOverriddenUserFlag: (flagKey: string) => ({ flagKey }), setSearchTerm: (searchTerm: string) => ({ searchTerm }), checkLocalOverrides: true, storeLocalOverrides: (localOverrides: Record) => ({ localOverrides }), - }, - connect: () => [toolbarLogic], - listeners: ({ actions, values }) => ({ - checkLocalOverrides: () => { - const { posthog: clientPostHog } = toolbarLogic.values - if (clientPostHog) { - const locallyOverrideFeatureFlags = clientPostHog.get_property('$override_feature_flags') || {} - actions.storeLocalOverrides(locallyOverrideFeatureFlags) - } - }, - setOverriddenUserFlag: ({ flagKey, overrideValue }) => { - const { posthog: clientPostHog } = toolbarLogic.values - if (clientPostHog) { - clientPostHog.featureFlags.override({ ...values.localOverrides, [flagKey]: overrideValue }) - posthog.capture('toolbar feature flag overridden') - actions.checkLocalOverrides() - toolbarLogic.values.posthog?.featureFlags.reloadFeatureFlags() - } - }, - deleteOverriddenUserFlag: async ({ flagKey }) => { - const { posthog: clientPostHog } = toolbarLogic.values - if (clientPostHog) { - const updatedFlags = { ...values.localOverrides } - delete updatedFlags[flagKey] - if (Object.keys(updatedFlags).length > 0) { - clientPostHog.featureFlags.override({ ...updatedFlags }) - } else { - clientPostHog.featureFlags.override(false) - } - posthog.capture('toolbar feature flag override removed') - actions.checkLocalOverrides() - toolbarLogic.values.posthog?.featureFlags.reloadFeatureFlags() - } - }, }), - loaders: () => ({ + loaders(() => ({ userFlags: [ [] as CombinedFeatureFlagAndValueType[], { @@ -77,8 +45,8 @@ export const featureFlagsLogic = kea({ }, }, ], - }), - reducers: { + })), + reducers({ localOverrides: [ {} as Record, { @@ -91,8 +59,8 @@ export const featureFlagsLogic = kea({ setSearchTerm: (_, { searchTerm }) => searchTerm, }, ], - }, - selectors: { + }), + selectors({ userFlagsWithOverrideInfo: [ (s) => [s.userFlags, s.localOverrides], (userFlags, localOverrides) => { @@ -125,14 +93,47 @@ export const featureFlagsLogic = kea({ }, ], countFlagsOverridden: [(s) => [s.localOverrides], (localOverrides) => Object.keys(localOverrides).length], - }, - events: ({ actions }) => ({ + }), + listeners(({ actions, values }) => ({ + checkLocalOverrides: () => { + const { posthog: clientPostHog } = toolbarLogic.values + if (clientPostHog) { + const locallyOverrideFeatureFlags = clientPostHog.get_property('$override_feature_flags') || {} + actions.storeLocalOverrides(locallyOverrideFeatureFlags) + } + }, + setOverriddenUserFlag: ({ flagKey, overrideValue }) => { + const { posthog: clientPostHog } = toolbarLogic.values + if (clientPostHog) { + clientPostHog.featureFlags.override({ ...values.localOverrides, [flagKey]: overrideValue }) + posthog.capture('toolbar feature flag overridden') + actions.checkLocalOverrides() + toolbarLogic.values.posthog?.featureFlags.reloadFeatureFlags() + } + }, + deleteOverriddenUserFlag: async ({ flagKey }) => { + const { posthog: clientPostHog } = toolbarLogic.values + if (clientPostHog) { + const updatedFlags = { ...values.localOverrides } + delete updatedFlags[flagKey] + if (Object.keys(updatedFlags).length > 0) { + clientPostHog.featureFlags.override({ ...updatedFlags }) + } else { + clientPostHog.featureFlags.override(false) + } + posthog.capture('toolbar feature flag override removed') + actions.checkLocalOverrides() + toolbarLogic.values.posthog?.featureFlags.reloadFeatureFlags() + } + }, + })), + events(({ actions }) => ({ afterMount: async () => { await actions.getUserFlags() actions.checkLocalOverrides() }, - }), -}) + })), +]) function getGroups(posthogInstance: PostHog | null): Record { try { diff --git a/frontend/src/toolbar/stats/currentPageLogic.ts b/frontend/src/toolbar/stats/currentPageLogic.ts index 276345a209222..97f372f96f899 100644 --- a/frontend/src/toolbar/stats/currentPageLogic.ts +++ b/frontend/src/toolbar/stats/currentPageLogic.ts @@ -1,22 +1,20 @@ -import { kea } from 'kea' +import { kea, path, actions, reducers, events } from 'kea' import type { currentPageLogicType } from './currentPageLogicType' -export const currentPageLogic = kea({ - path: ['toolbar', 'stats', 'currentPageLogic'], - actions: () => ({ +export const currentPageLogic = kea([ + path(['toolbar', 'stats', 'currentPageLogic']), + actions(() => ({ setHref: (href: string) => ({ href }), setWildcardHref: (href: string) => ({ href }), - }), - - reducers: () => ({ + })), + reducers(() => ({ href: [window.location.href, { setHref: (_, { href }) => href }], wildcardHref: [ window.location.href, { setHref: (_, { href }) => href, setWildcardHref: (_, { href }) => href }, ], - }), - - events: ({ actions, cache, values }) => ({ + })), + events(({ actions, cache, values }) => ({ afterMount: () => { cache.interval = window.setInterval(() => { if (window.location.href !== values.href) { @@ -34,5 +32,5 @@ export const currentPageLogic = kea({ window.clearInterval(cache.interval) window.removeEventListener('popstate', cache.location) }, - }), -}) + })), +])