Skip to content

Commit

Permalink
feat(intellisense): Setup the basics of intellisense for the query ed…
Browse files Browse the repository at this point in the history
…itor (#20155)

* Setup the basic of intellisense for the query editor

* Missing node usage

* Update query snapshots

* Update query snapshots

* rename intellisense to autocomplete

* Schema fix

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
Gilbert09 and github-actions[bot] authored Feb 7, 2024
1 parent 11e4d84 commit 56c8154
Show file tree
Hide file tree
Showing 9 changed files with 466 additions and 14 deletions.
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export const FEATURE_FLAGS = {
HOGQL_INSIGHTS_FUNNELS: 'hogql-insights-funnels', // owner: @thmsobrmlr
HOGQL_INSIGHT_LIVE_COMPARE: 'hogql-insight-live-compare', // owner: @mariusandra
BI_VIZ: 'bi_viz', // owner: @Gilbert09
HOGQL_AUTOCOMPLETE: 'hogql-autocomplete', // owner: @Gilbert09
WEBHOOKS_DENYLIST: 'webhooks-denylist', // owner: #team-pipeline
SURVEYS_RESULTS_VISUALIZATIONS: 'surveys-results-visualizations', // owner: @jurajmajerik
SURVEYS_PAYGATES: 'surveys-paygates',
Expand Down
122 changes: 120 additions & 2 deletions frontend/src/queries/nodes/HogQLQuery/HogQLQueryEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,79 @@ import { IconAutoAwesome, IconInfo } from 'lib/lemon-ui/icons'
import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
import { LemonButton, LemonButtonWithDropdown } from 'lib/lemon-ui/LemonButton'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import type { editor as importedEditor, IDisposable, languages } from 'monaco-editor'
import type { editor as importedEditor, IDisposable } from 'monaco-editor'
import { languages } from 'monaco-editor'
import { useEffect, useRef, useState } from 'react'
import { urls } from 'scenes/urls'

import { HogQLQuery } from '~/queries/schema'
import { query } from '~/queries/query'
import { AutocompleteCompletionItem, HogQLAutocomplete, HogQLQuery, NodeKind } from '~/queries/schema'

import { hogQLQueryEditorLogic } from './hogQLQueryEditorLogic'

const convertCompletionItemKind = (kind: AutocompleteCompletionItem['kind']): languages.CompletionItemKind => {
switch (kind) {
case 'Method':
return languages.CompletionItemKind.Method
case 'Function':
return languages.CompletionItemKind.Function
case 'Constructor':
return languages.CompletionItemKind.Constructor
case 'Field':
return languages.CompletionItemKind.Field
case 'Variable':
return languages.CompletionItemKind.Variable
case 'Class':
return languages.CompletionItemKind.Class
case 'Struct':
return languages.CompletionItemKind.Struct
case 'Interface':
return languages.CompletionItemKind.Interface
case 'Module':
return languages.CompletionItemKind.Module
case 'Property':
return languages.CompletionItemKind.Property
case 'Event':
return languages.CompletionItemKind.Event
case 'Operator':
return languages.CompletionItemKind.Operator
case 'Unit':
return languages.CompletionItemKind.Unit
case 'Value':
return languages.CompletionItemKind.Value
case 'Constant':
return languages.CompletionItemKind.Constant
case 'Enum':
return languages.CompletionItemKind.Enum
case 'EnumMember':
return languages.CompletionItemKind.EnumMember
case 'Keyword':
return languages.CompletionItemKind.Keyword
case 'Text':
return languages.CompletionItemKind.Text
case 'Color':
return languages.CompletionItemKind.Color
case 'File':
return languages.CompletionItemKind.File
case 'Reference':
return languages.CompletionItemKind.Reference
case 'Customcolor':
return languages.CompletionItemKind.Customcolor
case 'Folder':
return languages.CompletionItemKind.Folder
case 'TypeParameter':
return languages.CompletionItemKind.TypeParameter
case 'User':
return languages.CompletionItemKind.User
case 'Issue':
return languages.CompletionItemKind.Issue
case 'Snippet':
return languages.CompletionItemKind.Snippet
default:
throw new Error(`Unknown CompletionItemKind: ${kind}`)
}
}

export interface HogQLQueryEditorProps {
query: HogQLQuery
setQuery?: (query: HogQLQuery) => void
Expand Down Expand Up @@ -127,6 +192,59 @@ export function HogQLQueryEditor(props: HogQLQueryEditorProps): JSX.Element {
onChange={(v) => setQueryInput(v ?? '')}
height="100%"
onMount={(editor, monaco) => {
monaco.languages.registerCompletionItemProvider('mysql', {
triggerCharacters: [' ', ',', '.'],
provideCompletionItems: async (model, position) => {
if (!logic.isMounted()) {
return undefined
}

if (!featureFlags[FEATURE_FLAGS.HOGQL_AUTOCOMPLETE]) {
return undefined
}

const word = model.getWordUntilPosition(position)

const startOffset = model.getOffsetAt({
lineNumber: position.lineNumber,
column: word.startColumn,
})
const endOffset = model.getOffsetAt({
lineNumber: position.lineNumber,
column: word.endColumn,
})

const response = await query<HogQLAutocomplete>({
kind: NodeKind.HogQLAutocomplete,
select: logic.values.queryInput,
filters: props.query.filters,
startPosition: startOffset,
endPosition: endOffset,
})

const completionItems = response.suggestions

const suggestions = completionItems.map((item) => {
return {
label: item.label,
documentation: item.documentation,
insertText: item.insertText,
range: {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn,
},
kind: convertCompletionItemKind(item.kind),
}
})

return {
suggestions,
}
},
})

monaco.languages.registerCodeActionProvider('mysql', {
provideCodeActions: (model, _range, context) => {
if (logic.isMounted()) {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/queries/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ async function executeQuery<N extends DataNode = DataNode>(
queryId?: string
): Promise<NonNullable<N['response']>> {
const queryAsyncEnabled = Boolean(featureFlagLogic.findMounted()?.values.featureFlags?.[FEATURE_FLAGS.QUERY_ASYNC])
const excludedKinds = ['HogQLMetadata', 'EventsQuery']
const excludedKinds = ['HogQLMetadata', 'EventsQuery', 'HogQLAutocomplete']
const queryAsync = queryAsyncEnabled && !excludedKinds.includes(queryNode.kind)
const response = await api.query(queryNode, methodOptions, queryId, refresh, queryAsync)

Expand Down
120 changes: 120 additions & 0 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@
{
"$ref": "#/definitions/HogQLMetadata"
},
{
"$ref": "#/definitions/HogQLAutocomplete"
},
{
"$ref": "#/definitions/WebOverviewQuery"
},
Expand Down Expand Up @@ -276,6 +279,9 @@
{
"$ref": "#/definitions/HogQLMetadataResponse"
},
{
"$ref": "#/definitions/HogQLAutocompleteResponse"
},
{
"anyOf": [
{
Expand Down Expand Up @@ -305,6 +311,59 @@
}
]
},
"AutocompleteCompletionItem": {
"additionalProperties": false,
"properties": {
"documentation": {
"description": "A human-readable string that represents a doc-comment.",
"type": "string"
},
"insertText": {
"description": "A string or snippet that should be inserted in a document when selecting this completion.",
"type": "string"
},
"kind": {
"description": "The kind of this completion item. Based on the kind an icon is chosen by the editor.",
"enum": [
"Method",
"Function",
"Constructor",
"Field",
"Variable",
"Class",
"Struct",
"Interface",
"Module",
"Property",
"Event",
"Operator",
"Unit",
"Value",
"Constant",
"Enum",
"EnumMember",
"Keyword",
"Text",
"Color",
"File",
"Reference",
"Customcolor",
"Folder",
"TypeParameter",
"User",
"Issue",
"Snippet"
],
"type": "string"
},
"label": {
"description": "The label of this completion item. By default this is also the text that is inserted when selecting this completion.",
"type": "string"
}
},
"required": ["label", "insertText", "kind"],
"type": "object"
},
"BaseMathType": {
"enum": ["total", "dau", "weekly_active", "monthly_active", "unique_session"],
"type": "string"
Expand Down Expand Up @@ -1841,6 +1900,50 @@
}
]
},
"HogQLAutocomplete": {
"additionalProperties": false,
"properties": {
"endPosition": {
"description": "End position of the editor word",
"type": "integer"
},
"filters": {
"$ref": "#/definitions/HogQLFilters",
"description": "Table to validate the expression against"
},
"kind": {
"const": "HogQLAutocomplete",
"type": "string"
},
"response": {
"$ref": "#/definitions/HogQLAutocompleteResponse",
"description": "Cached query response"
},
"select": {
"description": "Full select query to validate",
"type": "string"
},
"startPosition": {
"description": "Start position of the editor word",
"type": "integer"
}
},
"required": ["endPosition", "kind", "select", "startPosition"],
"type": "object"
},
"HogQLAutocompleteResponse": {
"additionalProperties": false,
"properties": {
"suggestions": {
"items": {
"$ref": "#/definitions/AutocompleteCompletionItem"
},
"type": "array"
}
},
"required": ["suggestions"],
"type": "object"
},
"HogQLExpression": {
"type": "string"
},
Expand Down Expand Up @@ -2614,6 +2717,7 @@
"PersonsNode",
"HogQLQuery",
"HogQLMetadata",
"HogQLAutocomplete",
"ActorsQuery",
"SessionsTimelineQuery",
"DataTableNode",
Expand Down Expand Up @@ -3461,6 +3565,19 @@
"required": ["errors", "warnings", "notices"],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"suggestions": {
"items": {
"$ref": "#/definitions/AutocompleteCompletionItem"
},
"type": "array"
}
},
"required": ["suggestions"],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -3757,6 +3874,9 @@
{
"$ref": "#/definitions/HogQLMetadata"
},
{
"$ref": "#/definitions/HogQLAutocomplete"
},
{
"$ref": "#/definitions/WebOverviewQuery"
},
Expand Down
Loading

0 comments on commit 56c8154

Please sign in to comment.