diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/commentOnlyQuery.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/commentOnlyQuery.ts new file mode 100644 index 00000000000..ceb105b5762 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/commentOnlyQuery.ts @@ -0,0 +1,11 @@ +import { monacoTypes } from '@grafana/ui'; + +import { LogsTokenTypes } from '../../language/logs/completion/types'; +import { CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID } from '../../language/logs/definition'; + +export const commentOnlyQuery = { + query: `# comment ending with whitespace `, + tokens: [ + [{ offset: 0, type: LogsTokenTypes.Comment, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }], + ] as monacoTypes.Token[][], +}; diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/empty.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/empty.ts new file mode 100644 index 00000000000..6f4ae6aafba --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/empty.ts @@ -0,0 +1,5 @@ +export const emptyQuery = { + query: '', + tokens: [], + position: { lineNumber: 1, column: 1 }, +}; diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/index.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/index.ts new file mode 100644 index 00000000000..e656502929d --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/index.ts @@ -0,0 +1,5 @@ +export { emptyQuery } from './empty'; +export { whitespaceOnlyQuery } from './whitespaceQuery'; +export { commentOnlyQuery } from './commentOnlyQuery'; +export { singleLineFullQuery } from './singleLineFullQuery'; +export { multiLineFullQuery } from './multiLineFullQuery'; diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/multiLineFullQuery.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/multiLineFullQuery.ts new file mode 100644 index 00000000000..56321e94645 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/multiLineFullQuery.ts @@ -0,0 +1,109 @@ +import { monacoTypes } from '@grafana/ui'; + +import { LogsTokenTypes } from '../../language/logs/completion/types'; +import { CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID } from '../../language/logs/definition'; + +export const multiLineFullQuery = { + query: `fields @timestamp, unmask(@message) as msg, @memorySize + | filter (@message like /error/ and bytes > 1000) + | parse @message /(?eni-.*?)/ + | stats count(NetworkInterface), max(@memorySize / 1000 / 1000) as provisonedMemoryMB by bin(1m) + # this is a comment with the next line left being intentionally blank + + | limit 20`, + tokens: [ + [ + { offset: 0, type: LogsTokenTypes.Keyword, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 6, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 7, type: LogsTokenTypes.Identifier, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 17, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 18, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 19, type: LogsTokenTypes.Function, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 25, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 26, type: LogsTokenTypes.Identifier, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 34, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 35, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 36, type: LogsTokenTypes.Keyword, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 38, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 39, type: LogsTokenTypes.Identifier, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 42, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 43, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 44, type: LogsTokenTypes.Identifier, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + ], + [ + { offset: 0, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 1, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 2, type: LogsTokenTypes.Keyword, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 8, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 9, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 10, type: LogsTokenTypes.Identifier, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 18, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 19, type: LogsTokenTypes.Keyword, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 26, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 27, type: LogsTokenTypes.Regexp, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 31, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 32, type: LogsTokenTypes.Operator, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 35, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 36, type: LogsTokenTypes.Identifier, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 41, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 42, type: LogsTokenTypes.Operator, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 43, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 44, type: LogsTokenTypes.Number, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 48, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + ], + [ + { offset: 0, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 1, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 2, type: LogsTokenTypes.Keyword, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 7, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 8, type: LogsTokenTypes.Identifier, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 16, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 17, type: LogsTokenTypes.Regexp, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + ], + [ + { offset: 0, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 1, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 2, type: LogsTokenTypes.Keyword, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 7, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 8, type: LogsTokenTypes.Function, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 13, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 14, type: LogsTokenTypes.Identifier, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 30, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 31, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 32, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 33, type: LogsTokenTypes.Function, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 36, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 37, type: LogsTokenTypes.Identifier, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 48, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 49, type: LogsTokenTypes.Operator, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 50, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 51, type: LogsTokenTypes.Number, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 55, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 56, type: LogsTokenTypes.Operator, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 57, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 58, type: LogsTokenTypes.Number, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 62, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 63, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 64, type: LogsTokenTypes.Keyword, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 66, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 67, type: LogsTokenTypes.Identifier, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 85, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 86, type: LogsTokenTypes.Keyword, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 88, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 89, type: LogsTokenTypes.Function, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 92, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 93, type: LogsTokenTypes.Number, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 94, type: LogsTokenTypes.Identifier, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 95, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + ], + [{ offset: 0, type: LogsTokenTypes.Comment, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }], + [{ offset: 0, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }], + [ + { offset: 0, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 1, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 2, type: LogsTokenTypes.Keyword, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 7, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 8, type: LogsTokenTypes.Number, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + ], + ] as monacoTypes.Token[][], +}; diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/singleLineFullQuery.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/singleLineFullQuery.ts new file mode 100644 index 00000000000..88779221ab9 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/singleLineFullQuery.ts @@ -0,0 +1,26 @@ +import { monacoTypes } from '@grafana/ui'; + +import { LogsTokenTypes } from '../../language/logs/completion/types'; +import { CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID } from '../../language/logs/definition'; + +export const singleLineFullQuery = { + query: `fields @timestamp, @message | limit 20 # this is a comment`, + tokens: [ + [ + { offset: 0, type: LogsTokenTypes.Keyword, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 6, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 7, type: LogsTokenTypes.Identifier, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 17, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 18, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 19, type: LogsTokenTypes.Identifier, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 27, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 28, type: LogsTokenTypes.Delimiter, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 29, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 30, type: LogsTokenTypes.Keyword, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 35, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 36, type: LogsTokenTypes.Number, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 38, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + { offset: 39, type: LogsTokenTypes.Comment, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }, + ], + ] as monacoTypes.Token[][], +}; diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/whitespaceQuery.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/whitespaceQuery.ts new file mode 100644 index 00000000000..b4e93a15b69 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/whitespaceQuery.ts @@ -0,0 +1,11 @@ +import { monacoTypes } from '@grafana/ui'; + +import { LogsTokenTypes } from '../../language/logs/completion/types'; +import { CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID } from '../../language/logs/definition'; + +export const whitespaceOnlyQuery = { + query: ` `, + tokens: [ + [{ offset: 0, type: LogsTokenTypes.Whitespace, language: CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID }], + ] as monacoTypes.Token[][], +}; diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/monarch/Monaco.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/monarch/Monaco.ts index 599662d27dd..a20518aef5a 100644 --- a/public/app/plugins/datasource/cloudwatch/__mocks__/monarch/Monaco.ts +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/monarch/Monaco.ts @@ -1,6 +1,7 @@ import { monacoTypes } from '@grafana/ui'; import { Monaco } from '../../language/monarch/types'; +import * as CloudwatchLogsTestData from '../cloudwatch-logs-test-data'; import * as SQLTestData from '../cloudwatch-sql-test-data'; import * as DynamicLabelTestData from '../dynamic-label-test-data'; import * as MetricMathTestData from '../metric-math-test-data'; @@ -39,6 +40,16 @@ const MonacoMock: Monaco = { }; return TestData[value]; } + if (languageId === 'cloudwatch-logs') { + const TestData = { + [CloudwatchLogsTestData.emptyQuery.query]: CloudwatchLogsTestData.emptyQuery.tokens, + [CloudwatchLogsTestData.whitespaceOnlyQuery.query]: CloudwatchLogsTestData.whitespaceOnlyQuery.tokens, + [CloudwatchLogsTestData.commentOnlyQuery.query]: CloudwatchLogsTestData.commentOnlyQuery.tokens, + [CloudwatchLogsTestData.singleLineFullQuery.query]: CloudwatchLogsTestData.singleLineFullQuery.tokens, + [CloudwatchLogsTestData.multiLineFullQuery.query]: CloudwatchLogsTestData.multiLineFullQuery.tokens, + }; + return TestData[value]; + } return []; }, }, diff --git a/public/app/plugins/datasource/cloudwatch/language/logs/completion/statementPosition.test.ts b/public/app/plugins/datasource/cloudwatch/language/logs/completion/statementPosition.test.ts new file mode 100644 index 00000000000..c2ef700318f --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/language/logs/completion/statementPosition.test.ts @@ -0,0 +1,157 @@ +import { monacoTypes } from '@grafana/ui'; + +import { + emptyQuery, + whitespaceOnlyQuery, + commentOnlyQuery, + singleLineFullQuery, + multiLineFullQuery, +} from '../../../__mocks__/cloudwatch-logs-test-data'; +import MonacoMock from '../../../__mocks__/monarch/Monaco'; +import TextModel from '../../../__mocks__/monarch/TextModel'; +import { linkedTokenBuilder } from '../../monarch/linkedTokenBuilder'; +import { StatementPosition } from '../../monarch/types'; +import cloudWatchLogsLanguageDefinition from '../definition'; + +import { getStatementPosition } from './statementPosition'; +import { LogsTokenTypes } from './types'; + +function generateToken(query: string, position: monacoTypes.IPosition) { + const testModel = TextModel(query); + return linkedTokenBuilder( + MonacoMock, + cloudWatchLogsLanguageDefinition, + testModel as monacoTypes.editor.ITextModel, + position, + LogsTokenTypes + ); +} + +describe('getStatementPosition', () => { + it('should return StatementPosition.NewCommand the current token is null', () => { + expect(getStatementPosition(null)).toEqual(StatementPosition.NewCommand); + }); + + it('should return StatementPosition.NewCommand for an empty query', () => { + expect(getStatementPosition(generateToken(emptyQuery.query, { lineNumber: 1, column: 1 }))).toEqual( + StatementPosition.NewCommand + ); + }); + + it('should return StatementPosition.NewCommand for a query that is only whitespace', () => { + expect(getStatementPosition(generateToken(whitespaceOnlyQuery.query, { lineNumber: 1, column: 1 }))).toEqual( + StatementPosition.NewCommand + ); + }); + + it('should return StatementPosition.Comment for a query with only a comment', () => { + expect(getStatementPosition(generateToken(commentOnlyQuery.query, { lineNumber: 1, column: 1 }))).toEqual( + StatementPosition.Comment + ); + }); + + it('should return StatementPosition.NewCommand after a `|`', () => { + expect(getStatementPosition(generateToken(singleLineFullQuery.query, { lineNumber: 1, column: 30 }))).toEqual( + StatementPosition.NewCommand + ); + expect(getStatementPosition(generateToken(multiLineFullQuery.query, { lineNumber: 2, column: 2 }))).toEqual( + StatementPosition.NewCommand + ); + }); + + it('should return StatementPosition.FieldsKeyword inside the `fields` keyword', () => { + expect(getStatementPosition(generateToken(singleLineFullQuery.query, { lineNumber: 1, column: 6 }))).toEqual( + StatementPosition.FieldsKeyword + ); + }); + + it('should return StatementPosition.AfterFieldsKeyword after the `fields` keyword', () => { + expect(getStatementPosition(generateToken(singleLineFullQuery.query, { lineNumber: 1, column: 7 }))).toEqual( + StatementPosition.AfterFieldsKeyword + ); + }); + + it('should return StatementPosition.CommandArg after a keyword', () => { + expect(getStatementPosition(generateToken(singleLineFullQuery.query, { lineNumber: 1, column: 8 }))).toEqual( + StatementPosition.CommandArg + ); + }); + + it('should return StatementPosition.AfterCommand after a function', () => { + expect(getStatementPosition(generateToken(multiLineFullQuery.query, { lineNumber: 2, column: 49 }))).toEqual( + StatementPosition.AfterCommand + ); + }); + + it('should return StatementPosition.Function within a function', () => { + expect(getStatementPosition(generateToken(multiLineFullQuery.query, { lineNumber: 4, column: 9 }))).toEqual( + StatementPosition.Function + ); + }); + + it('should return StatementPosition.FunctionArg when providing arguments to a function', () => { + expect(getStatementPosition(generateToken(multiLineFullQuery.query, { lineNumber: 4, column: 16 }))).toEqual( + StatementPosition.FunctionArg + ); + }); + + it('should return StatementPosition.AfterFunction after a function', () => { + expect(getStatementPosition(generateToken(multiLineFullQuery.query, { lineNumber: 4, column: 64 }))).toEqual( + StatementPosition.AfterFunction + ); + }); + + it('should return StatementPosition.ArithmeticOperatorArg after an arithmetic operator', () => { + expect(getStatementPosition(generateToken(multiLineFullQuery.query, { lineNumber: 4, column: 51 }))).toEqual( + StatementPosition.ArithmeticOperatorArg + ); + expect(getStatementPosition(generateToken(multiLineFullQuery.query, { lineNumber: 4, column: 58 }))).toEqual( + StatementPosition.ArithmeticOperatorArg + ); + }); + + it('should return StatementPosition.BooleanOperatorArg after a boolean operator', () => { + expect(getStatementPosition(generateToken(multiLineFullQuery.query, { lineNumber: 2, column: 37 }))).toEqual( + StatementPosition.BooleanOperatorArg + ); + }); + + it('should return StatementPosition.ComparisonOperatorArg after a comparison operator', () => { + expect(getStatementPosition(generateToken(multiLineFullQuery.query, { lineNumber: 2, column: 44 }))).toEqual( + StatementPosition.ComparisonOperatorArg + ); + }); + + it('should return StatementPosition.ArithmeticOperator after an arithmetic operator', () => { + expect(getStatementPosition(generateToken(multiLineFullQuery.query, { lineNumber: 4, column: 50 }))).toEqual( + StatementPosition.ArithmeticOperator + ); + expect(getStatementPosition(generateToken(multiLineFullQuery.query, { lineNumber: 4, column: 57 }))).toEqual( + StatementPosition.ArithmeticOperator + ); + }); + + it('should return StatementPosition.BooleanOperator after a boolean operator', () => { + expect(getStatementPosition(generateToken(multiLineFullQuery.query, { lineNumber: 2, column: 35 }))).toEqual( + StatementPosition.BooleanOperator + ); + }); + + it('should return StatementPosition.ComparisonOperator after a comparison operator', () => { + expect(getStatementPosition(generateToken(multiLineFullQuery.query, { lineNumber: 2, column: 43 }))).toEqual( + StatementPosition.ComparisonOperator + ); + }); + + it('should return StatementPosition.Comment when token is a comment', () => { + expect(getStatementPosition(generateToken(singleLineFullQuery.query, { lineNumber: 1, column: 40 }))).toEqual( + StatementPosition.Comment + ); + expect(getStatementPosition(generateToken(commentOnlyQuery.query, { lineNumber: 1, column: 35 }))).toEqual( + StatementPosition.Comment + ); + expect(getStatementPosition(generateToken(multiLineFullQuery.query, { lineNumber: 5, column: 3 }))).toEqual( + StatementPosition.Comment + ); + }); +}); diff --git a/public/app/plugins/datasource/cloudwatch/language/logs/completion/statementPosition.ts b/public/app/plugins/datasource/cloudwatch/language/logs/completion/statementPosition.ts new file mode 100644 index 00000000000..0c05bc57119 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/language/logs/completion/statementPosition.ts @@ -0,0 +1,203 @@ +import { LinkedToken } from '../../monarch/LinkedToken'; +import { StatementPosition } from '../../monarch/types'; +import { + DISPLAY, + FIELDS, + FILTER, + STATS, + SORT, + LIMIT, + PARSE, + DEDUP, + LOGS_COMMANDS, + LOGS_FUNCTION_OPERATORS, + LOGS_LOGIC_OPERATORS, +} from '../language'; + +import { LogsTokenTypes } from './types'; + +export const getStatementPosition = (currentToken: LinkedToken | null): StatementPosition => { + const previousNonWhiteSpace = currentToken?.getPreviousNonWhiteSpaceToken(); + const nextNonWhiteSpace = currentToken?.getNextNonWhiteSpaceToken(); + + const normalizedCurrentToken = currentToken?.value?.toLowerCase(); + const normalizedPreviousNonWhiteSpace = previousNonWhiteSpace?.value?.toLowerCase(); + + if (currentToken?.is(LogsTokenTypes.Comment)) { + return StatementPosition.Comment; + } + + if (currentToken?.isFunction()) { + return StatementPosition.Function; + } + + if ( + currentToken === null || + (currentToken?.isWhiteSpace() && previousNonWhiteSpace === null && nextNonWhiteSpace === null) || + (previousNonWhiteSpace?.is(LogsTokenTypes.Delimiter, '|') && currentToken?.isWhiteSpace()) || + (currentToken?.isIdentifier() && + (previousNonWhiteSpace?.is(LogsTokenTypes.Delimiter, '|') || previousNonWhiteSpace === null)) + ) { + return StatementPosition.NewCommand; + } + + if ( + currentToken?.is(LogsTokenTypes.Delimiter, ')') || + (currentToken?.isWhiteSpace() && previousNonWhiteSpace?.is(LogsTokenTypes.Delimiter, ')')) + ) { + const openingParenthesis = currentToken?.getPreviousOfType(LogsTokenTypes.Delimiter, '('); + const normalizedNonWhitespacePreceedingOpeningParenthesis = openingParenthesis + ?.getPreviousNonWhiteSpaceToken() + ?.value?.toLowerCase(); + + if (normalizedNonWhitespacePreceedingOpeningParenthesis) { + if (LOGS_COMMANDS.includes(normalizedNonWhitespacePreceedingOpeningParenthesis)) { + return StatementPosition.AfterCommand; + } + if (LOGS_FUNCTION_OPERATORS.includes(normalizedNonWhitespacePreceedingOpeningParenthesis)) { + return StatementPosition.AfterFunction; + } + } + } + + if (currentToken?.isKeyword() && normalizedCurrentToken) { + switch (normalizedCurrentToken) { + case DEDUP: + return StatementPosition.DedupKeyword; + case DISPLAY: + return StatementPosition.DisplayKeyword; + case FIELDS: + return StatementPosition.FieldsKeyword; + case FILTER: + return StatementPosition.FilterKeyword; + case LIMIT: + return StatementPosition.LimitKeyword; + case PARSE: + return StatementPosition.ParseKeyword; + case STATS: + return StatementPosition.StatsKeyword; + case SORT: + return StatementPosition.SortKeyword; + case 'as': + return StatementPosition.AsKeyword; + case 'by': + return StatementPosition.ByKeyword; + case 'in': + return StatementPosition.InKeyword; + case 'like': + return StatementPosition.LikeKeyword; + } + } + + if (currentToken?.isWhiteSpace() && previousNonWhiteSpace?.isKeyword && normalizedPreviousNonWhiteSpace) { + switch (normalizedPreviousNonWhiteSpace) { + case DEDUP: + return StatementPosition.AfterDedupKeyword; + case DISPLAY: + return StatementPosition.AfterDisplayKeyword; + case FIELDS: + return StatementPosition.AfterFieldsKeyword; + case FILTER: + return StatementPosition.AfterFilterKeyword; + case LIMIT: + return StatementPosition.AfterLimitKeyword; + case PARSE: + return StatementPosition.AfterParseKeyword; + case STATS: + return StatementPosition.AfterStatsKeyword; + case SORT: + return StatementPosition.AfterSortKeyword; + case 'as': + return StatementPosition.AfterAsKeyword; + case 'by': + return StatementPosition.AfterByKeyword; + case 'in': + return StatementPosition.AfterInKeyword; + case 'like': + return StatementPosition.AfterLikeKeyword; + } + } + + if (currentToken?.is(LogsTokenTypes.Operator) && normalizedCurrentToken) { + if (['+', '-', '*', '/', '^', '%'].includes(normalizedCurrentToken)) { + return StatementPosition.ArithmeticOperator; + } + + if (['=', '!=', '<', '>', '<=', '>='].includes(normalizedCurrentToken)) { + return StatementPosition.ComparisonOperator; + } + + if (LOGS_LOGIC_OPERATORS.includes(normalizedCurrentToken)) { + return StatementPosition.BooleanOperator; + } + } + + if (previousNonWhiteSpace?.is(LogsTokenTypes.Operator) && normalizedPreviousNonWhiteSpace) { + if (['+', '-', '*', '/', '^', '%'].includes(normalizedPreviousNonWhiteSpace)) { + return StatementPosition.ArithmeticOperatorArg; + } + + if (['=', '!=', '<', '>', '<=', '>='].includes(normalizedPreviousNonWhiteSpace)) { + return StatementPosition.ComparisonOperatorArg; + } + + if (LOGS_LOGIC_OPERATORS.includes(normalizedPreviousNonWhiteSpace)) { + return StatementPosition.BooleanOperatorArg; + } + } + + if ( + currentToken?.isIdentifier() || + currentToken?.isNumber() || + currentToken?.is(LogsTokenTypes.Parenthesis, '()') || + currentToken?.is(LogsTokenTypes.Delimiter, ',') || + currentToken?.is(LogsTokenTypes.Parenthesis, ')') || + (currentToken?.isWhiteSpace() && previousNonWhiteSpace?.is(LogsTokenTypes.Delimiter, ',')) || + (currentToken?.isWhiteSpace() && previousNonWhiteSpace?.isIdentifier()) || + (currentToken?.isWhiteSpace() && + previousNonWhiteSpace?.isKeyword() && + normalizedPreviousNonWhiteSpace && + LOGS_COMMANDS.includes(normalizedPreviousNonWhiteSpace)) + ) { + const nearestKeyword = currentToken?.getPreviousOfType(LogsTokenTypes.Keyword); + const nearestFunction = currentToken?.getPreviousOfType(LogsTokenTypes.Function); + + if (nearestKeyword !== null && nearestFunction === null) { + if (nearestKeyword.value === SORT) { + return StatementPosition.SortArg; + } + if (nearestKeyword.value === FILTER) { + return StatementPosition.FilterArg; + } + return StatementPosition.CommandArg; + } + + if (nearestFunction !== null && nearestKeyword === null) { + return StatementPosition.FunctionArg; + } + + if (nearestKeyword !== null && nearestFunction !== null) { + if ( + nearestKeyword.range.startLineNumber > nearestFunction.range.startLineNumber || + nearestKeyword.range.endColumn > nearestFunction.range.endColumn + ) { + if (nearestKeyword.value === SORT) { + return StatementPosition.SortArg; + } + if (nearestKeyword.value === FILTER) { + return StatementPosition.FilterArg; + } + return StatementPosition.CommandArg; + } + + if ( + nearestFunction.range.startLineNumber > nearestKeyword.range.startLineNumber || + nearestFunction.range.endColumn > nearestKeyword.range.endColumn + ) { + return StatementPosition.FunctionArg; + } + } + } + + return StatementPosition.Unknown; +}; diff --git a/public/app/plugins/datasource/cloudwatch/language/monarch/types.ts b/public/app/plugins/datasource/cloudwatch/language/monarch/types.ts index ffd57ebee97..4e7a87fcd7e 100644 --- a/public/app/plugins/datasource/cloudwatch/language/monarch/types.ts +++ b/public/app/plugins/datasource/cloudwatch/language/monarch/types.ts @@ -45,6 +45,49 @@ export enum StatementPosition { PredefinedFuncSecondArg, AfterFunction, WithinString, + // logs + NewCommand, + Comment, + + DedupKeyword, + AfterDedupKeyword, + DisplayKeyword, + AfterDisplayKeyword, + FieldsKeyword, + AfterFieldsKeyword, + FilterKeyword, + AfterFilterKeyword, + FilterArg, + LimitKeyword, + AfterLimitKeyword, + ParseKeyword, + AfterParseKeyword, + SortKeyword, + AfterSortKeyword, + SortArg, + StatsKeyword, + AfterStatsKeyword, + + AsKeyword, + AfterAsKeyword, + ByKeyword, + AfterByKeyword, + InKeyword, + AfterInKeyword, + LikeKeyword, + AfterLikeKeyword, + + Function, + FunctionArg, + CommandArg, + AfterCommand, + + ArithmeticOperator, + ArithmeticOperatorArg, + BooleanOperator, + BooleanOperatorArg, + ComparisonOperator, + ComparisonOperatorArg, } export enum SuggestionKind {