diff --git a/frontend/src/layout/navigation-3000/navigationLogic.tsx b/frontend/src/layout/navigation-3000/navigationLogic.tsx index 8507fe104dade..5467937a674f6 100644 --- a/frontend/src/layout/navigation-3000/navigationLogic.tsx +++ b/frontend/src/layout/navigation-3000/navigationLogic.tsx @@ -512,7 +512,7 @@ export const navigation3000Logic = kea([ featureFlags[FEATURE_FLAGS.SQL_EDITOR] ? { identifier: Scene.SQLEditor, - label: 'Data warehouse 3000', + label: 'SQL Editor', icon: , to: urls.sqlEditor(), logic: editorSidebarLogic, diff --git a/frontend/src/lib/monaco/CodeEditor.tsx b/frontend/src/lib/monaco/CodeEditor.tsx index aaa5303d35451..f824b982d7f63 100644 --- a/frontend/src/lib/monaco/CodeEditor.tsx +++ b/frontend/src/lib/monaco/CodeEditor.tsx @@ -32,9 +32,15 @@ export interface CodeEditorProps extends Omit sourceQuery?: AnyDataNode globals?: Record schema?: Record | null + + onError?: (error: string | null, isValidView: boolean) => void } let codeEditorIndex = 0 +export function initModel(model: editor.ITextModel, builtCodeEditorLogic: BuiltLogic): void { + ;(model as any).codeEditorLogic = builtCodeEditorLogic +} + function initEditor( monaco: Monaco, editor: importedEditor.IStandaloneCodeEditor, @@ -44,7 +50,9 @@ function initEditor( ): void { // This gives autocomplete access to the specific editor const model = editor.getModel() - ;(model as any).codeEditorLogic = builtCodeEditorLogic + if (model) { + initModel(model, builtCodeEditorLogic) + } if (editorProps?.language === 'hog') { initHogLanguage(monaco) @@ -112,6 +120,7 @@ export function CodeEditor({ globals, sourceQuery, schema, + onError, ...editorProps }: CodeEditorProps): JSX.Element { const { isDarkModeOn } = useValues(themeLogic) @@ -130,6 +139,7 @@ export function CodeEditor({ sourceQuery, monaco: monaco, editor: editor, + onError, }) useMountedLogic(builtCodeEditorLogic) diff --git a/frontend/src/lib/monaco/codeEditorLogic.tsx b/frontend/src/lib/monaco/codeEditorLogic.tsx index 05506028c1c93..63290fa0012b7 100644 --- a/frontend/src/lib/monaco/codeEditorLogic.tsx +++ b/frontend/src/lib/monaco/codeEditorLogic.tsx @@ -1,6 +1,7 @@ import type { Monaco } from '@monaco-editor/react' import { actions, connect, kea, key, listeners, path, props, propsChanged, reducers, selectors } from 'kea' import { loaders } from 'kea-loaders' +import { subscriptions } from 'kea-subscriptions' import { FEATURE_FLAGS } from 'lib/constants' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' // Note: we can oly import types and not values from monaco-editor, because otherwise some Monaco code breaks @@ -48,6 +49,7 @@ export interface CodeEditorLogicProps { editor?: editor.IStandaloneCodeEditor | null globals?: Record multitab?: boolean + onError?: (error: string | null, isValidView: boolean) => void } export const codeEditorLogic = kea([ @@ -270,6 +272,14 @@ export const codeEditorLogic = kea([ }, ], }), + subscriptions(({ props, values }) => ({ + isValidView: (isValidView) => { + props.onError?.(values.error, isValidView) + }, + error: (error) => { + props.onError?.(error, values.isValidView) + }, + })), propsChanged(({ actions, props }, oldProps) => { if ( props.query !== oldProps.query || diff --git a/frontend/src/scenes/data-warehouse/editor/EditorScene.tsx b/frontend/src/scenes/data-warehouse/editor/EditorScene.tsx index 3576303ebddd9..edf174a7cf216 100644 --- a/frontend/src/scenes/data-warehouse/editor/EditorScene.tsx +++ b/frontend/src/scenes/data-warehouse/editor/EditorScene.tsx @@ -8,6 +8,7 @@ import { useRef } from 'react' import { Sidebar } from '~/layout/navigation-3000/components/Sidebar' import { navigation3000Logic } from '~/layout/navigation-3000/navigationLogic' +import { ViewLinkModal } from '../ViewLinkModal' import { editorSceneLogic } from './editorSceneLogic' import { editorSizingLogic } from './editorSizingLogic' import { QueryWindow } from './QueryWindow' @@ -47,6 +48,7 @@ export function EditorScene(): JSX.Element { )} + ) } diff --git a/frontend/src/scenes/data-warehouse/editor/OutputPane.tsx b/frontend/src/scenes/data-warehouse/editor/OutputPane.tsx index 988323bd02093..af571d78b8aa7 100644 --- a/frontend/src/scenes/data-warehouse/editor/OutputPane.tsx +++ b/frontend/src/scenes/data-warehouse/editor/OutputPane.tsx @@ -3,7 +3,7 @@ import 'react-data-grid/lib/styles.css' import { IconGear } from '@posthog/icons' import { LemonButton, LemonTabs, Spinner } from '@posthog/lemon-ui' import clsx from 'clsx' -import { BindLogic, useActions, useValues } from 'kea' +import { useActions, useValues } from 'kea' import { router } from 'kea-router' import { AnimationType } from 'lib/animations/animations' import { Animation } from 'lib/components/Animation/Animation' @@ -11,51 +11,49 @@ import { ExportButton } from 'lib/components/ExportButton/ExportButton' import { useMemo } from 'react' import DataGrid from 'react-data-grid' import { InsightErrorState } from 'scenes/insights/EmptyStates' -import { insightDataLogic } from 'scenes/insights/insightDataLogic' -import { insightLogic } from 'scenes/insights/insightLogic' import { HogQLBoldNumber } from 'scenes/insights/views/BoldNumber/BoldNumber' import { KeyboardShortcut } from '~/layout/navigation-3000/components/KeyboardShortcut' import { themeLogic } from '~/layout/navigation-3000/themeLogic' -import { dataNodeLogic, DataNodeLogicProps } from '~/queries/nodes/DataNode/dataNodeLogic' +import { dataNodeLogic } from '~/queries/nodes/DataNode/dataNodeLogic' import { LineGraph } from '~/queries/nodes/DataVisualization/Components/Charts/LineGraph' import { SideBar } from '~/queries/nodes/DataVisualization/Components/SideBar' import { Table } from '~/queries/nodes/DataVisualization/Components/Table' import { TableDisplay } from '~/queries/nodes/DataVisualization/Components/TableDisplay' -import { variableModalLogic } from '~/queries/nodes/DataVisualization/Components/Variables/variableModalLogic' +import { AddVariableButton } from '~/queries/nodes/DataVisualization/Components/Variables/AddVariableButton' import { VariablesForInsight } from '~/queries/nodes/DataVisualization/Components/Variables/Variables' import { variablesLogic } from '~/queries/nodes/DataVisualization/Components/Variables/variablesLogic' import { DataTableVisualizationProps } from '~/queries/nodes/DataVisualization/DataVisualization' -import { - dataVisualizationLogic, - DataVisualizationLogicProps, -} from '~/queries/nodes/DataVisualization/dataVisualizationLogic' -import { displayLogic } from '~/queries/nodes/DataVisualization/displayLogic' +import { dataVisualizationLogic } from '~/queries/nodes/DataVisualization/dataVisualizationLogic' import { DataVisualizationNode, HogQLQueryResponse, NodeKind } from '~/queries/schema' -import { ChartDisplayType, ExporterFormat, ItemMode } from '~/types' +import { ChartDisplayType, ExportContext, ExporterFormat } from '~/types' -import { DATAWAREHOUSE_EDITOR_ITEM_ID } from '../external/dataWarehouseExternalSceneLogic' import { dataWarehouseViewsLogic } from '../saved_queries/dataWarehouseViewsLogic' import { multitabEditorLogic } from './multitabEditorLogic' import { outputPaneLogic, OutputTab } from './outputPaneLogic' interface OutputPaneProps { - onSave: () => void + onSaveInsight: () => void + onSaveView: () => void saveDisabledReason?: string onQueryInputChange: () => void - logicKey: string + onQueryChange: (query: DataVisualizationNode) => void query: string + exportContext?: ExportContext } export function OutputPane({ onQueryInputChange, - onSave, + onQueryChange, + onSaveView, + onSaveInsight, saveDisabledReason, - logicKey, query, + exportContext, }: OutputPaneProps): JSX.Element { const { activeTab } = useValues(outputPaneLogic) const { setActiveTab } = useActions(outputPaneLogic) + const { variablesForInsight } = useValues(variablesLogic) const codeEditorKey = `hogQLQueryEditor/${router.values.location.pathname}` @@ -65,31 +63,12 @@ export function OutputPane({ }) ) const { isDarkModeOn } = useValues(themeLogic) - const { response, responseLoading } = useValues( - dataNodeLogic({ - key: logicKey, - query: { - kind: NodeKind.HogQLQuery, - query, - }, - doNotLoad: !query, - }) - ) + const { response, responseLoading } = useValues(dataNodeLogic) const { dataWarehouseSavedQueriesLoading } = useValues(dataWarehouseViewsLogic) const { updateDataWarehouseSavedQuery } = useActions(dataWarehouseViewsLogic) + const { visualizationType } = useValues(dataVisualizationLogic) - const { insightProps } = useValues( - insightLogic({ - dashboardItemId: DATAWAREHOUSE_EDITOR_ITEM_ID, - cachedInsight: null, - doNotLoad: true, - }) - ) - const { setQuery } = useActions( - insightDataLogic({ - ...insightProps, - }) - ) + const vizKey = `SQLEditorScene` const columns = useMemo(() => { return ( @@ -138,8 +117,8 @@ export function OutputPane({ ) : (
-
) @@ -158,6 +141,11 @@ export function OutputPane({ return (
+ {variablesForInsight.length > 0 && ( +
+ +
+ )}
+ + + {exportContext && ( + + )} + {editingView ? ( <> ) : ( - onSave()} disabledReason={saveDisabledReason}> + onSaveView()} disabledReason={saveDisabledReason}> Save as view )} @@ -211,65 +221,9 @@ export function OutputPane({ ) } -function DataTableVisualizationContent({ - query, - setQuery, - activeTab, -}: { - query: DataVisualizationNode - setQuery: (query: DataVisualizationNode) => void - activeTab: OutputTab -}): JSX.Element { - const vizKey = `SQLEditorScene.${activeTab}` - const dataVisualizationLogicProps: DataVisualizationLogicProps = { - key: vizKey, - query, - dashboardId: undefined, - dataNodeCollectionId: vizKey, - insightMode: ItemMode.Edit, - loadPriority: undefined, - setQuery, - cachedResults: undefined, - variablesOverride: undefined, - } - - const dataNodeLogicProps: DataNodeLogicProps = { - query: query.source, - key: vizKey, - cachedResults: undefined, - loadPriority: undefined, - dataNodeCollectionId: vizKey, - variablesOverride: undefined, - } - - return ( - - - - - - - - - - - - ) -} - -function InternalDataTableVisualization(props: DataTableVisualizationProps): JSX.Element { - const logic = insightLogic({ - dashboardItemId: DATAWAREHOUSE_EDITOR_ITEM_ID, - cachedInsight: null, - }) - const { saveAs } = useActions(logic) - +function InternalDataTableVisualization( + props: DataTableVisualizationProps & { onSaveInsight: () => void } +): JSX.Element { const { query, visualizationType, @@ -361,27 +315,7 @@ function InternalDataTableVisualization(props: DataTableVisualizationProps): JSX tooltip="Visualization settings" /> - {props.exportContext && ( - - )} - - saveAs(true, false)}> + props.onSaveInsight()}> Create insight
@@ -389,8 +323,6 @@ function InternalDataTableVisualization(props: DataTableVisualizationProps): JSX
)} - -
) diff --git a/frontend/src/scenes/data-warehouse/editor/QueryPane.tsx b/frontend/src/scenes/data-warehouse/editor/QueryPane.tsx index 10e36c436e739..502084bee80e6 100644 --- a/frontend/src/scenes/data-warehouse/editor/QueryPane.tsx +++ b/frontend/src/scenes/data-warehouse/editor/QueryPane.tsx @@ -3,12 +3,15 @@ import { Resizer } from 'lib/components/Resizer/Resizer' import { CodeEditor, CodeEditorProps } from 'lib/monaco/CodeEditor' import { AutoSizer } from 'react-virtualized/dist/es/AutoSizer' +import { HogQLQuery } from '~/queries/schema' + import { editorSizingLogic } from './editorSizingLogic' interface QueryPaneProps { queryInput: string promptError: string | null codeEditorProps: Partial + sourceQuery: HogQLQuery } export function QueryPane(props: QueryPaneProps): JSX.Element { @@ -31,6 +34,7 @@ export function QueryPane(props: QueryPaneProps): JSX.Element { className="border" language="hogQL" value={props.queryInput} + sourceQuery={props.sourceQuery} height={height} width={width} {...props.codeEditorProps} diff --git a/frontend/src/scenes/data-warehouse/editor/QueryWindow.tsx b/frontend/src/scenes/data-warehouse/editor/QueryWindow.tsx index fa8f52c604781..52bd671775772 100644 --- a/frontend/src/scenes/data-warehouse/editor/QueryWindow.tsx +++ b/frontend/src/scenes/data-warehouse/editor/QueryWindow.tsx @@ -1,39 +1,121 @@ import { Monaco } from '@monaco-editor/react' -import { useActions, useValues } from 'kea' +import { BindLogic, useActions, useValues } from 'kea' import { router } from 'kea-router' import type { editor as importedEditor } from 'monaco-editor' import { useState } from 'react' +import { dataNodeLogic, DataNodeLogicProps } from '~/queries/nodes/DataNode/dataNodeLogic' +import { variableModalLogic } from '~/queries/nodes/DataVisualization/Components/Variables/variableModalLogic' +import { + variablesLogic, + VariablesLogicProps, +} from '~/queries/nodes/DataVisualization/Components/Variables/variablesLogic' +import { + dataVisualizationLogic, + DataVisualizationLogicProps, +} from '~/queries/nodes/DataVisualization/dataVisualizationLogic' +import { displayLogic } from '~/queries/nodes/DataVisualization/displayLogic' +import { insightVizDataNodeKey } from '~/queries/nodes/InsightViz/InsightViz' +import { DataVisualizationNode, NodeKind } from '~/queries/schema' +import { ItemMode } from '~/types' + +import { DATAWAREHOUSE_EDITOR_ITEM_ID } from '../external/dataWarehouseExternalSceneLogic' import { multitabEditorLogic } from './multitabEditorLogic' import { OutputPane } from './OutputPane' import { QueryPane } from './QueryPane' import { QueryTabs } from './QueryTabs' +const dataNodeKey = insightVizDataNodeKey({ + dashboardItemId: DATAWAREHOUSE_EDITOR_ITEM_ID, + cachedInsight: null, + doNotLoad: true, +}) + export function QueryWindow(): JSX.Element { + const [querySource, localSetQuerySource] = useState({ + kind: NodeKind.DataVisualizationNode, + source: { + kind: NodeKind.HogQLQuery, + query: '', + }, + } as DataVisualizationNode) + + const dataVisualizationLogicProps: DataVisualizationLogicProps = { + key: dataNodeKey, + query: querySource, + dashboardId: undefined, + dataNodeCollectionId: dataNodeKey, + insightMode: ItemMode.Edit, + loadPriority: undefined, + cachedResults: undefined, + variablesOverride: undefined, + setQuery: localSetQuerySource, + } + + const dataNodeLogicProps: DataNodeLogicProps = { + query: querySource.source, + key: dataNodeKey, + cachedResults: undefined, + loadPriority: undefined, + dataNodeCollectionId: dataNodeKey, + variablesOverride: undefined, + } + + const variablesLogicProps: VariablesLogicProps = { + key: dataVisualizationLogicProps.key, + readOnly: false, + } + + return ( + + + + + + + + + + + + ) +} + +interface InternalQueryWindowProps { + setQuery: (query: DataVisualizationNode) => void + query: DataVisualizationNode +} + +function InternalQueryWindow({ setQuery, query }: InternalQueryWindowProps): JSX.Element { + const [error, setError] = useState(null) + const [isValidView, setIsValidView] = useState(true) + const [monacoAndEditor, setMonacoAndEditor] = useState( null as [Monaco, importedEditor.IStandaloneCodeEditor] | null ) const [monaco, editor] = monacoAndEditor ?? [] - const codeEditorKey = `hogQLQueryEditor/${router.values.location.pathname}` + const { setEditorQuery } = useActions(variablesLogic) + const logic = multitabEditorLogic({ key: codeEditorKey, monaco, editor, + sourceQuery: query, + onRunQuery: (query) => { + setQuery({ + kind: NodeKind.DataVisualizationNode, + source: query, + } as DataVisualizationNode) + }, + onQueryInputChange: (queryInput) => { + setEditorQuery(queryInput) + }, }) - const { - allTabs, - activeModelUri, - queryInput, - activeQuery, - activeTabKey, - hasErrors, - error, - isValidView, - editingView, - } = useValues(logic) - const { selectTab, deleteTab, createTab, setQueryInput, runQuery, saveAsView } = useActions(logic) + + const { allTabs, activeModelUri, queryInput, activeQuery, editingView, exportContext } = useValues(logic) + const { selectTab, deleteTab, createTab, setQueryInput, runQuery, saveAsView, saveAsInsight } = useActions(logic) return (
@@ -51,8 +133,10 @@ export function QueryWindow(): JSX.Element { )} { setQueryInput(v ?? '') }, @@ -66,16 +150,20 @@ export function QueryWindow(): JSX.Element { runQuery() } }, + onError: (error, isValidView) => { + setError(error) + setIsValidView(isValidView) + }, }} />
) diff --git a/frontend/src/scenes/data-warehouse/editor/multitabEditorLogic.tsx b/frontend/src/scenes/data-warehouse/editor/multitabEditorLogic.tsx index c6b9e4ed575c9..c53a94f9dfc93 100644 --- a/frontend/src/scenes/data-warehouse/editor/multitabEditorLogic.tsx +++ b/frontend/src/scenes/data-warehouse/editor/multitabEditorLogic.tsx @@ -1,23 +1,30 @@ import { Monaco } from '@monaco-editor/react' import { LemonDialog, LemonInput, lemonToast } from '@posthog/lemon-ui' -import { actions, connect, kea, key, listeners, path, props, propsChanged, reducers, selectors } from 'kea' +import { actions, afterMount, connect, kea, key, listeners, path, props, propsChanged, reducers, selectors } from 'kea' +import { router } from 'kea-router' import { subscriptions } from 'kea-subscriptions' import { LemonField } from 'lib/lemon-ui/LemonField' -import { ModelMarker } from 'lib/monaco/codeEditorLogic' -import { editor, MarkerSeverity, Uri } from 'monaco-editor' +import { initModel } from 'lib/monaco/CodeEditor' +import { codeEditorLogic } from 'lib/monaco/codeEditorLogic' +import { editor, Uri } from 'monaco-editor' +import { insightsApi } from 'scenes/insights/utils/api' +import { urls } from 'scenes/urls' import { dataNodeLogic } from '~/queries/nodes/DataNode/dataNodeLogic' -import { performQuery } from '~/queries/query' -import { HogLanguage, HogQLMetadata, HogQLMetadataResponse, HogQLNotice, HogQLQuery, NodeKind } from '~/queries/schema' -import { DataWarehouseSavedQuery } from '~/types' +import { queryExportContext } from '~/queries/query' +import { HogQLMetadataResponse, HogQLQuery, NodeKind } from '~/queries/schema' +import { DataVisualizationNode } from '~/queries/schema' +import { DataWarehouseSavedQuery, ExportContext } from '~/types' import { dataWarehouseViewsLogic } from '../saved_queries/dataWarehouseViewsLogic' import type { multitabEditorLogicType } from './multitabEditorLogicType' - export interface MultitabEditorLogicProps { key: string monaco?: Monaco | null editor?: editor.IStandaloneCodeEditor | null + onRunQuery?: (query: HogQLQuery) => void + onQueryInputChange?: (query: string) => void + sourceQuery?: DataVisualizationNode } export const editorModelsStateKey = (key: string | number): string => `${key}/editorModelQueries` @@ -53,15 +60,16 @@ export const multitabEditorLogic = kea([ initialize: true, saveAsView: true, saveAsViewSubmit: (name: string) => ({ name }), - reloadMetadata: true, setMetadata: (query: string, metadata: HogQLMetadataResponse) => ({ query, metadata }), + saveAsInsight: true, + saveAsInsightSubmit: (name: string) => ({ name }), }), propsChanged(({ actions, props }, oldProps) => { if (!oldProps.monaco && !oldProps.editor && props.monaco && props.editor) { actions.initialize() } }), - reducers(({ props }) => ({ + reducers({ queryInput: [ '', { @@ -100,57 +108,10 @@ export const multitabEditorLogic = kea([ setTabs: (_, { tabs }) => tabs, }, ], - metadata: [ - null as null | [string, HogQLMetadataResponse], - { - setMetadata: (_, { query, metadata }) => [query, metadata], - }, - ], - modelMarkers: [ - [] as ModelMarker[], - { - setMetadata: (_, { query, metadata }) => { - const model = props.editor?.getModel() - if (!model || !metadata) { - return [] - } - const markers: ModelMarker[] = [] - const metadataResponse = metadata - - function noticeToMarker(error: HogQLNotice, severity: MarkerSeverity): ModelMarker { - const start = model!.getPositionAt(error.start ?? 0) - const end = model!.getPositionAt(error.end ?? query.length) - return { - start: error.start ?? 0, - startLineNumber: start.lineNumber, - startColumn: start.column, - end: error.end ?? query.length, - endLineNumber: end.lineNumber, - endColumn: end.column, - message: error.message ?? 'Unknown error', - severity: severity, - hogQLFix: error.fix, - } - } - - for (const notice of metadataResponse?.errors ?? []) { - markers.push(noticeToMarker(notice, 8 /* MarkerSeverity.Error */)) - } - for (const notice of metadataResponse?.warnings ?? []) { - markers.push(noticeToMarker(notice, 4 /* MarkerSeverity.Warning */)) - } - for (const notice of metadataResponse?.notices ?? []) { - markers.push(noticeToMarker(notice, 1 /* MarkerSeverity.Hint */)) - } - - props.monaco?.editor.setModelMarkers(model, 'hogql', markers) - return markers - }, - }, - ], - })), + }), listeners(({ values, props, actions, asyncActions }) => ({ createTab: ({ query = '', view }) => { + const mountedCodeEditorLogic = codeEditorLogic.findMounted() let currentModelCount = 1 const allNumbers = values.allTabs.map((tab) => parseInt(tab.uri.path.split('/').pop() || '0')) while (allNumbers.includes(currentModelCount)) { @@ -161,6 +122,11 @@ export const multitabEditorLogic = kea([ const uri = props.monaco.Uri.parse(currentModelCount.toString()) const model = props.monaco.editor.createModel(query, 'hogQL', uri) props.editor?.setModel(model) + + if (mountedCodeEditorLogic) { + initModel(model, mountedCodeEditorLogic) + } + actions.addTab({ uri, view, @@ -218,6 +184,13 @@ export const multitabEditorLogic = kea([ initialize: () => { const allModelQueries = localStorage.getItem(editorModelsStateKey(props.key)) const activeModelUri = localStorage.getItem(activemodelStateKey(props.key)) + const mountedCodeEditorLogic = + codeEditorLogic.findMounted() || + codeEditorLogic({ + key: props.key, + query: props.sourceQuery?.source.query ?? '', + language: 'hogQL', + }) if (allModelQueries) { // clear existing models @@ -237,6 +210,7 @@ export const multitabEditorLogic = kea([ uri, view: model.view, }) + mountedCodeEditorLogic && initModel(newModel, mountedCodeEditorLogic) } }) @@ -273,8 +247,9 @@ export const multitabEditorLogic = kea([ } } }, - setQueryInput: () => { + setQueryInput: ({ queryInput }) => { actions.updateState() + props.onQueryInputChange?.(queryInput) }, updateState: async (_, breakpoint) => { await breakpoint(100) @@ -288,17 +263,14 @@ export const multitabEditorLogic = kea([ localStorage.setItem(editorModelsStateKey(props.key), JSON.stringify(queries)) }, runQuery: ({ queryOverride }) => { - if (values.activeQuery === queryOverride || values.activeQuery === values.queryInput) { - dataNodeLogic({ - key: values.activeTabKey, - query: { - kind: NodeKind.HogQLQuery, - query: queryOverride || values.queryInput, - }, - alwaysRefresh: true, - }).actions.loadData(true) - } - actions.setActiveQuery(queryOverride || values.queryInput) + const query = queryOverride || values.queryInput + + actions.setActiveQuery(query) + props.sourceQuery && + props.onRunQuery?.({ + ...props.sourceQuery.source, + query, + }) }, saveAsView: async () => { LemonDialog.openForm({ @@ -336,24 +308,33 @@ export const multitabEditorLogic = kea([ await dataWarehouseViewsLogic.asyncActions.createDataWarehouseSavedQuery({ name, query, types }) }, - reloadMetadata: async (_, breakpoint) => { - const model = props.editor?.getModel() - if (!model || !props.monaco) { - return - } - await breakpoint(300) - const query = values.queryInput - if (query === '') { - return - } - - const response = await performQuery({ - kind: NodeKind.HogQLMetadata, - language: HogLanguage.hogQL, - query: query, + saveAsInsight: async () => { + LemonDialog.openForm({ + title: 'Save as new insight', + initialValues: { + name: '', + }, + content: ( + + + + ), + errors: { + name: (name) => (!name ? 'You must enter a name' : undefined), + }, + onSubmit: async ({ name }) => actions.saveAsInsightSubmit(name), }) - breakpoint() - actions.setMetadata(query, response) + }, + saveAsInsightSubmit: async ({ name }) => { + const insight = await insightsApi.create({ + name, + query: props.sourceQuery, + saved: true, + }) + + lemonToast.info(`You're now viewing ${insight.name || insight.derived_name || name}`) + + router.actions.push(urls.insightView(insight.short_id)) }, deleteDataWarehouseSavedQuerySuccess: ({ payload: viewId }) => { const tabToRemove = values.allTabs.find((tab) => tab.view?.id === viewId) @@ -396,25 +377,23 @@ export const multitabEditorLogic = kea([ }).mount() } }, - queryInput: () => { - actions.reloadMetadata() - }, })), selectors({ activeTabKey: [(s) => [s.activeModelUri], (activeModelUri) => `hogQLQueryEditor/${activeModelUri?.uri.path}`], - isValidView: [(s) => [s.metadata], (metadata) => !!(metadata && metadata[1]?.isValidView)], - hasErrors: [ - (s) => [s.modelMarkers], - (modelMarkers) => !!(modelMarkers ?? []).filter((e) => e.severity === 8 /* MarkerSeverity.Error */).length, - ], - error: [ - (s) => [s.hasErrors, s.modelMarkers], - (hasErrors, modelMarkers) => { - const firstError = modelMarkers.find((e) => e.severity === 8 /* MarkerSeverity.Error */) - return hasErrors && firstError - ? `Error on line ${firstError.startLineNumber}, column ${firstError.startColumn}` - : null + exportContext: [ + () => [(_, props) => props.sourceQuery], + (sourceQuery) => { + // TODO: use active tab at some point + const filename = 'export' + + return { + ...queryExportContext(sourceQuery.source, undefined, undefined), + filename, + } as ExportContext }, ], }), + afterMount(({ props, values }) => { + props.onQueryInputChange?.(values.queryInput) + }), ])