-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for context aware autocompletion #171
Changes from 11 commits
6ce313e
698298d
62d5d14
5b3befb
2b8f528
da96412
3379155
23efcf2
b429d3a
622f5b9
ba40b45
bb77f54
7c25579
369d8f9
45fb797
03573a8
2b72896
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -109,22 +109,27 @@ e2e.scenario({ | |
e2eSelectors.ConfigEditor.table.wrapper().contains('cloudtrail_logs'); | ||
e2eSelectors.ConfigEditor.table.input().type('cloudtrail_logs').type('{enter}'); | ||
|
||
// Verify editor suggestions | ||
e2eSelectors.QueryEditor.CodeEditor.container().click({ force: true }).type(`{selectall}$__table`); | ||
e2eSelectors.QueryEditor.CodeEditor.container().contains('(Macro) cloudtrail_logs'); | ||
// The follwing section will verify that autocompletion in behaving as expected. | ||
// Throughout the composition of the SQL query, the autocompletion engine will provide appropriate suggestions. | ||
// In this test the first few suggestions are accepted by hitting enter which will create a basic query. | ||
// Increasing delay to allow tables names and columns names to be resolved async by the plugin | ||
e2eSelectors.QueryEditor.CodeEditor.container() | ||
.click({ force: true }) | ||
.type(`s{enter}{enter}{enter}{enter} {enter}{enter}`, { delay: 3000 }); | ||
e2eSelectors.QueryEditor.CodeEditor.container().contains( | ||
'SELECT * FROM cloudtrail_logs GROUP BY additionaleventdata' | ||
); | ||
|
||
e2eSelectors.QueryEditor.CodeEditor.container().click({ force: true }).type(`{selectall}{enter} | ||
SELECT | ||
$__parseTime(eventtime, 'yyyy-MM-dd''T''HH:mm:ss''Z'), | ||
sum(cast(json_extract_scalar(additionaleventdata, '$.bytesTransferredOut') as real)) AS bytes | ||
FROM | ||
$__table | ||
WHERE additionaleventdata IS NOT NULL AND json_extract_scalar(additionaleventdata, '$.bytesTransferredOut') IS NOT NULL | ||
AND | ||
$__timeFilter(eventtime, 'yyyy-MM-dd''T''HH:mm:ss''Z') | ||
e2eSelectors.QueryEditor.CodeEditor.container() | ||
.click({ force: true }) | ||
.type( | ||
`{selectall} | ||
SELECT $__parseTime(eventtime, 'yyyy-MM-dd''T''HH:mm:ss''Z'), sum(cast(json_extract_scalar(additionaleventdata, '$.bytesTransferredOut') as real)) AS bytes | ||
FROM $__table WHERE additionaleventdata IS NOT NULL AND json_extract_scalar(additionaleventdata, '$.bytesTransferredOut') IS NOT NULL AND $__timeFilter(eventtime, 'yyyy-MM-dd''T''HH:mm:ss''Z') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to adjust formatting slightly here so that suggestions are not accepted when hitting enter to get a newline. :) |
||
GROUP BY 1 | ||
ORDER BY 1 | ||
`); | ||
` | ||
); | ||
// blur and wait for loading | ||
cy.get('.panel-content').click(); | ||
cy.get('.panel-loading'); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
"author": "Grafana Labs", | ||
"license": "Apache-2.0", | ||
"devDependencies": { | ||
"@grafana/experimental": "^0.0.2-canary.38", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what happens if Athena is run with a lower version of Grafana? (that does not include the latest experimental package) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah good shout, better make this a dependency. I wonder what would happen though if grafana is running a version of the experimental package that is lower than the plugin specifies..? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I belive it gets ignored, no matter if it's a dependency or a devdep (there is no reference to the deps in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be fine since experimental package is not specified as an external dep in core grafana There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, I didn't know it works that way, interesting! |
||
"@grafana/aws-sdk": "0.0.37", | ||
"@grafana/data": "8.2.1", | ||
"@grafana/e2e": "8.2.1", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { SQLEditor as SQLCodeEditor } from '@grafana/experimental'; | ||
import { DataSource } from 'datasource'; | ||
import { getAthenaCompletionProvider } from 'language/completionItemProvider'; | ||
import { TABLE_MACRO } from 'language/macros'; | ||
import React, { useRef, useMemo, useCallback, useEffect } from 'react'; | ||
import { AthenaQuery } from 'types'; | ||
|
||
interface RawEditorProps { | ||
query: AthenaQuery; | ||
onRunQuery: () => void; | ||
onChange: (q: AthenaQuery) => void; | ||
datasource: DataSource; | ||
} | ||
|
||
export default function SQLEditor({ query, datasource, onRunQuery, onChange }: RawEditorProps) { | ||
const queryRef = useRef<AthenaQuery>(query); | ||
useEffect(() => { | ||
queryRef.current = query; | ||
}, [query]); | ||
|
||
const getTables = useCallback(async () => { | ||
const tables: string[] = await datasource.getTables(queryRef.current).catch(() => []); | ||
return tables.map((table) => ({ name: table, completion: table })); | ||
}, [datasource]); | ||
|
||
const getColumns = useCallback(async (tableName?: string) => { | ||
const columns: string[] = await datasource | ||
.getColumns({ | ||
...queryRef.current, | ||
table: tableName ? tableName.replace(TABLE_MACRO, queryRef.current.table ?? '') : queryRef.current.table, | ||
}) | ||
.catch(() => []); | ||
return columns.map((column) => ({ name: column, completion: column })); | ||
}, []); | ||
|
||
const getTablesRef = useRef(getTables); | ||
const getColumnsRef = useRef(getColumns); | ||
const completionProvider = useMemo( | ||
() => getAthenaCompletionProvider({ getTables: getTablesRef, getColumns: getColumnsRef }), | ||
[] | ||
); | ||
|
||
return ( | ||
<SQLCodeEditor | ||
query={query.rawSQL} | ||
onBlur={() => onRunQuery()} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason that this can't just be |
||
onChange={(rawSQL) => onChange({ ...queryRef.current, rawSQL })} | ||
language={{ | ||
id: 'sql', | ||
completionProvider, | ||
}} | ||
></SQLCodeEditor> | ||
); | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,9 @@ | ||
import React from 'react'; | ||
import { QueryCodeEditor } from '@grafana/aws-sdk'; | ||
import { getSuggestions } from 'Suggestions'; | ||
import { AthenaQuery, AthenaDataSourceOptions } from './types'; | ||
import { QueryEditorProps } from '@grafana/data'; | ||
import { DataSource } from 'datasource'; | ||
import { QueryEditor } from 'QueryEditor'; | ||
|
||
export function VariableQueryCodeEditor(props: QueryEditorProps<DataSource, AthenaQuery, AthenaDataSourceOptions>) { | ||
return <QueryCodeEditor {...props} language="sql" getSuggestions={getSuggestions} />; | ||
return <QueryEditor {...props} />; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { | ||
ColumnDefinition, | ||
getStandardSQLCompletionProvider, | ||
LanguageCompletionProvider, | ||
TableDefinition, | ||
TableIdentifier, | ||
} from '@grafana/experimental'; | ||
import { MACROS } from './macros'; | ||
|
||
interface CompletionProviderGetterArgs { | ||
getTables: React.MutableRefObject<(d?: string) => Promise<TableDefinition[]>>; | ||
getColumns: React.MutableRefObject<(table: string) => Promise<ColumnDefinition[]>>; | ||
} | ||
|
||
export const getAthenaCompletionProvider: (args: CompletionProviderGetterArgs) => LanguageCompletionProvider = | ||
({ getTables, getColumns }) => | ||
(monaco, language) => { | ||
return { | ||
// get standard SQL completion provider which will resolve functions and macros | ||
...(language && getStandardSQLCompletionProvider(monaco, language)), | ||
triggerCharacters: ['.', ' ', '$', ',', '(', "'"], | ||
tables: { | ||
resolve: async () => { | ||
return await getTables.current(); | ||
}, | ||
}, | ||
columns: { | ||
resolve: async (t: TableIdentifier) => getColumns.current(t.table!), | ||
}, | ||
supportedMacros: () => MACROS, | ||
}; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.