diff --git a/frontend/src/lib/components/DatabaseTableTree/DatabaseTableTree.tsx b/frontend/src/lib/components/DatabaseTableTree/DatabaseTableTree.tsx index 64f984eff8040..caf49ffd64108 100644 --- a/frontend/src/lib/components/DatabaseTableTree/DatabaseTableTree.tsx +++ b/frontend/src/lib/components/DatabaseTableTree/DatabaseTableTree.tsx @@ -14,6 +14,8 @@ export type TreeItem = TreeItemFolder | TreeItemLeaf | TreeTableItemLeaf export interface TreeItemFolder { name: string + table?: DatabaseSchemaTable + dropdownOverlay?: React.ReactNode items: TreeItem[] emptyLabel?: JSX.Element isLoading?: boolean @@ -38,6 +40,7 @@ export function DatabaseTableTree({ items, onSelectRow, selectedRow, depth = 1, return ( void selectedRow?: DatabaseSchemaTable | null + dropdownOverlay?: React.ReactNode } -export function TreeFolderRow({ item, depth, onClick, selectedRow }: TreeFolderRowProps): JSX.Element { +export function TreeFolderRow({ item, depth, onClick, selectedRow, dropdownOverlay }: TreeFolderRowProps): JSX.Element { const { name, items, emptyLabel } = item const isColumnType = items.length > 0 && 'type' in items[0] @@ -77,6 +78,17 @@ export function TreeFolderRow({ item, depth, onClick, selectedRow }: TreeFolderR size="small" fullWidth onClick={_onClick} + sideAction={ + dropdownOverlay + ? { + icon: , + + dropdown: { + overlay: dropdownOverlay, + }, + } + : undefined + } icon={} > {name} diff --git a/frontend/src/queries/nodes/HogQLQuery/HogQLQueryEditor.tsx b/frontend/src/queries/nodes/HogQLQuery/HogQLQueryEditor.tsx index 58708e49afc53..a066925e6a9e8 100644 --- a/frontend/src/queries/nodes/HogQLQuery/HogQLQueryEditor.tsx +++ b/frontend/src/queries/nodes/HogQLQuery/HogQLQueryEditor.tsx @@ -416,7 +416,7 @@ export function HogQLQueryEditor(props: HogQLQueryEditorProps): JSX.Element { } data-attr="hogql-query-editor-save-as-view" > - Save as View + Save as view ) : null} {featureFlags[FEATURE_FLAGS.DATA_WAREHOUSE] && ( diff --git a/frontend/src/scenes/data-management/database/DatabaseTables.tsx b/frontend/src/scenes/data-management/database/DatabaseTables.tsx index 52175f19b6538..99c712b6ff3ad 100644 --- a/frontend/src/scenes/data-management/database/DatabaseTables.tsx +++ b/frontend/src/scenes/data-management/database/DatabaseTables.tsx @@ -7,7 +7,6 @@ import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { humanFriendlyDetailedTime } from 'lib/utils' import { databaseTableListLogic } from 'scenes/data-management/database/databaseTableListLogic' import { viewLinkLogic } from 'scenes/data-warehouse/viewLinkLogic' -import { ViewLinkModal } from 'scenes/data-warehouse/ViewLinkModal' import { urls } from 'scenes/urls' import { DatabaseSchemaTable, DataTableNode, NodeKind } from '~/queries/schema' @@ -49,7 +48,6 @@ export function DatabaseTablesContainer(): JSX.Element { ) }} /> - ) } diff --git a/frontend/src/scenes/data-warehouse/external/DataWarehouseExternalScene.tsx b/frontend/src/scenes/data-warehouse/external/DataWarehouseExternalScene.tsx index 87b20f8ccb815..aaed2281b1ced 100644 --- a/frontend/src/scenes/data-warehouse/external/DataWarehouseExternalScene.tsx +++ b/frontend/src/scenes/data-warehouse/external/DataWarehouseExternalScene.tsx @@ -1,10 +1,12 @@ import { IconGear } from '@posthog/icons' import { LemonButton, Link } from '@posthog/lemon-ui' -import { useValues } from 'kea' +import { BindLogic, useActions, useValues } from 'kea' import { router } from 'kea-router' import { PageHeader } from 'lib/components/PageHeader' -import { FEATURE_FLAGS } from 'lib/constants' -import { featureFlagLogic } from 'lib/logic/featureFlagLogic' +import { insightDataLogic } from 'scenes/insights/insightDataLogic' +import { insightLogic } from 'scenes/insights/insightLogic' +import { InsightSaveButton } from 'scenes/insights/InsightSaveButton' +import { insightSceneLogic } from 'scenes/insights/insightSceneLogic' import { SceneExport } from 'scenes/sceneTypes' import { urls } from 'scenes/urls' @@ -18,30 +20,36 @@ export const scene: SceneExport = { } export function DataWarehouseExternalScene(): JSX.Element { - const { featureFlags } = useValues(featureFlagLogic) + const { insightProps, insightChanged, insightSaving, hasDashboardItemId } = useValues( + insightLogic({ + dashboardItemId: 'new', + cachedInsight: null, + }) + ) + + const { saveInsight, saveAs } = useActions(insightDataLogic(insightProps)) return (
- {featureFlags[FEATURE_FLAGS.DATA_WAREHOUSE] && ( - - Create View - - )} + + - Link Source + Link source - + + +
) } diff --git a/frontend/src/scenes/data-warehouse/external/DataWarehouseTables.tsx b/frontend/src/scenes/data-warehouse/external/DataWarehouseTables.tsx index 802eaa49161ce..f5b61e31951e4 100644 --- a/frontend/src/scenes/data-warehouse/external/DataWarehouseTables.tsx +++ b/frontend/src/scenes/data-warehouse/external/DataWarehouseTables.tsx @@ -1,27 +1,57 @@ -import { IconBrackets, IconChevronDown, IconDatabase } from '@posthog/icons' -import { LemonButton, Link } from '@posthog/lemon-ui' +import { IconBrackets, IconChevronDown, IconDatabase, IconGear } from '@posthog/icons' +import { LemonButton, LemonModal } from '@posthog/lemon-ui' import { clsx } from 'clsx' -import { useActions, useValues } from 'kea' +import { BindLogic, useActions, useValues } from 'kea' +import { router } from 'kea-router' import { DatabaseTableTree, TreeItem } from 'lib/components/DatabaseTableTree/DatabaseTableTree' import { FEATURE_FLAGS } from 'lib/constants' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { useState } from 'react' +import { insightDataLogic } from 'scenes/insights/insightDataLogic' +import { insightLogic } from 'scenes/insights/insightLogic' import { urls } from 'scenes/urls' +import { Query } from '~/queries/Query/Query' +import { DatabaseSchemaTable } from '~/queries/schema' + +import { viewLinkLogic } from '../viewLinkLogic' import { ViewLinkModal } from '../ViewLinkModal' import { dataWarehouseSceneLogic } from './dataWarehouseSceneLogic' -import { TableData } from './TableData' +import { DeleteTableModal, TableData } from './TableData' export const DataWarehouseTables = (): JSX.Element => { + // insightLogic + const logic = insightLogic({ + dashboardItemId: 'new', + cachedInsight: null, + }) + const { insightProps } = useValues(logic) + // insightDataLogic + const { query } = useValues( + insightDataLogic({ + ...insightProps, + }) + ) + + const { setQuery: setInsightQuery } = useActions(insightDataLogic(insightProps)) + return ( <> -
- -
- + +
+
-
- + ) } @@ -31,11 +61,68 @@ interface DatabaseTableTreeProps { } export const DatabaseTableTreeWithItems = ({ inline }: DatabaseTableTreeProps): JSX.Element => { - const { dataWarehouseTablesBySourceType, posthogTables, databaseLoading, views, selectedRow } = + const { dataWarehouseTablesBySourceType, posthogTables, databaseLoading, views, selectedRow, schemaModalIsOpen } = useValues(dataWarehouseSceneLogic) - const { selectRow } = useActions(dataWarehouseSceneLogic) + const { selectRow, deleteDataWarehouseSavedQuery, deleteDataWarehouseTable, toggleSchemaModal } = + useActions(dataWarehouseSceneLogic) const { featureFlags } = useValues(featureFlagLogic) const [collapsed, setCollapsed] = useState(false) + const { toggleJoinTableModal, selectSourceTable } = useActions(viewLinkLogic) + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) + + const deleteButton = (table: DatabaseSchemaTable | null): JSX.Element => { + if (!table) { + return <> + } + + if (table.type === 'view' || table.type === 'data_warehouse') { + return ( + { + selectRow(table) + setIsDeleteModalOpen(true) + }} + fullWidth + > + Delete + + ) + } + + if (table.type === 'posthog') { + return <> + } + + return <> + } + + const dropdownOverlay = (table: DatabaseSchemaTable): JSX.Element => ( + <> + { + selectRow(table) + toggleSchemaModal() + }} + data-attr="schema-list-item-schema" + fullWidth + > + View table schema + + { + selectSourceTable(table.name) + toggleJoinTableModal() + }} + data-attr="schema-list-item-join" + fullWidth + > + Add join + + {deleteButton(table)} + + ) const treeItems = (): TreeItem[] => { if (inline) { @@ -46,6 +133,8 @@ export const DatabaseTableTreeWithItems = ({ inline }: DatabaseTableTreeProps): name: source_type, items: dataWarehouseTablesBySourceType[source_type].map((table) => ({ name: table.name, + table: table, + dropdownOverlay: dropdownOverlay(table), items: Object.values(table.fields).map((column) => ({ name: column.name, type: column.type, @@ -53,17 +142,15 @@ export const DatabaseTableTreeWithItems = ({ inline }: DatabaseTableTreeProps): })), })), })), - emptyLabel: ( - - No tables found. Link source - - ), + emptyLabel: No tables found, isLoading: databaseLoading, }, { name: 'PostHog', items: posthogTables.map((table) => ({ name: table.name, + table: table, + dropdownOverlay: dropdownOverlay(table), items: Object.values(table.fields).map((column) => ({ name: column.name, type: column.type, @@ -79,6 +166,8 @@ export const DatabaseTableTreeWithItems = ({ inline }: DatabaseTableTreeProps): name: 'Views', items: views.map((table) => ({ name: table.name, + table: table, + dropdownOverlay: dropdownOverlay(table), items: Object.values(table.fields).map((column) => ({ name: column.name, type: column.type, @@ -103,11 +192,7 @@ export const DatabaseTableTreeWithItems = ({ inline }: DatabaseTableTreeProps): icon: , })), })), - emptyLabel: ( - - No tables found. Link source - - ), + emptyLabel: No tables found, isLoading: databaseLoading, }, { @@ -150,13 +235,63 @@ export const DatabaseTableTreeWithItems = ({ inline }: DatabaseTableTreeProps): size="xsmall" onClick={() => setCollapsed(true)} fullWidth - sideIcon={} + sideIcon={ +
+ { + e.stopPropagation() + router.actions.push(urls.dataWarehouseTable()) + }} + type="primary" + size="xsmall" + > + Link source + + } + data-attr="new-data-warehouse-settings-link" + key="new-data-warehouse-settings-link" + onClick={() => router.actions.push(urls.dataWarehouseSettings())} + /> + +
+ } > Schemas )} + { + selectRow(null) + toggleSchemaModal() + }} + title="Table Schema" + > + + + + {selectedRow && ( + { + if (selectedRow) { + if (selectedRow.type === 'view') { + deleteDataWarehouseSavedQuery(selectedRow.id) + } else { + deleteDataWarehouseTable(selectedRow.id) + } + } + }} + /> + )}
) } diff --git a/frontend/src/scenes/data-warehouse/external/TableData.tsx b/frontend/src/scenes/data-warehouse/external/TableData.tsx index f18d4a26a4630..cddabcc609ba3 100644 --- a/frontend/src/scenes/data-warehouse/external/TableData.tsx +++ b/frontend/src/scenes/data-warehouse/external/TableData.tsx @@ -1,17 +1,15 @@ import { IconDatabase } from '@posthog/icons' -import { LemonButton, LemonModal, Link } from '@posthog/lemon-ui' +import { LemonButton, LemonModal } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { capitalizeFirstLetter } from 'kea-forms' -import { EmptyMessage } from 'lib/components/EmptyMessage/EmptyMessage' import { humanFriendlyDetailedTime } from 'lib/utils' import { Dispatch, SetStateAction, useEffect, useState } from 'react' import { DatabaseTable } from 'scenes/data-management/database/DatabaseTable' -import { urls } from 'scenes/urls' import { HogQLQueryEditor } from '~/queries/nodes/HogQLQuery/HogQLQueryEditor' import { DatabaseSchemaTable, HogQLQuery, NodeKind } from '~/queries/schema' -import { viewLinkLogic } from '../viewLinkLogic' +import { ViewLinkModal } from '../ViewLinkModal' import { dataWarehouseSceneLogic } from './dataWarehouseSceneLogic' export function TableData(): JSX.Element { @@ -32,7 +30,6 @@ export function TableData(): JSX.Element { saveSchema, cancelEditSchema, } = useActions(dataWarehouseSceneLogic) - const { toggleJoinTableModal, selectSourceTable } = useActions(viewLinkLogic) const [localQuery, setLocalQuery] = useState() const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) @@ -45,31 +42,6 @@ export function TableData(): JSX.Element { } }, [table]) - const deleteButton = (selectedRow: DatabaseSchemaTable | null): JSX.Element => { - if (!selectedRow) { - return <> - } - - if (selectedRow.type === 'view' || selectedRow.type === 'data_warehouse') { - return ( - { - setIsDeleteModalOpen(true) - }} - > - Delete - - ) - } - - if (selectedRow.type === 'posthog') { - return <> - } - - return <> - } - return (
{table ? ( @@ -109,16 +81,6 @@ export function TableData(): JSX.Element { )} {!inEditSchemaMode && !isEditingSavedQuery && (
- {deleteButton(table)} - { - selectSourceTable(table.name) - toggleJoinTableModal() - }} - > - Add join - {isManuallyLinkedTable && ( )} - - !table && !fields && !chain && schema_valid - ) - .map(({ hogql_value }) => hogql_value)} FROM ${ - table.name - } LIMIT 100`, - }, - }) - )} - > - - Query - - {table.type === 'view' && ( setIsEditingSavedQuery(true)}> Edit @@ -241,7 +177,7 @@ export function TableData(): JSX.Element { } data-attr="hogql-query-editor-save-as-view" > - Save as View + Save as view )} /> @@ -249,14 +185,7 @@ export function TableData(): JSX.Element { )} ) : ( -
- -
+
)} {table && ( )} +
) } diff --git a/frontend/src/scenes/data-warehouse/external/dataWarehouseSceneLogic.ts b/frontend/src/scenes/data-warehouse/external/dataWarehouseSceneLogic.ts index 35e62f96f4ea3..19d12fcf24fce 100644 --- a/frontend/src/scenes/data-warehouse/external/dataWarehouseSceneLogic.ts +++ b/frontend/src/scenes/data-warehouse/external/dataWarehouseSceneLogic.ts @@ -36,6 +36,7 @@ export const dataWarehouseSceneLogic = kea([ setEditSchemaIsLoading: (isLoading: boolean) => ({ isLoading }), cancelEditSchema: () => ({ database: values.database }), deleteDataWarehouseTable: (tableId: string) => ({ tableId }), + toggleSchemaModal: true, })), reducers({ selectedRow: [ @@ -122,6 +123,12 @@ export const dataWarehouseSceneLogic = kea([ loadDatabaseFailure: () => false, }, ], + schemaModalIsOpen: [ + false, + { + toggleSchemaModal: (state) => !state, + }, + ], }), selectors({ dataWarehouseTablesBySourceType: [ diff --git a/frontend/src/scenes/data-warehouse/settings/DataWarehouseSettingsScene.tsx b/frontend/src/scenes/data-warehouse/settings/DataWarehouseSettingsScene.tsx index 2f89fd3032e4e..8ca757a11d9a5 100644 --- a/frontend/src/scenes/data-warehouse/settings/DataWarehouseSettingsScene.tsx +++ b/frontend/src/scenes/data-warehouse/settings/DataWarehouseSettingsScene.tsx @@ -23,7 +23,7 @@ export function DataWarehouseSettingsScene(): JSX.Element { key="new-data-warehouse-easy-link" to={urls.dataWarehouseTable()} > - Link Source + Link source
} caption={ diff --git a/frontend/src/scenes/data-warehouse/settings/DataWarehouseSourcesTable.tsx b/frontend/src/scenes/data-warehouse/settings/DataWarehouseSourcesTable.tsx index 61967a42e4d26..99f92ad4a543d 100644 --- a/frontend/src/scenes/data-warehouse/settings/DataWarehouseSourcesTable.tsx +++ b/frontend/src/scenes/data-warehouse/settings/DataWarehouseSourcesTable.tsx @@ -333,7 +333,7 @@ const SchemaTable = ({ schemas }: SchemaTableProps): JSX.Element => { }, } return ( - + {schema.table.name} ) diff --git a/frontend/src/scenes/insights/insightSceneLogic.tsx b/frontend/src/scenes/insights/insightSceneLogic.tsx index d5414716a2448..1f0c062895f74 100644 --- a/frontend/src/scenes/insights/insightSceneLogic.tsx +++ b/frontend/src/scenes/insights/insightSceneLogic.tsx @@ -16,6 +16,7 @@ import { urls } from 'scenes/urls' import { ActivityFilters } from '~/layout/navigation-3000/sidepanel/panels/activity/activityForSceneLogic' import { cohortsModel } from '~/models/cohortsModel' import { groupsModel } from '~/models/groupsModel' +import { examples } from '~/queries/examples' import { getQueryBasedInsightModel } from '~/queries/nodes/InsightViz/utils' import { ActivityScope, Breadcrumb, FilterType, InsightShortId, InsightType, ItemMode } from '~/types' @@ -175,6 +176,20 @@ export const insightSceneLogic = kea([ setSceneState: sharedListeners.reloadInsightLogic, })), urlToAction(({ actions, values }) => ({ + '/data-warehouse': (_, __, { q }) => { + actions.setSceneState(String('new') as InsightShortId, ItemMode.Edit, undefined) + values.insightDataLogicRef?.logic.actions.setQuery(examples.DataVisualization) + values.insightLogicRef?.logic.actions.setInsight( + { + ...createEmptyInsight('new', false), + ...(q ? { query: JSON.parse(q) } : {}), + }, + { + fromPersistentApi: false, + overrideFilter: false, + } + ) + }, '/insights/:shortId(/:mode)(/:subscriptionId)': ( { shortId, mode, subscriptionId }, // url params { dashboard, ...searchParams }, // search params diff --git a/frontend/src/scenes/urls.ts b/frontend/src/scenes/urls.ts index c5c68db5b337c..cb300d96749ad 100644 --- a/frontend/src/scenes/urls.ts +++ b/frontend/src/scenes/urls.ts @@ -155,7 +155,9 @@ export const urls = { /** @param id A UUID or 'new'. ':id' for routing. */ survey: (id: string): string => `/surveys/${id}`, surveyTemplates: (): string => '/survey_templates', - dataWarehouse: (): string => '/data-warehouse', + dataWarehouse: (query?: string | Record): string => + combineUrl('/data-warehouse', {}, query ? { q: typeof query === 'string' ? query : JSON.stringify(query) } : {}) + .url, dataWarehouseTable: (): string => `/data-warehouse/new`, dataWarehouseSettings: (): string => '/data-warehouse/settings', dataWarehouseRedirect: (kind: string): string => `/data-warehouse/${kind}/redirect`,