diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--dark.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--dark.png index 68d31fe0ddd18..1ef8fdbdc0010 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--dark.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--dark.png differ diff --git a/frontend/src/scenes/data-warehouse/new/DataWarehouseTableForm.tsx b/frontend/src/scenes/data-warehouse/new/DataWarehouseTableForm.tsx index a084ce462fe14..025948f759c75 100644 --- a/frontend/src/scenes/data-warehouse/new/DataWarehouseTableForm.tsx +++ b/frontend/src/scenes/data-warehouse/new/DataWarehouseTableForm.tsx @@ -1,4 +1,4 @@ -import { LemonInput, LemonSelect, Link } from '@posthog/lemon-ui' +import { LemonButton, LemonInput, LemonSelect, Link } from '@posthog/lemon-ui' import { useValues } from 'kea' import { Form } from 'kea-forms' import { LemonField } from 'lib/lemon-ui/LemonField' @@ -44,7 +44,11 @@ const ProviderMappings: Record< }, } -export function DatawarehouseTableForm(): JSX.Element { +interface Props { + onUpdate?: () => void +} + +export function DatawarehouseTableForm({ onUpdate }: Props): JSX.Element { const { manualLinkingProvider } = useValues(sourceWizardLogic) const provider = manualLinkingProvider ?? 'aws' @@ -57,67 +61,87 @@ export function DatawarehouseTableForm(): JSX.Element { enableFormOnSubmit autoComplete="off" > -
+
- + {({ value = '', onChange }) => ( + + )}
This will be the table name used when writing queries
- + {({ value = '', onChange }) => ( + + )}
You can use * to select multiple files.
- + {({ value = '', onChange }) => ( + + )} - + {({ value = '', onChange }) => ( + + )} - + {({ value = '', onChange }) => ( + + )} {provider === 'google-cloud' && (
@@ -128,6 +152,13 @@ export function DatawarehouseTableForm(): JSX.Element {
)}
+ {!!onUpdate && ( +
+ + Save + +
+ )} ) } diff --git a/frontend/src/scenes/data-warehouse/new/NewSourceWizard.tsx b/frontend/src/scenes/data-warehouse/new/NewSourceWizard.tsx index eb2aafa9d7e6c..2b8e8e9672a54 100644 --- a/frontend/src/scenes/data-warehouse/new/NewSourceWizard.tsx +++ b/frontend/src/scenes/data-warehouse/new/NewSourceWizard.tsx @@ -1,9 +1,10 @@ import { LemonButton, LemonTable, Link } from '@posthog/lemon-ui' -import { useActions, useValues } from 'kea' +import { BindLogic, useActions, useValues } from 'kea' import { PageHeader } from 'lib/components/PageHeader' import { FEATURE_FLAGS } from 'lib/constants' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { useCallback, useEffect } from 'react' +import { DataWarehouseSourceIcon } from 'scenes/data-warehouse/settings/DataWarehouseSourceIcon' import { SceneExport } from 'scenes/sceneTypes' import { ManualLinkSourceType, SourceConfig } from '~/types' @@ -13,7 +14,6 @@ import SchemaForm from '../external/forms/SchemaForm' import SourceForm from '../external/forms/SourceForm' import { SyncProgressStep } from '../external/forms/SyncProgressStep' import { DatawarehouseTableForm } from '../new/DataWarehouseTableForm' -import { RenderDataWarehouseSourceIcon } from '../settings/DataWarehouseManagedSourcesTable' import { dataWarehouseTableLogic } from './dataWarehouseTableLogic' import { sourceWizardLogic } from './sourceWizardLogic' @@ -160,33 +160,29 @@ function FirstStep(): JSX.Element { { title: 'Source', width: 0, - render: function RenderAppInfo(_, sourceConfig) { - return + render: function (_, sourceConfig) { + return }, }, { title: 'Name', key: 'name', - render: function RenderName(_, sourceConfig) { - return ( - - {sourceConfig.label ?? sourceConfig.name} - - ) - }, + render: (_, sourceConfig) => ( + + {sourceConfig.label ?? sourceConfig.name} + + ), }, { key: 'actions', width: 0, - render: function RenderActions(_, sourceConfig) { - return ( -
- onClick(sourceConfig)} className="my-2" type="primary"> - Link - -
- ) - }, + render: (_, sourceConfig) => ( +
+ onClick(sourceConfig)} className="my-2" type="primary"> + Link + +
+ ), }, ]} /> @@ -205,33 +201,29 @@ function FirstStep(): JSX.Element { { title: 'Source', width: 0, - render: function RenderAppInfo(_, sourceConfig) { - return - }, + render: (_, sourceConfig) => , }, { title: 'Name', key: 'name', - render: function RenderName(_, sourceConfig) { - return {sourceConfig.name} - }, + render: (_, sourceConfig) => ( + {sourceConfig.name} + ), }, { key: 'actions', width: 0, - render: function RenderActions(_, sourceConfig) { - return ( -
- onManualLinkClick(sourceConfig.type)} - className="my-2" - type="primary" - > - Link - -
- ) - }, + render: (_, sourceConfig) => ( +
+ onManualLinkClick(sourceConfig.type)} + className="my-2" + type="primary" + > + Link + +
+ ), }, ]} /> @@ -242,7 +234,13 @@ function FirstStep(): JSX.Element { function SecondStep(): JSX.Element { const { selectedConnector } = useValues(sourceWizardLogic) - return selectedConnector ? : + return selectedConnector ? ( + + ) : ( + + + + ) } function ThirdStep(): JSX.Element { diff --git a/frontend/src/scenes/data-warehouse/new/dataWarehouseTableLogic.tsx b/frontend/src/scenes/data-warehouse/new/dataWarehouseTableLogic.tsx index fb9e96055985b..37d15ee1a760a 100644 --- a/frontend/src/scenes/data-warehouse/new/dataWarehouseTableLogic.tsx +++ b/frontend/src/scenes/data-warehouse/new/dataWarehouseTableLogic.tsx @@ -1,5 +1,5 @@ import { lemonToast } from '@posthog/lemon-ui' -import { actions, connect, kea, listeners, path, props, reducers } from 'kea' +import { actions, connect, events, kea, listeners, path, props, reducers } from 'kea' import { forms } from 'kea-forms' import { loaders } from 'kea-loaders' import { router } from 'kea-router' @@ -71,6 +71,7 @@ export const dataWarehouseTableLogic = kea([ updateTableSuccess: async ({ table }) => { lemonToast.success(<>Table {table.name} updated) actions.editingTable(false) + actions.loadDatabase() router.actions.replace(urls.dataWarehouse()) }, })), @@ -92,6 +93,13 @@ export const dataWarehouseTableLogic = kea([ table: { defaults: { ...NEW_WAREHOUSE_TABLE } as DataWarehouseTable, errors: ({ name, url_pattern, credential, format }) => { + const HOGQL_TABLE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/ + if (!HOGQL_TABLE_NAME_REGEX.test(name)) { + return { + name: 'Invalid table name. Table names must start with a letter or underscore and contain only alphanumeric characters or underscores.', + } + } + if (url_pattern?.startsWith('s3://')) { return { url_pattern: @@ -125,4 +133,9 @@ export const dataWarehouseTableLogic = kea([ }, }, })), + events(({ actions }) => ({ + propsChanged: () => { + actions.loadTable() + }, + })), ]) diff --git a/frontend/src/scenes/data-warehouse/settings/DataWarehouseManagedSourcesTable.tsx b/frontend/src/scenes/data-warehouse/settings/DataWarehouseManagedSourcesTable.tsx index 9fb9b0e11d3c7..1a42a4fe564f9 100644 --- a/frontend/src/scenes/data-warehouse/settings/DataWarehouseManagedSourcesTable.tsx +++ b/frontend/src/scenes/data-warehouse/settings/DataWarehouseManagedSourcesTable.tsx @@ -1,23 +1,9 @@ -import { LemonButton, LemonDialog, LemonTable, LemonTag, Link, Spinner, Tooltip } from '@posthog/lemon-ui' +import { LemonButton, LemonDialog, LemonTable, LemonTag, Spinner, Tooltip } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { TZLabel } from 'lib/components/TZLabel' import { More } from 'lib/lemon-ui/LemonButton/More' import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink' -import IconAwsS3 from 'public/services/aws-s3.png' -import Iconazure from 'public/services/azure.png' -import IconBigQuery from 'public/services/bigquery.png' -import IconChargebee from 'public/services/chargebee.png' -import IconCloudflare from 'public/services/cloudflare.png' -import IconGoogleCloudStorage from 'public/services/google-cloud-storage.png' -import IconHubspot from 'public/services/hubspot.png' -import IconMySQL from 'public/services/mysql.png' -import IconPostgres from 'public/services/postgres.png' -import IconSalesforce from 'public/services/salesforce.png' -import IconSnowflake from 'public/services/snowflake.png' -import IconMSSQL from 'public/services/sql-azure.png' -import IconStripe from 'public/services/stripe.png' -import IconVitally from 'public/services/vitally.png' -import IconZendesk from 'public/services/zendesk.png' +import { DataWarehouseSourceIcon } from 'scenes/data-warehouse/settings/DataWarehouseSourceIcon' import { urls } from 'scenes/urls' import { manualLinkSources, PipelineNodeTab, PipelineStage } from '~/types' @@ -47,26 +33,22 @@ export function DataWarehouseManagedSourcesTable(): JSX.Element { columns={[ { width: 0, - render: function RenderAppInfo(_, source) { - return - }, + render: (_, source) => , }, { title: 'Source', key: 'name', - render: function RenderName(_, source) { - return ( - - ) - }, + render: (_, source) => ( + + ), }, { title: 'Last Successful Run', @@ -84,78 +66,75 @@ export function DataWarehouseManagedSourcesTable(): JSX.Element { title: 'Total Rows Synced', key: 'rows_synced', tooltip: 'Total number of rows synced across all schemas in this source', - render: function RenderRowsSynced(_, source) { - return source.schemas + render: (_, source) => + source.schemas .reduce((acc, schema) => acc + (schema.table?.row_count ?? 0), 0) - .toLocaleString() - }, + .toLocaleString(), }, { title: 'Status', key: 'status', - render: function RenderStatus(_, source) { - return {source.status} - }, + render: (_, source) => ( + {source.status} + ), }, { key: 'actions', width: 0, - render: function RenderActions(_, source) { - return ( -
- {sourceReloadingById[source.id] ? ( -
- -
- ) : ( -
- - - { - reloadSource(source) - }} - > - Reload - - - + render: (_, source) => ( +
+ {sourceReloadingById[source.id] ? ( +
+ +
+ ) : ( +
+ + { - LemonDialog.open({ - title: 'Delete data source?', - description: - 'Are you sure you want to delete this data source? All related tables will be deleted.', - - primaryButton: { - children: 'Delete', - status: 'danger', - onClick: () => deleteSource(source), - }, - secondaryButton: { - children: 'Cancel', - }, - }) + reloadSource(source) }} > - Delete + Reload - - } - /> -
- )} -
- ) - }, + + + { + LemonDialog.open({ + title: 'Delete data source?', + description: + 'Are you sure you want to delete this data source? All related tables will be deleted.', + + primaryButton: { + children: 'Delete', + status: 'danger', + onClick: () => deleteSource(source), + }, + secondaryButton: { + children: 'Cancel', + }, + }) + }} + > + Delete + + + } + /> +
+ )} +
+ ), }, ]} /> @@ -169,55 +148,3 @@ export function getDataWarehouseSourceUrl(service: string): string { return `https://posthog.com/docs/data-warehouse/setup#${service.toLowerCase()}` } - -export function RenderDataWarehouseSourceIcon({ - type, - size = 'small', -}: { - type: string - size?: 'small' | 'medium' -}): JSX.Element { - const sizePx = size === 'small' ? 30 : 60 - - const icon = { - Stripe: IconStripe, - Hubspot: IconHubspot, - Zendesk: IconZendesk, - Postgres: IconPostgres, - MySQL: IconMySQL, - Snowflake: IconSnowflake, - aws: IconAwsS3, - 'google-cloud': IconGoogleCloudStorage, - 'cloudflare-r2': IconCloudflare, - azure: Iconazure, - Salesforce: IconSalesforce, - MSSQL: IconMSSQL, - Vitally: IconVitally, - BigQuery: IconBigQuery, - Chargebee: IconChargebee, - }[type] - - return ( -
- - {type} -
- Click to view docs - - } - > - - {type} - -
-
- ) -} diff --git a/frontend/src/scenes/data-warehouse/settings/DataWarehouseSelfManagedSourcesTable.tsx b/frontend/src/scenes/data-warehouse/settings/DataWarehouseSelfManagedSourcesTable.tsx index 19e37015f5760..4e0680be2ebf3 100644 --- a/frontend/src/scenes/data-warehouse/settings/DataWarehouseSelfManagedSourcesTable.tsx +++ b/frontend/src/scenes/data-warehouse/settings/DataWarehouseSelfManagedSourcesTable.tsx @@ -1,7 +1,11 @@ import { LemonButton, LemonDialog, LemonTable } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' +import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink' +import { DataWarehouseSourceIcon, mapUrlToProvider } from 'scenes/data-warehouse/settings/DataWarehouseSourceIcon' +import { urls } from 'scenes/urls' import { DatabaseSchemaDataWarehouseTable } from '~/queries/schema' +import { PipelineNodeTab, PipelineStage } from '~/types' import { dataWarehouseSettingsLogic } from './dataWarehouseSettingsLogic' @@ -15,50 +19,64 @@ export function DataWarehouseSelfManagedSourcesTable(): JSX.Element { pagination={{ pageSize: 10 }} columns={[ { - title: 'Name', + width: 0, + render: (_, item: DatabaseSchemaDataWarehouseTable) => ( + + ), + }, + { + title: 'Source', dataIndex: 'name', key: 'name', + render: (_, item: DatabaseSchemaDataWarehouseTable) => ( + + ), }, { key: 'actions', - render: (_, item: DatabaseSchemaDataWarehouseTable) => { - return ( -
- refreshSelfManagedTableSchema(item.id)} - > - Update schema from source - - { - LemonDialog.open({ - title: 'Delete table?', - description: - 'Table deletion cannot be undone. All views and joins related to this table will be deleted.', + render: (_, item: DatabaseSchemaDataWarehouseTable) => ( +
+ refreshSelfManagedTableSchema(item.id)} + > + Update schema from source + + { + LemonDialog.open({ + title: 'Delete table?', + description: + 'Table deletion cannot be undone. All views and joins related to this table will be deleted.', - primaryButton: { - children: 'Delete', - status: 'danger', - onClick: () => { - deleteSelfManagedTable(item.id) - }, - }, - secondaryButton: { - children: 'Cancel', + primaryButton: { + children: 'Delete', + status: 'danger', + onClick: () => { + deleteSelfManagedTable(item.id) }, - }) - }} - > - Delete - -
- ) - }, + }, + secondaryButton: { + children: 'Cancel', + }, + }) + }} + > + Delete +
+
+ ), }, ]} /> diff --git a/frontend/src/scenes/data-warehouse/settings/DataWarehouseSourceIcon.tsx b/frontend/src/scenes/data-warehouse/settings/DataWarehouseSourceIcon.tsx new file mode 100644 index 0000000000000..4dca0f4e269ab --- /dev/null +++ b/frontend/src/scenes/data-warehouse/settings/DataWarehouseSourceIcon.tsx @@ -0,0 +1,95 @@ +import { Link } from 'lib/lemon-ui/Link' +import { Tooltip } from 'lib/lemon-ui/Tooltip' +import BlushingHog from 'public/hedgehog/blushing-hog.png' +import IconAwsS3 from 'public/services/aws-s3.png' +import Iconazure from 'public/services/azure.png' +import IconBigQuery from 'public/services/bigquery.png' +import IconChargebee from 'public/services/chargebee.png' +import IconCloudflare from 'public/services/cloudflare.png' +import IconGoogleCloudStorage from 'public/services/google-cloud-storage.png' +import IconHubspot from 'public/services/hubspot.png' +import IconMySQL from 'public/services/mysql.png' +import IconPostgres from 'public/services/postgres.png' +import IconSalesforce from 'public/services/salesforce.png' +import IconSnowflake from 'public/services/snowflake.png' +import IconMSSQL from 'public/services/sql-azure.png' +import IconStripe from 'public/services/stripe.png' +import IconVitally from 'public/services/vitally.png' +import IconZendesk from 'public/services/zendesk.png' +import { getDataWarehouseSourceUrl } from 'scenes/data-warehouse/settings/DataWarehouseManagedSourcesTable' + +/** + * In some cases we don't have the backend telling us what provider we have for blob storage, so we can have some + * heuristic to guess, then fallback to a shrugging hedgehog. + * @param url + */ +export function mapUrlToProvider(url: string): string { + if (url.includes('.s3.amazonaws.com')) { + return 'aws' + } else if (url.startsWith('https://storage.googleapis.com')) { + return 'google-cloud' + } else if (url.includes('.blob.')) { + return 'azure' + } else if (url.includes('.r2.cloudflarestorage.com')) { + return 'cloudflare-r2' + } + return 'BlushingHog' +} + +/** + * DataWarehouseSourceIcon component to render an icon + * @param type + * @param size + */ +export function DataWarehouseSourceIcon({ + type, + size = 'small', +}: { + type: string + size?: 'small' | 'medium' +}): JSX.Element { + const sizePx = size === 'small' ? 30 : 60 + + const icon = { + Stripe: IconStripe, + Hubspot: IconHubspot, + Zendesk: IconZendesk, + Postgres: IconPostgres, + MySQL: IconMySQL, + Snowflake: IconSnowflake, + aws: IconAwsS3, + 'google-cloud': IconGoogleCloudStorage, + 'cloudflare-r2': IconCloudflare, + azure: Iconazure, + Salesforce: IconSalesforce, + MSSQL: IconMSSQL, + Vitally: IconVitally, + BigQuery: IconBigQuery, + Chargebee: IconChargebee, + BlushingHog: BlushingHog, // fallback, we don't know what this is + }[type] + + return ( +
+ + {type} +
+ Click to view docs + + } + > + + {type} + +
+
+ ) +} diff --git a/frontend/src/scenes/data-warehouse/settings/dataWarehouseSettingsLogic.ts b/frontend/src/scenes/data-warehouse/settings/dataWarehouseSettingsLogic.ts index c0adf6907c65d..2242bfb47c641 100644 --- a/frontend/src/scenes/data-warehouse/settings/dataWarehouseSettingsLogic.ts +++ b/frontend/src/scenes/data-warehouse/settings/dataWarehouseSettingsLogic.ts @@ -7,23 +7,12 @@ import posthog from 'posthog-js' import { databaseTableListLogic } from 'scenes/data-management/database/databaseTableListLogic' import { DatabaseSchemaDataWarehouseTable } from '~/queries/schema' -import { DataWarehouseSettingsTab, ExternalDataSource, ExternalDataSourceSchema } from '~/types' +import { ExternalDataSource, ExternalDataSourceSchema } from '~/types' import type { dataWarehouseSettingsLogicType } from './dataWarehouseSettingsLogicType' const REFRESH_INTERVAL = 10000 -export interface DataWarehouseSource {} - -export const humanFriendlyDataWarehouseSettingsTabName = (tab: DataWarehouseSettingsTab): string => { - switch (tab) { - case DataWarehouseSettingsTab.Managed: - return 'Managed' - case DataWarehouseSettingsTab.SelfManaged: - return 'Self managed' - } -} - export const dataWarehouseSettingsLogic = kea([ path(['scenes', 'data-warehouse', 'settings', 'dataWarehouseSettingsLogic']), connect(() => ({ diff --git a/frontend/src/scenes/pipeline/PipelineNode.tsx b/frontend/src/scenes/pipeline/PipelineNode.tsx index 389b5f621802c..d1f473424fd02 100644 --- a/frontend/src/scenes/pipeline/PipelineNode.tsx +++ b/frontend/src/scenes/pipeline/PipelineNode.tsx @@ -10,6 +10,7 @@ import { Schemas } from 'scenes/data-warehouse/settings/source/Schemas' import { SourceConfiguration } from 'scenes/data-warehouse/settings/source/SourceConfiguration' import { Syncs } from 'scenes/data-warehouse/settings/source/Syncs' import { PipelineNodeLogs } from 'scenes/pipeline/PipelineNodeLogs' +import { SelfManaged } from 'scenes/pipeline/sources/SelfManaged' import { SceneExport } from 'scenes/sceneTypes' import { urls } from 'scenes/urls' @@ -70,7 +71,8 @@ export function PipelineNode(params: { stage?: string; id?: string } = {}): JSX. ? { [PipelineNodeTab.SourceConfiguration]: } : {}), } - : { + : node.backend !== PipelineBackend.SelfManagedSource + ? { [PipelineNodeTab.Configuration]: , [PipelineNodeTab.Metrics]: node.backend === PipelineBackend.HogFunction ? ( @@ -80,12 +82,17 @@ export function PipelineNode(params: { stage?: string; id?: string } = {}): JSX. ), [PipelineNodeTab.Logs]: , } + : {} if (node.backend === PipelineBackend.BatchExport) { tabToContent[PipelineNodeTab.Runs] = tabToContent[PipelineNodeTab.Backfills] = } + if (node.backend === PipelineBackend.SelfManagedSource) { + tabToContent[PipelineNodeTab.SourceConfiguration] = + } + if (node.backend === PipelineBackend.Plugin) { tabToContent[PipelineNodeTab.History] = } diff --git a/frontend/src/scenes/pipeline/PipelineNodeConfiguration.tsx b/frontend/src/scenes/pipeline/PipelineNodeConfiguration.tsx index 1202b30550df8..71b7ed4ae413c 100644 --- a/frontend/src/scenes/pipeline/PipelineNodeConfiguration.tsx +++ b/frontend/src/scenes/pipeline/PipelineNodeConfiguration.tsx @@ -21,7 +21,7 @@ export function PipelineNodeConfiguration(): JSX.Element { ) : node.backend === PipelineBackend.Plugin ? ( ) : ( - + )}
) diff --git a/frontend/src/scenes/pipeline/pipelineNodeLogic.tsx b/frontend/src/scenes/pipeline/pipelineNodeLogic.tsx index 2d2e7b977aec5..c6207a30c1e72 100644 --- a/frontend/src/scenes/pipeline/pipelineNodeLogic.tsx +++ b/frontend/src/scenes/pipeline/pipelineNodeLogic.tsx @@ -33,7 +33,16 @@ type ManagedSourceId = { backend: PipelineBackend.ManagedSource id: string } -export type PipelineNodeLimitedType = PluginNodeId | BatchExportNodeId | HogFunctionNodeId | ManagedSourceId +type DataWarehouseId = { + backend: PipelineBackend.SelfManagedSource + id: number | string +} +export type PipelineNodeLimitedType = + | PluginNodeId + | BatchExportNodeId + | HogFunctionNodeId + | ManagedSourceId + | DataWarehouseId export const pipelineNodeLogic = kea([ props({} as PipelineNodeLogicProps), @@ -110,6 +119,10 @@ export const pipelineNodeLogic = kea([ return { backend: PipelineBackend.ManagedSource, id: `${id}`.replace('managed-', '') } } + if (id.indexOf('self-managed') === 0) { + return { backend: PipelineBackend.SelfManagedSource, id: `${id}`.replace('self-managed-', '') } + } + return { backend: PipelineBackend.BatchExport, id } } diff --git a/frontend/src/scenes/pipeline/pipelineNodeLogsLogic.tsx b/frontend/src/scenes/pipeline/pipelineNodeLogsLogic.tsx index 3eba926a3ae0c..88cd0bd13fcea 100644 --- a/frontend/src/scenes/pipeline/pipelineNodeLogsLogic.tsx +++ b/frontend/src/scenes/pipeline/pipelineNodeLogsLogic.tsx @@ -56,7 +56,7 @@ export const pipelineNodeLogsLogic = kea([ // handled in data warehouse specific component return [] } else { - results = await api.pluginConfigs.logs(values.node.id, logParams) + results = await api.pluginConfigs.logs(Number(values.node.id), logParams) } if (!cache.pollingInterval) { @@ -84,7 +84,7 @@ export const pipelineNodeLogsLogic = kea([ // handled in data warehouse specific component return [] } else { - results = await api.pluginConfigs.logs(values.node.id, logParams) + results = await api.pluginConfigs.logs(Number(values.node.id), logParams) } if (results.length < LOGS_PORTION_LIMIT) { @@ -128,7 +128,7 @@ export const pipelineNodeLogsLogic = kea([ // handled in data warehouse specific component return [] } else { - results = await api.pluginConfigs.logs(values.node.id, logParams) + results = await api.pluginConfigs.logs(Number(values.node.id), logParams) } return [...results, ...values.backgroundLogs] diff --git a/frontend/src/scenes/pipeline/sources/SelfManaged.tsx b/frontend/src/scenes/pipeline/sources/SelfManaged.tsx new file mode 100644 index 0000000000000..f65b1bfde23ee --- /dev/null +++ b/frontend/src/scenes/pipeline/sources/SelfManaged.tsx @@ -0,0 +1,73 @@ +import { LemonButton } from '@posthog/lemon-ui' +import { BindLogic, useActions, useValues } from 'kea' +import { router } from 'kea-router' +import { PageHeader } from 'lib/components/PageHeader' +import { DatawarehouseTableForm } from 'scenes/data-warehouse/new/DataWarehouseTableForm' +import { dataWarehouseTableLogic } from 'scenes/data-warehouse/new/dataWarehouseTableLogic' +import { urls } from 'scenes/urls' + +import { DataWarehouseTable } from '~/types' + +interface SelfManagedProps { + id: string +} + +export const SelfManaged = ({ id }: SelfManagedProps): JSX.Element => { + const { table } = useValues(dataWarehouseTableLogic({ id })) + const { updateTable, editingTable } = useActions(dataWarehouseTableLogic({ id })) + + return ( + + + + ) +} + +interface Props { + table: DataWarehouseTable + updateTable: (tablePayload: any) => void + editingTable: (editing: boolean) => void +} + +export function SelfManagedTable({ table, updateTable, editingTable }: Props): JSX.Element { + return ( + <> + { + editingTable(false) + router.actions.push(urls.pipeline()) + }} + > + Cancel + + } + /> +
+ + updateTable({ + name: table.name, + url_pattern: table.url_pattern, + format: table.format, + ...(table.credential?.access_key || table.credential?.access_secret + ? { + credential: { + ...(table.credential.access_key + ? { access_key: table.credential.access_key } + : {}), + ...(table.credential.access_secret + ? { access_secret: table.credential.access_secret } + : {}), + }, + } + : {}), + }) + } + /> +
+ + ) +} diff --git a/frontend/src/scenes/pipeline/types.ts b/frontend/src/scenes/pipeline/types.ts index ff7639f2ffcc1..f417c3df22183 100644 --- a/frontend/src/scenes/pipeline/types.ts +++ b/frontend/src/scenes/pipeline/types.ts @@ -12,6 +12,7 @@ export enum PipelineBackend { Plugin = 'plugin', HogFunction = 'hog_function', ManagedSource = 'managed_source', + SelfManagedSource = 'self_managed', } // Base - we're taking a discriminated union approach here, so that TypeScript can discern types for free diff --git a/posthog/warehouse/api/table.py b/posthog/warehouse/api/table.py index b99fa590f3c53..c55cb4fc77438 100644 --- a/posthog/warehouse/api/table.py +++ b/posthog/warehouse/api/table.py @@ -192,6 +192,21 @@ def destroy(self, request: request.Request, *args: Any, **kwargs: Any) -> respon return response.Response(status=status.HTTP_204_NO_CONTENT) + def perform_update(self, serializer): + instance = serializer.instance + validated_data = serializer.validated_data + + credential_data = validated_data.pop("credential", None) + if credential_data: + credential = instance.credential + credential.access_key = credential_data.get("access_key", credential.access_key) + credential.access_secret = credential_data.get("access_secret", credential.access_secret) + credential.save() + + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.save() + @action(methods=["POST"], detail=True) def update_schema(self, request: request.Request, *args: Any, **kwargs: Any) -> response.Response: updates = request.data.get("updates", None)