diff --git a/frontend/src/lib/utils/dataTableTransformations.ts b/frontend/src/lib/utils/dataTableTransformations.ts new file mode 100644 index 0000000000000..edf4eedabcc7c --- /dev/null +++ b/frontend/src/lib/utils/dataTableTransformations.ts @@ -0,0 +1,14 @@ +import { DataTableRow } from '~/queries/nodes/DataTable/dataTableLogic' + +/** + * Transforms DataTable format back to DataTableRow format for clipboard operations + */ +export function transformDataTableToDataTableRows(rows: Record[], columns: string[]): DataTableRow[] { + if (!columns.length || !rows.length) { + return [] + } + + return rows.map((row) => ({ + result: columns.map((col) => row[col]), + })) +} diff --git a/frontend/src/scenes/data-warehouse/editor/OutputPane.tsx b/frontend/src/scenes/data-warehouse/editor/OutputPane.tsx index bfa24eae45903..cffc0e273be5a 100644 --- a/frontend/src/scenes/data-warehouse/editor/OutputPane.tsx +++ b/frontend/src/scenes/data-warehouse/editor/OutputPane.tsx @@ -20,7 +20,7 @@ import { IconPlus, IconShare, } from '@posthog/icons' -import { LemonButton, LemonDivider, LemonModal, LemonTable, Tooltip } from '@posthog/lemon-ui' +import { LemonButton, LemonDivider, LemonMenu, LemonModal, LemonTable, Tooltip } from '@posthog/lemon-ui' import { ExportButton } from 'lib/components/ExportButton/ExportButton' import { JSONViewer } from 'lib/components/JSONViewer' @@ -30,6 +30,7 @@ import { LoadingBar } from 'lib/lemon-ui/LoadingBar' import { IconTableChart } from 'lib/lemon-ui/icons' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { copyToClipboard } from 'lib/utils/copyToClipboard' +import { transformDataTableToDataTableRows } from 'lib/utils/dataTableTransformations' import { InsightErrorState, StatelessInsightLoadingState } from 'scenes/insights/EmptyStates' import { HogQLBoldNumber } from 'scenes/insights/views/BoldNumber/BoldNumber' @@ -49,9 +50,11 @@ import { DataTableVisualizationProps } from '~/queries/nodes/DataVisualization/D import { dataVisualizationLogic } from '~/queries/nodes/DataVisualization/dataVisualizationLogic' import { displayLogic } from '~/queries/nodes/DataVisualization/displayLogic' import { renderHogQLX } from '~/queries/nodes/HogQLX/render' +import { type DataTableNode, NodeKind } from '~/queries/schema/schema-general' import { HogQLQueryResponse } from '~/queries/schema/schema-general' import { ChartDisplayType, ExporterFormat } from '~/types' +import { copyTableToCsv, copyTableToExcel, copyTableToJson } from '../../../queries/nodes/DataTable/clipboardUtils' import TabScroller from './TabScroller' import { FixErrorButton } from './components/FixErrorButton' import { multitabEditorLogic } from './multitabEditorLogic' @@ -96,6 +99,29 @@ const CLICKHOUSE_TYPES = [ 'FixedString', ] +const copyMap = { + [ExporterFormat.CSV]: { + label: 'CSV', + copyFn: copyTableToCsv, + }, + [ExporterFormat.JSON]: { + label: 'JSON', + copyFn: copyTableToJson, + }, + [ExporterFormat.XLSX]: { + label: 'Excel', + copyFn: copyTableToExcel, + }, +} + +const createDataTableQuery = (): DataTableNode => ({ + kind: NodeKind.DataTableNode, + source: { + kind: NodeKind.HogQLQuery, + query: '', + }, +}) + const cleanClickhouseType = (type: string | undefined): string | undefined => { if (!type) { return undefined @@ -564,6 +590,28 @@ export function OutputPane({ tabId }: { tabId: string }): JSX.Element { {editingInsight ? 'View insight' : 'Create insight'} )} + {activeTab === OutputTab.Results && ( + ({ + label, + onClick: () => { + if (response?.columns && rows.length > 0) { + const dataTableRows = transformDataTableToDataTableRows(rows, response.columns) + const query = createDataTableQuery() + copyFn(dataTableRows, response.columns, query) + } + }, + }))} + placement="bottom-end" + > + } + /> + + )} {activeTab === OutputTab.Results && exportContext && (