diff --git a/frontend/src/queries/QueryEditor/QueryEditor.tsx b/frontend/src/queries/QueryEditor/QueryEditor.tsx index dc7b44eb22e12d..88026a6c7262c4 100644 --- a/frontend/src/queries/QueryEditor/QueryEditor.tsx +++ b/frontend/src/queries/QueryEditor/QueryEditor.tsx @@ -16,6 +16,7 @@ export interface QueryEditorProps { query: string setQuery?: (query: string) => void className?: string + aboveButton?: JSX.Element context?: QueryContext } @@ -78,6 +79,7 @@ export function QueryEditor(props: QueryEditorProps): JSX.Element { Error parsing JSON: {error} ) : null} + {props.aboveButton} {time.toFixed(3)}s ))} - {timings.length > 0 ? ( + {elapsedTime !== undefined && timings.length > 0 ? (
+ HTTP overhead
{(elapsedTime / 1000 - timings[timings.length - 1].t).toFixed(3)}s
diff --git a/frontend/src/scenes/debug/DebugScene.tsx b/frontend/src/scenes/debug/DebugScene.tsx index fffbe0dc4e5211..91d189f97ead47 100644 --- a/frontend/src/scenes/debug/DebugScene.tsx +++ b/frontend/src/scenes/debug/DebugScene.tsx @@ -1,20 +1,17 @@ import { useActions, useValues } from 'kea' -import { CodeEditor } from 'lib/components/CodeEditors' import { PageHeader } from 'lib/components/PageHeader' import { LemonButton } from 'lib/lemon-ui/LemonButton' -import { LemonDivider } from 'lib/lemon-ui/LemonDivider' import { LemonLabel } from 'lib/lemon-ui/LemonLabel/LemonLabel' import { LemonSelect } from 'lib/lemon-ui/LemonSelect' import { HogQLDebug } from 'scenes/debug/HogQLDebug' import { Modifiers } from 'scenes/debug/Modifiers' +import { QueryTabs } from 'scenes/debug/QueryTabs' import { SceneExport } from 'scenes/sceneTypes' import { stringifiedExamples } from '~/queries/examples' import { dataNodeLogic, DataNodeLogicProps } from '~/queries/nodes/DataNode/dataNodeLogic' -import { Query } from '~/queries/Query/Query' import { QueryEditor } from '~/queries/QueryEditor/QueryEditor' import { DataNode, HogQLQuery, Node } from '~/queries/schema' -import { isDataTableNode, isInsightVizNode } from '~/queries/utils' import { debugSceneLogic } from './debugSceneLogic' @@ -48,28 +45,27 @@ function QueryDebug({ query, setQuery, queryKey }: QueryDebugProps): JSX.Element /> ) : (
- - setQuery(JSON.stringify({ ...parsed, source: query }, null, 2)) - : (query) => setQuery(JSON.stringify(query, null, 2)) - } - query={parsed?.source ?? parsed} - response={response} - /> - - setQuery(JSON.stringify(query, null, 2))} + setQuery={setQuery} + aboveButton={ + setQuery(JSON.stringify({ ...parsed, source: query }, null, 2)) + : (query) => setQuery(JSON.stringify(query, null, 2)) + } + query={parsed?.source ?? parsed} + response={response} + /> + } /> - {response && parsed && (isDataTableNode(parsed as Node) || isInsightVizNode(parsed as Node)) ? ( - setQuery(JSON.stringify(query, null, 2))} /> ) : null}
diff --git a/frontend/src/scenes/debug/HogQLDebug.tsx b/frontend/src/scenes/debug/HogQLDebug.tsx index d42f4af5d56640..99be6f2c02d962 100644 --- a/frontend/src/scenes/debug/HogQLDebug.tsx +++ b/frontend/src/scenes/debug/HogQLDebug.tsx @@ -1,67 +1,39 @@ import { BindLogic, useValues } from 'kea' -import { CodeEditor } from 'lib/components/CodeEditors' -import { CodeSnippet, Language } from 'lib/components/CodeSnippet' -import { LemonTable } from 'lib/lemon-ui/LemonTable' +import { LemonDivider } from 'lib/lemon-ui/LemonDivider' import { Modifiers } from 'scenes/debug/Modifiers' import { dataNodeLogic, DataNodeLogicProps } from '~/queries/nodes/DataNode/dataNodeLogic' import { DateRange } from '~/queries/nodes/DataNode/DateRange' -import { ElapsedTime, Timings } from '~/queries/nodes/DataNode/ElapsedTime' +import { ElapsedTime } from '~/queries/nodes/DataNode/ElapsedTime' import { Reload } from '~/queries/nodes/DataNode/Reload' import { EventPropertyFilters } from '~/queries/nodes/EventsNode/EventPropertyFilters' import { HogQLQueryEditor } from '~/queries/nodes/HogQLQuery/HogQLQueryEditor' import { DataNode, HogQLQuery, HogQLQueryResponse } from '~/queries/schema' +import { QueryTabs } from './QueryTabs' + interface HogQLDebugProps { queryKey: string query: HogQLQuery setQuery: (query: DataNode) => void } -function toLineColumn(hogql: string, position: number): { line: number; column: number } { - const lines = hogql.split('\n') - let line = 0 - let column = 0 - for (let i = 0; i < lines.length; i++) { - if (position < lines[i].length) { - line = i + 1 - column = position + 1 - break - } - position -= lines[i].length + 1 - } - return { line, column } -} - -function toLine(hogql: string, position: number): number { - return toLineColumn(hogql, position).line -} - -function toColumn(hogql: string, position: number): number { - return toLineColumn(hogql, position).column -} - export function HogQLDebug({ query, setQuery, queryKey }: HogQLDebugProps): JSX.Element { const dataNodeLogicProps: DataNodeLogicProps = { query, key: queryKey, dataNodeCollectionId: queryKey } - const { - dataLoading, - response: _response, - responseErrorObject, - elapsedTime, - } = useValues(dataNodeLogic(dataNodeLogicProps)) + const { dataLoading, response: _response } = useValues(dataNodeLogic(dataNodeLogicProps)) const response = _response as HogQLQueryResponse | null - const clickHouseTime = response?.timings?.find(({ k }) => k === './clickhouse_execute')?.t return (
+ +
- {dataLoading ? ( <>

Running query...

@@ -71,87 +43,7 @@ export function HogQLDebug({ query, setQuery, queryKey }: HogQLDebugProps): JSX. ) : ( <> - {response?.error ? ( - <> -

Error Running Query!

- - {response.error} - - - ) : null} - {response?.hogql ? ( - <> -

Executed HogQL

- - {response.hogql} - - - ) : null} - {response?.clickhouse ? ( - <> -

- Executed ClickHouse SQL - {clickHouseTime !== undefined - ? ` (${Math.floor(clickHouseTime * 1000) / 1000}s)` - : ''} -

- - {response.clickhouse} - - - ) : null} - {response?.metadata ? ( - <> -

Metadata

- ({ - type: 'error', - line: toLine(response.hogql ?? '', error.start ?? 0), - column: toColumn(response.hogql ?? '', error.start ?? 0), - ...error, - })), - ...response.metadata.warnings.map((warn) => ({ - type: 'warning', - line: toLine(response.hogql ?? '', warn.start ?? 0), - column: toColumn(response.hogql ?? '', warn.start ?? 0), - ...warn, - })), - ...response.metadata.notices.map((notice) => ({ - type: 'notice', - line: toLine(response.hogql ?? '', notice.start ?? 0), - column: toColumn(response.hogql ?? '', notice.start ?? 0), - ...notice, - })), - ].sort((a, b) => (a.start ?? 0) - (b.start ?? 0))} - columns={[ - { title: 'Line', dataIndex: 'line', key: 'line', width: '40px' }, - { title: 'Column', dataIndex: 'column', key: 'column', width: '40px' }, - { title: 'Type', dataIndex: 'type', key: 'type', width: '80px' }, - { title: 'Message', dataIndex: 'message', key: 'message' }, - ]} - /> - - ) : null} - {response?.explain ? ( - <> -

Explained ClickHouseSQL

- {response.explain.join('\n')} - - ) : null} - {response?.timings && elapsedTime !== null ? ( - <> -

Time spent

- - - ) : null} -

Raw response

- + )}
diff --git a/frontend/src/scenes/debug/Modifiers.tsx b/frontend/src/scenes/debug/Modifiers.tsx index ea6551ef65f6c7..4b1e2792c9171a 100644 --- a/frontend/src/scenes/debug/Modifiers.tsx +++ b/frontend/src/scenes/debug/Modifiers.tsx @@ -13,10 +13,11 @@ export function Modifiers({ setQuery, query, response = null }: ModifiersProps): if (query === null) { return null } + const labelClassName = 'flex flex-col gap-1 items-start' return (
- - POE: + +
POE:
- - Persons ArgMax: + +
Persons ArgMax:
- - In Cohort Via: + +
In Cohort Via:
- - Materialization Mode: + +
Materialization Mode:
| null + setQuery: (query: DataNode) => void +} +export function QueryTabs({ query, queryKey, setQuery, response }: QueryTabsProps): JSX.Element { + const [tab, setTab] = useState(null) + const clickHouseTime = (response?.timings as QueryTiming[])?.find(({ k }) => k === './clickhouse_execute')?.t ?? 0 + const explainTime = (response?.timings as QueryTiming[])?.find(({ k }) => k === './explain')?.t ?? 0 + const totalTime = (response?.timings as QueryTiming[])?.find(({ k }) => k === '.')?.t ?? 0 + const hogQLTime = totalTime - explainTime - clickHouseTime + const tabs = query + ? [ + response?.error && { + key: 'error', + label: 'Error', + content: ( + <> +

Error Running Query!

+ + {response.error} + + + ), + }, + isInsightVizNode(query) && { + key: 'viz', + label: 'Visualization', + content: setQuery(query)} />, + }, + isInsightQueryNode(query) && { + key: 'insight', + label: 'Insight', + content: ( + setQuery(query)} + /> + ), + }, + isDataTableNode(query) && { + key: 'table', + label: 'Data Table', + content: setQuery(query)} />, + }, + + (response?.result || response?.results) && { + key: 'result', + label: 'Result JSON', + content: ( + + ), + }, + response?.hogql && { + key: 'hogql', + label: `HogQL${hogQLTime ? ` (${Math.floor(hogQLTime * 10) / 10}s)` : ''}`, + content: ( + + ), + }, + response?.clickhouse && { + key: 'clickhouse', + label: `ClickHouse${clickHouseTime ? ` (${Math.floor(clickHouseTime * 10) / 10}s)` : ''}`, + content: ( + + ), + }, + response?.explain && { + key: 'explain', + label: 'Explain', + content: {response.explain.join('\n')}, + }, + response?.timings && { + key: 'timings', + label: 'Timings', + content: , + }, + response && { + key: 'response', + label: 'Full response', + content: ( + + ), + }, + response?.metadata && { + key: 'metadata', + label: 'Metadata', + content: ( + ({ + type: 'error', + line: toLine(response.hogql ?? '', error.start ?? 0), + column: toColumn(response.hogql ?? '', error.start ?? 0), + ...error, + })), + ...(response.metadata as HogQLMetadataResponse).warnings.map((warn) => ({ + type: 'warning', + line: toLine(response.hogql ?? '', warn.start ?? 0), + column: toColumn(response.hogql ?? '', warn.start ?? 0), + ...warn, + })), + ...(response.metadata as HogQLMetadataResponse).notices.map((notice) => ({ + type: 'notice', + line: toLine(response.hogql ?? '', notice.start ?? 0), + column: toColumn(response.hogql ?? '', notice.start ?? 0), + ...notice, + })), + ].sort((a, b) => (a.start ?? 0) - (b.start ?? 0))} + columns={[ + { title: 'Line', dataIndex: 'line', key: 'line', width: '40px' }, + { title: 'Column', dataIndex: 'column', key: 'column', width: '40px' }, + { title: 'Type', dataIndex: 'type', key: 'type', width: '80px' }, + { title: 'Message', dataIndex: 'message', key: 'message' }, + ]} + /> + ), + }, + ] + .filter(Boolean) + .map((tab) => ({ ...tab, content: {tab.content} })) + : [] + + return ( + + setTab(t)} tabs={tabs} /> + + ) +}