diff --git a/frontend/__snapshots__/scenes-app-apps--installed--dark.png b/frontend/__snapshots__/scenes-app-apps--installed--dark.png index 46d9b6ac0f584..951b1068bb7fc 100644 Binary files a/frontend/__snapshots__/scenes-app-apps--installed--dark.png and b/frontend/__snapshots__/scenes-app-apps--installed--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-apps--installed--light.png b/frontend/__snapshots__/scenes-app-apps--installed--light.png index b9be95598c8c5..c2f105784ea76 100644 Binary files a/frontend/__snapshots__/scenes-app-apps--installed--light.png and b/frontend/__snapshots__/scenes-app-apps--installed--light.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--user-paths-edit--dark--webkit.png b/frontend/__snapshots__/scenes-app-insights--user-paths-edit--dark--webkit.png index f998c5f040fd0..fecccc919915f 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--user-paths-edit--dark--webkit.png and b/frontend/__snapshots__/scenes-app-insights--user-paths-edit--dark--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--user-paths-edit--dark.png b/frontend/__snapshots__/scenes-app-insights--user-paths-edit--dark.png index 2a87f72cafd98..c6b98a58f4942 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--user-paths-edit--dark.png and b/frontend/__snapshots__/scenes-app-insights--user-paths-edit--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--user-paths-edit--light--webkit.png b/frontend/__snapshots__/scenes-app-insights--user-paths-edit--light--webkit.png index e676a048434c7..ce3d83527b233 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--user-paths-edit--light--webkit.png and b/frontend/__snapshots__/scenes-app-insights--user-paths-edit--light--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--user-paths-edit--light.png b/frontend/__snapshots__/scenes-app-insights--user-paths-edit--light.png index 66f315769be98..28cf1dd94717a 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--user-paths-edit--light.png and b/frontend/__snapshots__/scenes-app-insights--user-paths-edit--light.png differ diff --git a/frontend/src/lib/api.mock.ts b/frontend/src/lib/api.mock.ts index 68b1fbd7a466d..7da459c7baf54 100644 --- a/frontend/src/lib/api.mock.ts +++ b/frontend/src/lib/api.mock.ts @@ -239,6 +239,8 @@ export const MOCK_DEFAULT_PLUGIN: PluginType = { }, metrics: {}, public_jobs: {}, + // urls are hard-coded in frontend/src/scenes/pipeline/utils.tsx so it must be one of those URLs for tests to work + url: 'https://github.com/PostHog/downsampling-plugin', } export const MOCK_DEFAULT_PLUGIN_CONFIG: PluginConfigWithPluginInfo = { diff --git a/frontend/src/lib/components/PayGateMini/PayGateMini.tsx b/frontend/src/lib/components/PayGateMini/PayGateMini.tsx index f801f600cf045..1fc4ac3e22fbb 100644 --- a/frontend/src/lib/components/PayGateMini/PayGateMini.tsx +++ b/frontend/src/lib/components/PayGateMini/PayGateMini.tsx @@ -20,6 +20,7 @@ type PayGateSupportedFeatures = | AvailableFeature.PATHS_ADVANCED | AvailableFeature.SURVEYS_STYLING | AvailableFeature.SURVEYS_TEXT_HTML + | AvailableFeature.DATA_PIPELINES export interface PayGateMiniProps { feature: PayGateSupportedFeatures @@ -83,6 +84,11 @@ const FEATURE_SUMMARIES: Record< umbrella: 'surveys customization', docsHref: 'https://posthog.com/docs/surveys', }, + [AvailableFeature.DATA_PIPELINES]: { + description: 'Create export workflows to send your data to a destination of your choice.', + umbrella: 'data pipelines', + docsHref: 'https://posthog.com/docs/data-pipelines', + }, } /** A sort of paywall for premium features. @@ -143,8 +149,7 @@ export function PayGateMini({ ? '/organization/billing' : undefined } - type="secondary" - fullWidth + type="primary" center > {gateVariant === 'add-card' diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index 15a37e6a2a279..1e72f7cc572f1 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -226,6 +226,7 @@ export const FEATURE_MINIMUM_PLAN: Partial [AvailableFeature.SURVEYS_STYLING]: LicensePlan.Scale, [AvailableFeature.SURVEYS_MULTIPLE_QUESTIONS]: LicensePlan.Scale, [AvailableFeature.SURVEYS_TEXT_HTML]: LicensePlan.Scale, + [AvailableFeature.DATA_PIPELINES]: LicensePlan.Scale, } export const ENTITY_MATCH_TYPE = 'entities' diff --git a/frontend/src/scenes/batch_exports/BatchExportBackfillModal.tsx b/frontend/src/scenes/batch_exports/BatchExportBackfillModal.tsx index 3bdb8c51fce21..1c5ae84c4f2cb 100644 --- a/frontend/src/scenes/batch_exports/BatchExportBackfillModal.tsx +++ b/frontend/src/scenes/batch_exports/BatchExportBackfillModal.tsx @@ -4,10 +4,17 @@ import { Field } from 'lib/forms/Field' import { LemonButton } from 'lib/lemon-ui/LemonButton' import { LemonCalendarSelectInput } from 'lib/lemon-ui/LemonCalendar/LemonCalendarSelect' import { LemonModal } from 'lib/lemon-ui/LemonModal' +import { userLogic } from 'scenes/userLogic' + +import { AvailableFeature } from '~/types' import { batchExportLogic } from './batchExportLogic' export function BatchExportBackfillModal(): JSX.Element { + const { hasAvailableFeature } = useValues(userLogic) + if (!hasAvailableFeature(AvailableFeature.DATA_PIPELINES)) { + return <> + } const { batchExportConfig, isBackfillModalOpen, isBackfillFormSubmitting } = useValues(batchExportLogic) const { closeBackfillModal } = useActions(batchExportLogic) diff --git a/frontend/src/scenes/batch_exports/BatchExportEditForm.tsx b/frontend/src/scenes/batch_exports/BatchExportEditForm.tsx index 92b5246510c71..0a5b7f66d6aac 100644 --- a/frontend/src/scenes/batch_exports/BatchExportEditForm.tsx +++ b/frontend/src/scenes/batch_exports/BatchExportEditForm.tsx @@ -11,10 +11,17 @@ import { LemonSelectMultiple } from 'lib/lemon-ui/LemonSelectMultiple/LemonSelec import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton' import { Tooltip } from 'lib/lemon-ui/Tooltip' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' +import { userLogic } from 'scenes/userLogic' + +import { AvailableFeature } from '~/types' import { batchExportsEditLogic, BatchExportsEditLogicProps } from './batchExportEditLogic' export function BatchExportsEditForm(props: BatchExportsEditLogicProps): JSX.Element { + const { hasAvailableFeature } = useValues(userLogic) + if (!hasAvailableFeature(AvailableFeature.DATA_PIPELINES)) { + return <> + } const logic = batchExportsEditLogic(props) const { isNew, batchExportConfigForm, isBatchExportConfigFormSubmitting, batchExportConfigLoading } = useValues(logic) diff --git a/frontend/src/scenes/batch_exports/BatchExportEditScene.tsx b/frontend/src/scenes/batch_exports/BatchExportEditScene.tsx index 43822ae065c8f..3d5f2edaa4236 100644 --- a/frontend/src/scenes/batch_exports/BatchExportEditScene.tsx +++ b/frontend/src/scenes/batch_exports/BatchExportEditScene.tsx @@ -1,6 +1,9 @@ import { useValues } from 'kea' import { PageHeader } from 'lib/components/PageHeader' import { SceneExport } from 'scenes/sceneTypes' +import { userLogic } from 'scenes/userLogic' + +import { AvailableFeature } from '~/types' import { BatchExportsEditForm } from './BatchExportEditForm' import { BatchExportsEditLogicProps } from './batchExportEditLogic' @@ -15,6 +18,10 @@ export const scene: SceneExport = { } export function BatchExportsEditScene(): JSX.Element { + const { hasAvailableFeature } = useValues(userLogic) + if (!hasAvailableFeature(AvailableFeature.DATA_PIPELINES)) { + return <> + } const { id } = useValues(batchExportsEditSceneLogic) return ( diff --git a/frontend/src/scenes/batch_exports/BatchExportScene.tsx b/frontend/src/scenes/batch_exports/BatchExportScene.tsx index b02c42fdb1341..eac8dc5b260d5 100644 --- a/frontend/src/scenes/batch_exports/BatchExportScene.tsx +++ b/frontend/src/scenes/batch_exports/BatchExportScene.tsx @@ -26,8 +26,9 @@ import { useEffect, useState } from 'react' import { PipelineAppLogLevel } from 'scenes/pipeline/pipelineAppLogsLogic' import { SceneExport } from 'scenes/sceneTypes' import { urls } from 'scenes/urls' +import { userLogic } from 'scenes/userLogic' -import { BatchExportLogEntry } from '~/types' +import { AvailableFeature, BatchExportLogEntry } from '~/types' import { BatchExportBackfillModal } from './BatchExportBackfillModal' import { batchExportLogic, BatchExportLogicProps, BatchExportTab } from './batchExportLogic' @@ -44,6 +45,10 @@ export const scene: SceneExport = { } export function RunsTab(): JSX.Element { + const { hasAvailableFeature } = useValues(userLogic) + if (!hasAvailableFeature(AvailableFeature.DATA_PIPELINES)) { + return <> + } const { batchExportRunsResponse, batchExportConfig, diff --git a/frontend/src/scenes/batch_exports/BatchExports.stories.tsx b/frontend/src/scenes/batch_exports/BatchExports.stories.tsx index a10466a01c79b..7545d46ba34ae 100644 --- a/frontend/src/scenes/batch_exports/BatchExports.stories.tsx +++ b/frontend/src/scenes/batch_exports/BatchExports.stories.tsx @@ -5,6 +5,8 @@ import { App } from 'scenes/App' import { urls } from 'scenes/urls' import { mswDecorator } from '~/mocks/browser' +import { useAvailableFeatures } from '~/mocks/features' +import { AvailableFeature } from '~/types' import { createExportServiceHandlers } from './__mocks__/api-mocks' @@ -84,6 +86,7 @@ export default { } as Meta export const Exports: StoryFn = () => { + useAvailableFeatures([AvailableFeature.DATA_PIPELINES]) useEffect(() => { router.actions.push(urls.batchExports()) }) @@ -96,6 +99,7 @@ Exports.parameters = { } export const CreateExport: StoryFn = () => { + useAvailableFeatures([AvailableFeature.DATA_PIPELINES]) useEffect(() => { router.actions.push(urls.batchExportNew()) }) @@ -103,6 +107,7 @@ export const CreateExport: StoryFn = () => { } export const ViewExport: StoryFn = () => { + useAvailableFeatures([AvailableFeature.DATA_PIPELINES]) useEffect(() => { router.actions.push(urls.batchExport('1')) }) diff --git a/frontend/src/scenes/batch_exports/BatchExportsListScene.tsx b/frontend/src/scenes/batch_exports/BatchExportsListScene.tsx index f800bf0651fd1..4a245bbb5b5c0 100644 --- a/frontend/src/scenes/batch_exports/BatchExportsListScene.tsx +++ b/frontend/src/scenes/batch_exports/BatchExportsListScene.tsx @@ -5,6 +5,9 @@ import { IconEllipsis } from 'lib/lemon-ui/icons' import { LemonMenu, LemonMenuItems } from 'lib/lemon-ui/LemonMenu' import { SceneExport } from 'scenes/sceneTypes' import { urls } from 'scenes/urls' +import { userLogic } from 'scenes/userLogic' + +import { AvailableFeature } from '~/types' import { batchExportsListLogic } from './batchExportsListLogic' import { BatchExportRunIcon, BatchExportTag } from './components' @@ -14,6 +17,10 @@ export const scene: SceneExport = { } export function BatchExportsListScene(): JSX.Element { + const { hasAvailableFeature } = useValues(userLogic) + if (!hasAvailableFeature(AvailableFeature.DATA_PIPELINES)) { + return <> + } return ( <> { if (!canViewPlugins(user?.organization)) { window.location.href = '/' @@ -44,7 +47,7 @@ export function AppsScene(): JSX.Element | null { Create export workflow @@ -57,7 +60,11 @@ export function AppsScene(): JSX.Element | null { onChange={(newKey) => setPluginTab(newKey)} tabs={[ { key: PluginTab.Apps, label: 'Apps', content: }, - { key: PluginTab.BatchExports, label: 'Batch Exports', content: }, + { + key: PluginTab.BatchExports, + label: 'Batch Exports', + content: , + }, { key: PluginTab.History, label: 'History', diff --git a/frontend/src/scenes/plugins/tabs/apps/AppView.tsx b/frontend/src/scenes/plugins/tabs/apps/AppView.tsx index baf4b83363845..ec6fea4eb64b0 100644 --- a/frontend/src/scenes/plugins/tabs/apps/AppView.tsx +++ b/frontend/src/scenes/plugins/tabs/apps/AppView.tsx @@ -3,13 +3,15 @@ import { useActions, useValues } from 'kea' import { IconEllipsis, IconErrorOutline, IconLegend, IconLink, IconSettings } from 'lib/lemon-ui/icons' import { LemonMenu, LemonMenuItem } from 'lib/lemon-ui/LemonMenu' import { Tooltip } from 'lib/lemon-ui/Tooltip' +import { PLUGINS_ALLOWED_WITHOUT_DATA_PIPELINES } from 'scenes/pipeline/utils' import { PluginImage } from 'scenes/plugins/plugin/PluginImage' import { SuccessRateBadge } from 'scenes/plugins/plugin/SuccessRateBadge' import { pluginsLogic } from 'scenes/plugins/pluginsLogic' import { PluginRepositoryEntry, PluginTypeWithConfig } from 'scenes/plugins/types' import { urls } from 'scenes/urls' +import { userLogic } from 'scenes/userLogic' -import { PluginType } from '~/types' +import { AvailableFeature, PluginType } from '~/types' import { PluginTags } from './components' @@ -20,6 +22,13 @@ export function AppView({ }): JSX.Element { const { showAppMetricsForPlugin, sortableEnabledPlugins } = useValues(pluginsLogic) const { editPlugin, toggleEnabled, openReorderModal } = useActions(pluginsLogic) + const { hasAvailableFeature } = useValues(userLogic) + if (!hasAvailableFeature(AvailableFeature.DATA_PIPELINES)) { + // If the app isn't in the allowed apps list don't show it + if (!plugin.url || !PLUGINS_ALLOWED_WITHOUT_DATA_PIPELINES.has(plugin.url)) { + return <> + } + } const pluginConfig = 'pluginConfig' in plugin ? plugin.pluginConfig : null const isConfigured = !!pluginConfig?.id diff --git a/frontend/src/scenes/plugins/tabs/batch-exports/BatchExportsTab.tsx b/frontend/src/scenes/plugins/tabs/batch-exports/BatchExportsTab.tsx index 00907e6a84f4a..b32d465fe9aeb 100644 --- a/frontend/src/scenes/plugins/tabs/batch-exports/BatchExportsTab.tsx +++ b/frontend/src/scenes/plugins/tabs/batch-exports/BatchExportsTab.tsx @@ -1,5 +1,12 @@ +import { PayGateMini } from 'lib/components/PayGateMini/PayGateMini' import { BatchExportsList } from 'scenes/batch_exports/BatchExportsListScene' +import { AvailableFeature } from '~/types' + export function BatchExportsTab(): JSX.Element { - return + return ( + + + + ) } diff --git a/frontend/src/types.ts b/frontend/src/types.ts index f36653496d53e..a70ce538e12b2 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -93,6 +93,7 @@ export enum AvailableFeature { SURVEYS_STYLING = 'surveys_styling', SURVEYS_TEXT_HTML = 'surveys_text_html', SURVEYS_MULTIPLE_QUESTIONS = 'surveys_multiple_questions', + DATA_PIPELINES = 'data_pipelines', SESSION_REPLAY_SAMPLING = 'session_replay_sampling', RECORDING_DURATION_MINIMUM = 'replay_recording_duration_minimum', FEATURE_FLAG_BASED_RECORDING = 'replay_feature_flag_based_recording',