From 42d7c32759c33e04e00a4739b9fc508c6a115121 Mon Sep 17 00:00:00 2001 From: Isabella Siu Date: Mon, 4 Oct 2021 09:17:59 -0400 Subject: [PATCH] AzureMonitor: Remove unused editor component (#39874) --- .../annotations_query_ctrl.ts | 83 -- .../editor/KustoQueryField.tsx | 445 ----------- .../editor/editor_component.tsx | 81 -- .../editor/kusto/kusto.ts | 720 ------------------ .../editor/query_field.tsx | 341 --------- .../editor/typeahead.tsx | 77 -- .../partials/query.editor.html | 531 ------------- .../query_ctrl.test.ts | 138 ---- .../query_ctrl.ts | 569 -------------- 9 files changed, 2985 deletions(-) delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/annotations_query_ctrl.ts delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto/kusto.ts delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/typeahead.tsx delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.test.ts delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/annotations_query_ctrl.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/annotations_query_ctrl.ts deleted file mode 100644 index c8200c3ccc1..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/annotations_query_ctrl.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; - -export class AzureMonitorAnnotationsQueryCtrl { - static templateUrl = 'partials/annotations.editor.html'; - declare datasource: any; - declare annotation: any; - declare workspaces: any[]; - declare subscriptions: Array<{ text: string; value: string }>; - private templateSrv: TemplateSrv = getTemplateSrv(); - - defaultQuery = - '\n| where $__timeFilter() \n| project TimeGenerated, Text=YourTitleColumn, Tags="tag1,tag2"'; - - /** @ngInject */ - constructor($scope: any) { - this.annotation = $scope.ctrl.annotation; - this.datasource = $scope.ctrl.datasource; - - this.annotation.queryType = this.annotation.queryType || 'Azure Log Analytics'; - this.annotation.rawQuery = this.annotation.rawQuery || this.defaultQuery; - this.initDropdowns(); - } - - async initDropdowns() { - await this.getSubscriptions(); - await this.getWorkspaces(); - } - - async getSubscriptions() { - if (!this.datasource.azureMonitorDatasource.isConfigured()) { - return; - } - - return this.datasource.azureMonitorDatasource.getSubscriptions().then((subs: any[]) => { - this.subscriptions = subs; - - if (!this.annotation.subscription && this.annotation.queryType === 'Azure Log Analytics') { - this.annotation.subscription = this.datasource.azureLogAnalyticsDatasource.subscriptionId; - } - - if (!this.annotation.subscription && this.subscriptions.length > 0) { - this.annotation.subscription = this.subscriptions[0].value; - } - }); - } - - async getWorkspaces(bustCache?: boolean) { - if (!bustCache && this.workspaces && this.workspaces.length > 0) { - return this.workspaces; - } - - return this.datasource - .getAzureLogAnalyticsWorkspaces(this.annotation.subscription) - .then((list: any[]) => { - this.workspaces = list; - if (list.length > 0 && !this.annotation.workspace) { - this.annotation.workspace = list[0].value; - } - return this.workspaces; - }) - .catch(() => {}); - } - - getAzureLogAnalyticsSchema = () => { - return this.getWorkspaces() - .then(() => { - return this.datasource.azureLogAnalyticsDatasource.getSchema(this.annotation.workspace); - }) - .catch(() => {}); - }; - - onSubscriptionChange = () => { - this.getWorkspaces(true); - }; - - onLogAnalyticsQueryChange = (nextQuery: string) => { - this.annotation.rawQuery = nextQuery; - }; - - get templateVariables() { - return this.templateSrv.getVariables().map((t: any) => '$' + t.name); - } -} diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx deleted file mode 100644 index f1248b48554..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx +++ /dev/null @@ -1,445 +0,0 @@ -import { debounce, map } from 'lodash'; -import Plain from 'slate-plain-serializer'; - -import QueryField from './query_field'; -import { DOMUtil } from '@grafana/ui'; -import { Editor as CoreEditor } from 'slate'; - -import { KEYWORDS, functionTokens, operatorTokens, grafanaMacros } from './kusto/kusto'; -// import '../sass/editor.base.scss'; - -const TYPEAHEAD_DELAY = 100; - -interface Suggestion { - text: string; - deleteBackwards?: number; - type?: string; -} - -interface SuggestionGroup { - label: string; - items: Suggestion[]; - prefixMatch?: boolean; - skipFilter?: boolean; -} - -interface KustoSchema { - Databases: { - Default: KustoDBSchema; - }; - Plugins?: any[]; -} - -interface KustoDBSchema { - Name?: string; - Functions?: any; - Tables?: any; -} - -const defaultSchema: any = () => ({ - Databases: { - Default: {}, - }, -}); - -const cleanText = (s: string) => s.replace(/[{}[\]="(),!~+\-*/^%]/g, '').trim(); -const wrapText = (text: string) => ({ text }); - -export default class KustoQueryField extends QueryField { - fields: any; - events: any; - schema: KustoSchema; - - constructor(props: any, context: any) { - super(props, context); - this.schema = defaultSchema(); - - this.onTypeahead = debounce(this.onTypeahead, TYPEAHEAD_DELAY); - } - - componentDidMount() { - super.componentDidMount(); - this.fetchSchema(); - } - - onTypeahead = (force = false) => { - const selection = window.getSelection(); - - if (selection && selection.anchorNode) { - const wrapperNode = selection.anchorNode.parentElement; - if (wrapperNode === null) { - return; - } - const editorNode = wrapperNode.closest('.slate-query-field'); - if (!editorNode || this.state.value.isBlurred) { - // Not inside this editor - return; - } - - // DOM ranges - const range = selection.getRangeAt(0); - const text = selection.anchorNode.textContent; - if (text === null) { - return; - } - const offset = range.startOffset; - let prefix = cleanText(text.substr(0, offset)); - - // Model ranges - const modelOffset = this.state.value.anchorOffset; - const modelPrefix = this.state.value.anchorText.text.slice(0, modelOffset); - - // Determine candidates by context - let suggestionGroups: SuggestionGroup[] = []; - const wrapperClasses = wrapperNode.classList; - let typeaheadContext: string | null = null; - - // Built-in functions - if (wrapperClasses.contains('function-context')) { - typeaheadContext = 'context-function'; - suggestionGroups = this.getColumnSuggestions(); - - // where - } else if (modelPrefix.match(/(where\s(\w+\b)?$)/i)) { - typeaheadContext = 'context-where'; - suggestionGroups = this.getColumnSuggestions(); - - // summarize by - } else if (modelPrefix.match(/(summarize\s(\w+\b)?$)/i)) { - typeaheadContext = 'context-summarize'; - suggestionGroups = this.getFunctionSuggestions(); - } else if (modelPrefix.match(/(summarize\s(.+\s)?by\s+([^,\s]+,\s*)*([^,\s]+\b)?$)/i)) { - typeaheadContext = 'context-summarize-by'; - suggestionGroups = this.getColumnSuggestions(); - - // order by, top X by, ... by ... - } else if (modelPrefix.match(/(by\s+([^,\s]+,\s*)*([^,\s]+\b)?$)/i)) { - typeaheadContext = 'context-by'; - suggestionGroups = this.getColumnSuggestions(); - - // join - } else if (modelPrefix.match(/(on\s(.+\b)?$)/i)) { - typeaheadContext = 'context-join-on'; - suggestionGroups = this.getColumnSuggestions(); - } else if (modelPrefix.match(/(join\s+(\(\s+)?(\w+\b)?$)/i)) { - typeaheadContext = 'context-join'; - suggestionGroups = this.getTableSuggestions(); - - // distinct - } else if (modelPrefix.match(/(distinct\s(.+\b)?$)/i)) { - typeaheadContext = 'context-distinct'; - suggestionGroups = this.getColumnSuggestions(); - - // database() - } else if (modelPrefix.match(/(database\(\"(\w+)\"\)\.(.+\b)?$)/i)) { - typeaheadContext = 'context-database-table'; - const db = this.getDBFromDatabaseFunction(modelPrefix); - suggestionGroups = this.getTableSuggestions(db); - prefix = prefix.replace('.', ''); - - // new - } else if (normalizeQuery(Plain.serialize(this.state.value)).match(/^\s*\w*$/i)) { - typeaheadContext = 'context-new'; - if (this.schema) { - suggestionGroups = this.getInitialSuggestions(); - } else { - this.fetchSchema(); - setTimeout(this.onTypeahead, 0); - return; - } - - // built-in - } else if (prefix && !wrapperClasses.contains('argument') && !force) { - // Use only last typed word as a prefix for searching - if (modelPrefix.match(/\s$/i)) { - prefix = ''; - return; - } - prefix = getLastWord(prefix); - typeaheadContext = 'context-builtin'; - suggestionGroups = this.getKeywordSuggestions(); - } else if (force === true) { - typeaheadContext = 'context-builtin-forced'; - if (modelPrefix.match(/\s$/i)) { - prefix = ''; - } - suggestionGroups = this.getKeywordSuggestions(); - } - - let results = 0; - prefix = prefix.toLowerCase(); - const filteredSuggestions = suggestionGroups - .map((group) => { - if (group.items && prefix && !group.skipFilter) { - group.items = group.items.filter((c) => c.text.length >= prefix.length); - if (group.prefixMatch) { - group.items = group.items.filter((c) => c.text.toLowerCase().indexOf(prefix) === 0); - } else { - group.items = group.items.filter((c) => c.text.toLowerCase().indexOf(prefix) > -1); - } - } - results += group.items.length; - return group; - }) - .filter((group) => group.items.length > 0); - - // console.log('onTypeahead', selection.anchorNode, wrapperClasses, text, offset, prefix, typeaheadContext); - // console.log('onTypeahead', prefix, typeaheadContext, force); - - this.setState({ - typeaheadPrefix: prefix, - typeaheadContext, - typeaheadText: text, - suggestions: results > 0 ? filteredSuggestions : [], - }); - } - }; - - applyTypeahead = (editor: CoreEditor, suggestion: { text: any; type: string; deleteBackwards: any }): CoreEditor => { - const { typeaheadPrefix, typeaheadContext, typeaheadText } = this.state; - let suggestionText = suggestion.text || suggestion; - const move = 0; - - // Modify suggestion based on context - - const nextChar = DOMUtil.getNextCharacter(); - if (suggestion.type === 'function') { - if (!nextChar || nextChar !== '(') { - suggestionText += '('; - } - } else if (typeaheadContext === 'context-function') { - if (!nextChar || nextChar !== ')') { - suggestionText += ')'; - } - } else { - if (!nextChar || nextChar !== ' ') { - suggestionText += ' '; - } - } - - // Remove the current, incomplete text and replace it with the selected suggestion - const backward = suggestion.deleteBackwards || typeaheadPrefix.length; - const text = cleanText(typeaheadText); - const suffixLength = text.length - typeaheadPrefix.length; - const offset = typeaheadText.indexOf(typeaheadPrefix); - const midWord = typeaheadPrefix && ((suffixLength > 0 && offset > -1) || suggestionText === typeaheadText); - const forward = midWord ? suffixLength + offset : 0; - - this.resetTypeahead(() => - editor.deleteBackward(backward).deleteForward(forward).insertText(suggestionText).moveForward(move).focus() - ); - - return editor; - }; - - // private _getFieldsSuggestions(): SuggestionGroup[] { - // return [ - // { - // prefixMatch: true, - // label: 'Fields', - // items: this.fields.map(wrapText) - // }, - // { - // prefixMatch: true, - // label: 'Variables', - // items: this.props.templateVariables.map(wrapText) - // } - // ]; - // } - - // private _getAfterFromSuggestions(): SuggestionGroup[] { - // return [ - // { - // skipFilter: true, - // label: 'Events', - // items: this.events.map(wrapText) - // }, - // { - // prefixMatch: true, - // label: 'Variables', - // items: this.props.templateVariables - // .map(wrapText) - // .map(suggestion => { - // suggestion.deleteBackwards = 0; - // return suggestion; - // }) - // } - // ]; - // } - - // private _getAfterSelectSuggestions(): SuggestionGroup[] { - // return [ - // { - // prefixMatch: true, - // label: 'Fields', - // items: this.fields.map(wrapText) - // }, - // { - // prefixMatch: true, - // label: 'Functions', - // items: FUNCTIONS.map((s: any) => { s.type = 'function'; return s; }) - // }, - // { - // prefixMatch: true, - // label: 'Variables', - // items: this.props.templateVariables.map(wrapText) - // } - // ]; - // } - - private getInitialSuggestions(): SuggestionGroup[] { - return this.getTableSuggestions(); - } - - private getKeywordSuggestions(): SuggestionGroup[] { - return [ - { - prefixMatch: true, - label: 'Keywords', - items: KEYWORDS.map(wrapText), - }, - { - prefixMatch: true, - label: 'Operators', - items: operatorTokens, - }, - { - prefixMatch: true, - label: 'Functions', - items: functionTokens.map((s: any) => { - s.type = 'function'; - return s; - }), - }, - { - prefixMatch: true, - label: 'Macros', - items: grafanaMacros.map((s: any) => { - s.type = 'function'; - return s; - }), - }, - { - prefixMatch: true, - label: 'Tables', - items: map(this.schema.Databases.Default.Tables, (t: any) => ({ text: t.Name })), - }, - ]; - } - - private getFunctionSuggestions(): SuggestionGroup[] { - return [ - { - prefixMatch: true, - label: 'Functions', - items: functionTokens.map((s: any) => { - s.type = 'function'; - return s; - }), - }, - { - prefixMatch: true, - label: 'Macros', - items: grafanaMacros.map((s: any) => { - s.type = 'function'; - return s; - }), - }, - ]; - } - - getTableSuggestions(db = 'Default'): SuggestionGroup[] { - // @ts-ignore - if (this.schema.Databases[db]) { - return [ - { - prefixMatch: true, - label: 'Tables', - // @ts-ignore - items: map(this.schema.Databases[db].Tables, (t: any) => ({ text: t.Name })), - }, - ]; - } else { - return []; - } - } - - private getColumnSuggestions(): SuggestionGroup[] { - const table = this.getTableFromContext(); - if (table) { - const tableSchema = this.schema.Databases.Default.Tables[table]; - if (tableSchema) { - return [ - { - prefixMatch: true, - label: 'Fields', - items: map(tableSchema.OrderedColumns, (f: any) => ({ - text: f.Name, - hint: f.Type, - })), - }, - ]; - } - } - return []; - } - - private getTableFromContext() { - const query = Plain.serialize(this.state.value); - const tablePattern = /^\s*(\w+)\s*|/g; - const normalizedQuery = normalizeQuery(query); - const match = tablePattern.exec(normalizedQuery); - if (match && match.length > 1 && match[0] && match[1]) { - return match[1]; - } else { - return null; - } - } - - private getDBFromDatabaseFunction(prefix: string) { - const databasePattern = /database\(\"(\w+)\"\)/gi; - const match = databasePattern.exec(prefix); - if (match && match.length > 1 && match[0] && match[1]) { - return match[1]; - } else { - return undefined; - } - } - - private async fetchSchema() { - let schema = await this.props.getSchema(); - if (schema) { - if (schema.Type === 'AppInsights') { - schema = castSchema(schema); - } - this.schema = schema; - } else { - this.schema = defaultSchema(); - } - } -} - -/** - * Cast schema from App Insights to default Kusto schema - */ -function castSchema(schema: any) { - const defaultSchemaTemplate = defaultSchema(); - defaultSchemaTemplate.Databases.Default = schema; - return defaultSchemaTemplate; -} - -function normalizeQuery(query: string): string { - const commentPattern = /\/\/.*$/gm; - let normalizedQuery = query.replace(commentPattern, ''); - normalizedQuery = normalizedQuery.replace('\n', ' '); - return normalizedQuery; -} - -function getLastWord(str: string): string { - const lastWordPattern = /(?:.*\s)?([^\s]+\s*)$/gi; - const match = lastWordPattern.exec(str); - if (match && match.length > 1) { - return match[1]; - } - return ''; -} diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx deleted file mode 100644 index 355c354fd80..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import KustoQueryField from './KustoQueryField'; -import Kusto from './kusto/kusto'; - -import React, { Component } from 'react'; -import coreModule from 'app/core/core_module'; - -interface EditorProps { - index: number; - placeholder?: string; - change: (value: string, index: number) => void; - variables: () => string[] | string[]; - getSchema?: () => Promise; - execute?: () => void; - query?: string; -} - -class Editor extends Component { - static defaultProps = { - placeholder: 'Enter a query', - }; - - constructor(props: EditorProps) { - super(props); - this.state = { - edited: false, - query: props.query || '', - }; - } - - onChangeQuery = (value: any) => { - const { index, change } = this.props; - const { query } = this.state; - const edited = query !== value; - this.setState({ edited, query: value }); - if (change) { - change(value, index); - } - }; - - onPressEnter = () => { - const { execute } = this.props; - if (execute) { - execute(); - } - }; - - render() { - const { variables, getSchema, placeholder } = this.props; - const { edited, query } = this.state; - - return ( -
- -
- ); - } -} - -coreModule.directive('kustoEditor', [ - 'reactDirective', - (reactDirective) => { - return reactDirective(Editor, [ - 'change', - 'database', - 'execute', - 'query', - 'variables', - 'placeholder', - ['getSchema', { watchDepth: 'reference' }], - ]); - }, -]); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto/kusto.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto/kusto.ts deleted file mode 100644 index c809f7d1228..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto/kusto.ts +++ /dev/null @@ -1,720 +0,0 @@ -/* eslint-disable max-len */ -export const operatorTokens = [ - { text: '!between', hint: 'Matches the input that is outside the inclusive range.' }, - { text: 'as', hint: "Binds a name to the operator's input tabular expression." }, - { text: 'between', hint: 'Matches the input that is inside the inclusive range.' }, - { - text: 'consume', - hint: - 'The `consume` operator consumes the tabular data stream handed to it. It is\r\nmostly used for triggering the query side-effect without actually returning\r\nthe results back to the caller.', - }, - { text: 'count', hint: 'Returns the number of records in the input record set.' }, - { text: 'datatable', hint: 'Returns a table whose schema and values are defined in the query itself.' }, - { - text: 'distinct', - hint: 'Produces a table with the distinct combination of the provided columns of the input table.', - }, - { text: 'evaluate', hint: 'Invokes a service-side query extension (plugin).' }, - { text: 'extend', hint: 'Create calculated columns and append them to the result set.' }, - { - text: 'externaldata', - hint: - 'Returns a table whose schema is defined in the query itself, and whose data is read from an external raw file.', - }, - { - text: 'facet', - hint: - 'Returns a set of tables, one for each specified column.\r\nEach table specifies the list of values taken by its column.\r\nAn additional table can be created by using the `with` clause.', - }, - { text: 'find', hint: 'Finds rows that match a predicate across a set of tables.' }, - { text: 'fork', hint: 'Runs multiple consumer operators in parallel.' }, - { text: 'getschema', hint: 'Produce a table that represents a tabular schema of the input.' }, - { text: 'in', hint: 'Filters a recordset based on the provided set of values.' }, - { text: 'invoke', hint: 'Invokes lambda that receives the source of `invoke` as tabular parameter argument.' }, - { - text: 'join', - hint: - 'Merge the rows of two tables to form a new table by matching values of the specified column(s) from each table.', - }, - { text: 'limit', hint: 'Return up to the specified number of rows.' }, - { text: 'make-series', hint: 'Create series of specified aggregated values along specified axis.' }, - { text: 'mvexpand', hint: 'Expands multi-value array or property bag.' }, - { text: 'order', hint: 'Sort the rows of the input table into order by one or more columns.' }, - { text: 'parse', hint: 'Evaluates a string expression and parses its value into one or more calculated columns.' }, - { - text: 'print', - hint: - 'Evaluates one or more scalar expressions and inserts the results (as a single-row table with as many columns as there are expressions) into the output.', - }, - { text: 'project', hint: 'Select the columns to include, rename or drop, and insert new computed columns.' }, - { text: 'project-away', hint: 'Select what columns to exclude from the input.' }, - { text: 'project-rename', hint: 'Renames columns in the result output.' }, - { text: 'range', hint: 'Generates a single-column table of values.' }, - { text: 'reduce', hint: 'Groups a set of strings together based on values similarity.' }, - { text: 'render', hint: 'Instructs the user agent to render the results of the query in a particular way.' }, - { text: 'sample', hint: 'Returns up to the specified number of random rows from the input table.' }, - { - text: 'sample-distinct', - hint: - 'Returns a single column that contains up to the specified number of distinct values of the requested column.', - }, - { text: 'search', hint: 'The search operator provides a multi-table/multi-column search experience.' }, - { text: 'serialize', hint: 'Marks that order of the input row set is safe for window functions usage.' }, - { text: 'sort', hint: 'Sort the rows of the input table into order by one or more columns.' }, - { text: 'summarize', hint: 'Produces a table that aggregates the content of the input table.' }, - { text: 'take', hint: 'Return up to the specified number of rows.' }, - { text: 'top', hint: 'Returns the first *N* records sorted by the specified columns.' }, - { - text: 'top-hitters', - hint: 'Returns an approximation of the first *N* results (assuming skewed distribution of the input).', - }, - { - text: 'top-nested', - hint: 'Produces hierarchical top results, where each level is a drill-down based on previous level values.', - }, - { text: 'union', hint: 'Takes two or more tables and returns the rows of all of them.' }, - { text: 'where', hint: 'Filters a table to the subset of rows that satisfy a predicate.' }, -]; - -export const functionTokens = [ - { text: 'abs', hint: 'Calculates the absolute value of the input.' }, - { - text: 'acos', - hint: - 'Returns the angle whose cosine is the specified number (the inverse operation of [`cos()`](cosfunction.md)) .', - }, - { text: 'ago', hint: 'Subtracts the given timespan from the current UTC clock time.' }, - { text: 'any', hint: 'Returns random non-empty value from the specified expression values.' }, - { - text: 'arg_max', - hint: - 'Finds a row in the group that maximizes *ExprToMaximize*, and returns the value of *ExprToReturn* (or `*` to return the entire row).', - }, - { - text: 'arg_min', - hint: - 'Finds a row in the group that minimizes *ExprToMinimize*, and returns the value of *ExprToReturn* (or `*` to return the entire row).', - }, - { - text: 'argmax', - hint: - 'Finds a row in the group that maximizes *ExprToMaximize*, and returns the value of *ExprToReturn* (or `*` to return the entire row).', - }, - { - text: 'argmin', - hint: - 'Finds a row in the group that minimizes *ExprToMinimize*, and returns the value of *ExprToReturn* (or `*` to return the entire row).', - }, - { text: 'array_concat', hint: 'Concatenates a number of dynamic arrays to a single array.' }, - { text: 'array_length', hint: 'Calculates the number of elements in a dynamic array.' }, - { text: 'array_slice', hint: 'Extracts a slice of a dynamic array.' }, - { - text: 'array_split', - hint: - 'Splits an array to multiple arrays according to the split indices and packs the generated array in a dynamic array.', - }, - { - text: 'asin', - hint: 'Returns the angle whose sine is the specified number (the inverse operation of [`sin()`](sinfunction.md)) .', - }, - { - text: 'assert', - hint: 'Checks for a condition; if the condition is false, outputs error messages and fails the query.', - }, - { - text: 'atan', - hint: - 'Returns the angle whose tangent is the specified number (the inverse operation of [`tan()`](tanfunction.md)) .', - }, - { - text: 'atan2', - hint: - 'Calculates the angle, in radians, between the positive x-axis and the ray from the origin to the point (y, x).', - }, - { text: 'avg', hint: 'Calculates the average of *Expr* across the group.' }, - { - text: 'avgif', - hint: - 'Calculates the [average](avg-aggfunction.md) of *Expr* across the group for which *Predicate* evaluates to `true`.', - }, - { text: 'bag_keys', hint: 'Enumerates all the root keys in a dynamic property-bag object.' }, - { text: 'base64_decodestring', hint: 'Decodes a base64 string to a UTF-8 string' }, - { text: 'base64_encodestring', hint: 'Encodes a string as base64 string' }, - { text: 'beta_cdf', hint: 'Returns the standard cumulative beta distribution function.' }, - { text: 'beta_inv', hint: 'Returns the inverse of the beta cumulative probability beta density function.' }, - { text: 'beta_pdf', hint: 'Returns the probability density beta function.' }, - { text: 'bin', hint: 'Rounds values down to an integer multiple of a given bin size.' }, - { - text: 'bin_at', - hint: - "Rounds values down to a fixed-size 'bin', with control over the bin's starting point.\r\n(See also [`bin function`](./binfunction.md).)", - }, - { - text: 'bin_auto', - hint: - "Rounds values down to a fixed-size 'bin', with control over the bin size and starting point provided by a query property.", - }, - { text: 'binary_and', hint: 'Returns a result of the bitwise `and` operation between two values.' }, - { text: 'binary_not', hint: 'Returns a bitwise negation of the input value.' }, - { text: 'binary_or', hint: 'Returns a result of the bitwise `or` operation of the two values.' }, - { text: 'binary_shift_left', hint: 'Returns binary shift left operation on a pair of numbers.' }, - { text: 'binary_shift_right', hint: 'Returns binary shift right operation on a pair of numbers.' }, - { text: 'binary_xor', hint: 'Returns a result of the bitwise `xor` operation of the two values.' }, - { text: 'buildschema', hint: 'Returns the minimal schema that admits all values of *DynamicExpr*.' }, - { - text: 'case', - hint: 'Evaluates a list of predicates and returns the first result expression whose predicate is satisfied.', - }, - { - text: 'ceiling', - hint: 'Calculates the smallest integer greater than, or equal to, the specified numeric expression.', - }, - { text: 'cluster', hint: 'Changes the reference of the query to a remote cluster.' }, - { - text: 'coalesce', - hint: 'Evaluates a list of expressions and returns the first non-null (or non-empty for string) expression.', - }, - { text: 'cos', hint: 'Returns the cosine function.' }, - { text: 'cot', hint: 'Calculates the trigonometric cotangent of the specified angle, in radians.' }, - { - text: 'count', - hint: - 'Returns a count of the records per summarization group (or in total if summarization is done without grouping).', - }, - { text: 'countif', hint: 'Returns a count of rows for which *Predicate* evaluates to `true`.' }, - { - text: 'countof', - hint: 'Counts occurrences of a substring in a string. Plain string matches may overlap; regex matches do not.', - }, - { text: 'current_principal', hint: 'Returns the current principal running this query.' }, - { - text: 'cursor_after', - hint: 'A predicate over the records of a table to compare their ingestion time\r\nagainst a database cursor.', - }, - { - text: 'cursor_before_or_at', - hint: 'A predicate over the records of a table to compare their ingestion time\r\nagainst a database cursor.', - }, - { text: 'database', hint: 'Changes the reference of the query to a specific database within the cluster scope.' }, - { - text: 'datetime_add', - hint: - 'Calculates a new [datetime](./scalar-data-types/datetime.md) from a specified datepart multiplied by a specified amount, added to a specified [datetime](./scalar-data-types/datetime.md).', - }, - { - text: 'datetime_diff', - hint: 'Calculates calendarian difference between two [datetime](./scalar-data-types/datetime.md) values.', - }, - { text: 'datetime_part', hint: 'Extracts the requested date part as an integer value.' }, - { text: 'dayofmonth', hint: 'Returns the integer number representing the day number of the given month' }, - { text: 'dayofweek', hint: 'Returns the integer number of days since the preceding Sunday, as a `timespan`.' }, - { text: 'dayofyear', hint: 'Returns the integer number represents the day number of the given year.' }, - { text: 'dcount', hint: 'Returns an estimate of the number of distinct values of *Expr* in the group.' }, - { - text: 'dcount_hll', - hint: - 'Calculates the dcount from hll results (which was generated by [hll](hll-aggfunction.md) or [hll_merge](hll-merge-aggfunction.md)).', - }, - { - text: 'dcountif', - hint: - 'Returns an estimate of the number of distinct values of *Expr* of rows for which *Predicate* evaluates to `true`.', - }, - { - text: 'degrees', - hint: - 'Converts angle value in radians into value in degrees, using formula `degrees = (180 / PI ) * angle_in_radians`', - }, - { text: 'distance', hint: 'Returns the distance between two points in meters.' }, - { text: 'endofday', hint: 'Returns the end of the day containing the date, shifted by an offset, if provided.' }, - { text: 'endofmonth', hint: 'Returns the end of the month containing the date, shifted by an offset, if provided.' }, - { text: 'endofweek', hint: 'Returns the end of the week containing the date, shifted by an offset, if provided.' }, - { text: 'endofyear', hint: 'Returns the end of the year containing the date, shifted by an offset, if provided.' }, - { - text: 'estimate_data_size', - hint: 'Returns an estimated data size of the selected columns of the tabular expression.', - }, - { text: 'exp', hint: 'The base-e exponential function of x, which is e raised to the power x: e^x.' }, - { - text: 'exp10', - hint: 'The base-10 exponential function of x, which is 10 raised to the power x: 10^x. \r\n**Syntax**', - }, - { text: 'exp2', hint: 'The base-2 exponential function of x, which is 2 raised to the power x: 2^x.' }, - { - text: 'extent_id', - hint: 'Returns a unique identifier that identifies the data shard ("extent") that the current record resides in.', - }, - { - text: 'extent_tags', - hint: - 'Returns a dynamic array with the [tags](../management/extents-overview.md#extent-tagging) of the data shard ("extent") that the current record resides in.', - }, - { text: 'extract', hint: 'Get a match for a [regular expression](./re2.md) from a text string.' }, - { text: 'extract_all', hint: 'Get all matches for a [regular expression](./re2.md) from a text string.' }, - { text: 'extractjson', hint: 'Get a specified element out of a JSON text using a path expression.' }, - { text: 'floor', hint: 'An alias for [`bin()`](binfunction.md).' }, - { text: 'format_datetime', hint: 'Formats a datetime parameter based on the format pattern parameter.' }, - { text: 'format_timespan', hint: 'Formats a timespan parameter based on the format pattern parameter.' }, - { text: 'gamma', hint: 'Computes [gamma function](https://en.wikipedia.org/wiki/Gamma_function)' }, - { text: 'getmonth', hint: 'Get the month number (1-12) from a datetime.' }, - { text: 'gettype', hint: 'Returns the runtime type of its single argument.' }, - { text: 'getyear', hint: 'Returns the year part of the `datetime` argument.' }, - { text: 'hash', hint: 'Returns a hash value for the input value.' }, - { text: 'hash_sha256', hint: 'Returns a sha256 hash value for the input value.' }, - { text: 'hll', hint: 'Calculates the Intermediate results of [dcount](dcount-aggfunction.md) across the group.' }, - { - text: 'hll_merge', - hint: 'Merges hll results (scalar version of the aggregate version [`hll_merge()`](hll-merge-aggfunction.md)).', - }, - { text: 'hourofday', hint: 'Returns the integer number representing the hour number of the given date' }, - { - text: 'iff', - hint: - 'Evaluates the first argument (the predicate), and returns the value of either the second or third arguments, depending on whether the predicate evaluated to `true` (second) or `false` (third).', - }, - { - text: 'iif', - hint: - 'Evaluates the first argument (the predicate), and returns the value of either the second or third arguments, depending on whether the predicate evaluated to `true` (second) or `false` (third).', - }, - { - text: 'indexof', - hint: 'Function reports the zero-based index of the first occurrence of a specified string within input string.', - }, - { text: 'ingestion_time', hint: "Retrieves the record's `$IngestionTime` hidden `datetime` column, or null." }, - { - text: 'iscolumnexists', - hint: - 'Returns a boolean value indicating if the given string argument exists in the schema produced by the preceding tabular operator.', - }, - { text: 'isempty', hint: 'Returns `true` if the argument is an empty string or is null.' }, - { text: 'isfinite', hint: 'Returns whether input is a finite value (is neither infinite nor NaN).' }, - { text: 'isinf', hint: 'Returns whether input is an infinite (positive or negative) value.' }, - { text: 'isnan', hint: 'Returns whether input is Not-a-Number (NaN) value.' }, - { text: 'isnotempty', hint: 'Returns `true` if the argument is not an empty string nor it is a null.' }, - { text: 'isnotnull', hint: 'Returns `true` if the argument is not null.' }, - { - text: 'isnull', - hint: - 'Evaluates its sole argument and returns a `bool` value indicating if the argument evaluates to a null value.', - }, - { text: 'log', hint: 'Returns the natural logarithm function.' }, - { text: 'log10', hint: 'Returns the common (base-10) logarithm function.' }, - { text: 'log2', hint: 'Returns the base-2 logarithm function.' }, - { - text: 'loggamma', - hint: 'Computes log of absolute value of the [gamma function](https://en.wikipedia.org/wiki/Gamma_function)', - }, - { - text: 'make_datetime', - hint: 'Creates a [datetime](./scalar-data-types/datetime.md) scalar value from the specified date and time.', - }, - { - text: 'make_dictionary', - hint: 'Returns a `dynamic` (JSON) property-bag (dictionary) of all the values of *Expr* in the group.', - }, - { text: 'make_string', hint: 'Returns the string generated by the Unicode characters.' }, - { - text: 'make_timespan', - hint: 'Creates a [timespan](./scalar-data-types/timespan.md) scalar value from the specified time period.', - }, - { text: 'makelist', hint: 'Returns a `dynamic` (JSON) array of all the values of *Expr* in the group.' }, - { - text: 'makeset', - hint: 'Returns a `dynamic` (JSON) array of the set of distinct values that *Expr* takes in the group.', - }, - { - text: 'materialize', - hint: - 'Allows caching a sub-query result during the time of query execution in a way that other subqueries can reference the partial result.', - }, - { text: 'max', hint: 'Returns the maximum value across the group.' }, - { text: 'max_of', hint: 'Returns the maximum value of several evaluated numeric expressions.' }, - { - text: 'merge_tdigests', - hint: - 'Merges tdigest results (scalar version of the aggregate version [`merge_tdigests()`](merge-tdigests-aggfunction.md)).', - }, - { text: 'min', hint: 'Returns the minimum value agross the group.' }, - { text: 'min_of', hint: 'Returns the minimum value of several evaluated numeric expressions.' }, - { text: 'monthofyear', hint: 'Returns the integer number represents the month number of the given year.' }, - { - text: 'next', - hint: - 'Returns the value of a column in a row that it at some offset following the\r\ncurrent row in a [serialized row set](./windowsfunctions.md#serialized-row-set).', - }, - { text: 'not', hint: 'Reverses the value of its `bool` argument.' }, - { - text: 'now', - hint: - 'Returns the current UTC clock time, optionally offset by a given timespan.\r\nThis function can be used multiple times in a statement and the clock time being referenced will be the same for all instances.', - }, - { text: 'pack', hint: 'Creates a `dynamic` object (property bag) from a list of names and values.' }, - { - text: 'pack_all', - hint: 'Creates a `dynamic` object (property bag) from all the columns of the tabular expression.', - }, - { text: 'pack_array', hint: 'Packs all input values into a dynamic array.' }, - { text: 'parse_ipv4', hint: 'Converts input to integer (signed 64-bit) number representation.' }, - { - text: 'parse_json', - hint: - 'Interprets a `string` as a [JSON value](https://json.org/)) and returns the value as [`dynamic`](./scalar-data-types/dynamic.md). \r\nIt is superior to using [extractjson() function](./extractjsonfunction.md)\r\nwhen you need to extract more than one element of a JSON compound object.', - }, - { - text: 'parse_path', - hint: - 'Parses a file path `string` and returns a [`dynamic`](./scalar-data-types/dynamic.md) object that contains the following parts of the path: \r\nScheme, RootPath, DirectoryPath, DirectoryName, FileName, Extension, AlternateDataStreamName.\r\nIn addition to the simple paths with both types of slashes, supports paths with schemas (e.g. "file://..."), shared paths (e.g. "\\\\shareddrive\\users..."), long paths (e.g "\\\\?\\C:...""), alternate data streams (e.g. "file1.exe:file2.exe")', - }, - { - text: 'parse_url', - hint: - 'Parses an absolute URL `string` and returns a [`dynamic`](./scalar-data-types/dynamic.md) object contains all parts of the URL (Scheme, Host, Port, Path, Username, Password, Query Parameters, Fragment).', - }, - { - text: 'parse_urlquery', - hint: - 'Parses a url query `string` and returns a [`dynamic`](./scalar-data-types/dynamic.md) object contains the Query parameters.', - }, - { - text: 'parse_user_agent', - hint: - "Interprets a user-agent string, which identifies the user's browser and provides certain system details to servers hosting the websites the user visits. The result is returned as [`dynamic`](./scalar-data-types/dynamic.md).", - }, - { text: 'parse_version', hint: 'Converts input string representation of version to a comparable decimal number.' }, - { - text: 'parse_xml', - hint: - 'Interprets a `string` as a XML value, converts the value to a [JSON value](https://json.org/) and returns the value as [`dynamic`](./scalar-data-types/dynamic.md).', - }, - { - text: 'percentile', - hint: - 'Returns an estimate for the specified [nearest-rank percentile](#nearest-rank-percentile) of the population defined by *Expr*. \r\nThe accuracy depends on the density of population in the region of the percentile.', - }, - { - text: 'percentile_tdigest', - hint: - 'Calculates the percentile result from tdigest results (which was generated by [tdigest](tdigest-aggfunction.md) or [merge-tdigests](merge-tdigests-aggfunction.md))', - }, - { - text: 'percentrank_tdigest', - hint: - "Calculates the approximate rank of the value in a set where rank is expressed as percentage of set's size. \r\nThis function can be viewed as the inverse of the percentile.", - }, - { text: 'pi', hint: 'Returns the constant value of Pi (π).' }, - { text: 'point', hint: 'Returns a dynamic array representation of a point.' }, - { text: 'pow', hint: 'Returns a result of raising to power' }, - { - text: 'prev', - hint: - 'Returns the value of a column in a row that it at some offset prior to the\r\ncurrent row in a [serialized row set](./windowsfunctions.md#serialized-row-set).', - }, - { - text: 'radians', - hint: - 'Converts angle value in degrees into value in radians, using formula `radians = (PI / 180 ) * angle_in_degrees`', - }, - { text: 'rand', hint: 'Returns a random number.' }, - { text: 'range', hint: 'Generates a dynamic array holding a series of equally-spaced values.' }, - { text: 'repeat', hint: 'Generates a dynamic array holding a series of equal values.' }, - { text: 'replace', hint: 'Replace all regex matches with another string.' }, - { text: 'reverse', hint: 'Function makes reverse of input string.' }, - { text: 'round', hint: 'Returns the rounded source to the specified precision.' }, - { - text: 'row_cumsum', - hint: - 'Calculates the cumulative sum of a column in a [serialized row set](./windowsfunctions.md#serialized-row-set).', - }, - { - text: 'row_number', - hint: - "Returns the current row's index in a [serialized row set](./windowsfunctions.md#serialized-row-set).\r\nThe row index starts by default at `1` for the first row, and is incremented by `1` for each additional row.\r\nOptionally, the row index can start at a different value than `1`.\r\nAdditionally, the row index may be reset according to some provided predicate.", - }, - { text: 'series_add', hint: 'Calculates the element-wise addition of two numeric series inputs.' }, - { text: 'series_decompose', hint: 'Applies a decomposition transformation on a series.' }, - { - text: 'series_decompose_anomalies', - hint: - 'Anomaly Detection based on series decomposition (refer to [series_decompose()](series-decomposefunction.md))', - }, - { text: 'series_decompose_forecast', hint: 'Forecast based on series decomposition.' }, - { text: 'series_divide', hint: 'Calculates the element-wise division of two numeric series inputs.' }, - { - text: 'series_equals', - hint: 'Calculates the element-wise equals (`==`) logic operation of two numeric series inputs.', - }, - { text: 'series_fill_backward', hint: 'Performs backward fill interpolation of missing values in a series.' }, - { text: 'series_fill_const', hint: 'Replaces missing values in a series with a specified constant value.' }, - { text: 'series_fill_forward', hint: 'Performs forward fill interpolation of missing values in a series.' }, - { text: 'series_fill_linear', hint: 'Performs linear interpolation of missing values in a series.' }, - { text: 'series_fir', hint: 'Applies a Finite Impulse Response filter on a series.' }, - { - text: 'series_fit_2lines', - hint: 'Applies two segments linear regression on a series, returning multiple columns.', - }, - { - text: 'series_fit_2lines_dynamic', - hint: 'Applies two segments linear regression on a series, returning dynamic object.', - }, - { text: 'series_fit_line', hint: 'Applies linear regression on a series, returning multiple columns.' }, - { text: 'series_fit_line_dynamic', hint: 'Applies linear regression on a series, returning dynamic object.' }, - { - text: 'series_greater', - hint: 'Calculates the element-wise greater (`>`) logic operation of two numeric series inputs.', - }, - { - text: 'series_greater_equals', - hint: 'Calculates the element-wise greater or equals (`>=`) logic operation of two numeric series inputs.', - }, - { text: 'series_iir', hint: 'Applies a Infinite Impulse Response filter on a series.' }, - { text: 'series_less', hint: 'Calculates the element-wise less (`<`) logic operation of two numeric series inputs.' }, - { - text: 'series_less_equals', - hint: 'Calculates the element-wise less or equal (`<=`) logic operation of two numeric series inputs.', - }, - { text: 'series_multiply', hint: 'Calculates the element-wise multiplication of two numeric series inputs.' }, - { - text: 'series_not_equals', - hint: 'Calculates the element-wise not equals (`!=`) logic operation of two numeric series inputs.', - }, - { text: 'series_outliers', hint: 'Scores anomaly points in a series.' }, - { text: 'series_periods_detect', hint: 'Finds the most significant periods that exist in a time series.' }, - { - text: 'series_periods_validate', - hint: 'Checks whether a time series contains periodic patterns of given lengths.', - }, - { - text: 'series_seasonal', - hint: 'Calculates the seasonal component of a series according to the detected or given seasonal period.', - }, - { text: 'series_stats', hint: 'Returns statistics for a series in multiple columns.' }, - { text: 'series_stats_dynamic', hint: 'Returns statistics for a series in dynamic object.' }, - { text: 'series_subtract', hint: 'Calculates the element-wise subtraction of two numeric series inputs.' }, - { text: 'sign', hint: 'Sign of a numeric expression' }, - { text: 'sin', hint: 'Returns the sine function.' }, - { - text: 'split', - hint: - 'Splits a given string according to a given delimiter and returns a string array with the contained substrings.', - }, - { text: 'sqrt', hint: 'Returns the square root function.' }, - { text: 'startofday', hint: 'Returns the start of the day containing the date, shifted by an offset, if provided.' }, - { - text: 'startofmonth', - hint: 'Returns the start of the month containing the date, shifted by an offset, if provided.', - }, - { - text: 'startofweek', - hint: 'Returns the start of the week containing the date, shifted by an offset, if provided.', - }, - { - text: 'startofyear', - hint: 'Returns the start of the year containing the date, shifted by an offset, if provided.', - }, - { - text: 'stdev', - hint: - 'Calculates the standard deviation of *Expr* across the group, considering the group as a [sample](https://en.wikipedia.org/wiki/Sample_%28statistics%29).', - }, - { - text: 'stdevif', - hint: - 'Calculates the [stdev](stdev-aggfunction.md) of *Expr* across the group for which *Predicate* evaluates to `true`.', - }, - { - text: 'stdevp', - hint: - 'Calculates the standard deviation of *Expr* across the group, considering the group as a [population](https://en.wikipedia.org/wiki/Statistical_population).', - }, - { text: 'strcat', hint: 'Concatenates between 1 and 64 arguments.' }, - { text: 'strcat_array', hint: 'Creates a concatenated string of array values using specified delimiter.' }, - { - text: 'strcat_delim', - hint: 'Concatenates between 2 and 64 arguments, with delimiter, provided as first argument.', - }, - { text: 'strcmp', hint: 'Compares two strings.' }, - { text: 'string_size', hint: 'Returns the size, in bytes, of the input string.' }, - { text: 'strlen', hint: 'Returns the length, in characters, of the input string.' }, - { text: 'strrep', hint: 'Repeats given [string](./scalar-data-types/string.md) provided amount of times.' }, - { - text: 'substring', - hint: 'Extracts a substring from a source string starting from some index to the end of the string.', - }, - { text: 'sum', hint: 'Calculates the sum of *Expr* across the group.' }, - { text: 'sumif', hint: 'Returns a sum of *Expr* for which *Predicate* evaluates to `true`.' }, - { text: 'table', hint: 'References specific table using an query-time evaluated string-expression.' }, - { text: 'tan', hint: 'Returns the tangent function.' }, - { - text: 'tdigest', - hint: 'Calculates the Intermediate results of [`percentiles()`](percentiles-aggfunction.md) across the group.', - }, - { - text: 'tdigest_merge', - hint: - 'Merges tdigest results (scalar version of the aggregate version [`tdigest_merge()`](tdigest-merge-aggfunction.md)).', - }, - { text: 'tobool', hint: 'Converts input to boolean (signed 8-bit) representation.' }, - { text: 'todatetime', hint: 'Converts input to [datetime](./scalar-data-types/datetime.md) scalar.' }, - { text: 'todecimal', hint: 'Converts input to decimal number representation.' }, - { - text: 'todouble', - hint: 'Converts the input to a value of type `real`. (`todouble()` and `toreal()` are synonyms.)', - }, - { - text: 'todynamic', - hint: - 'Interprets a `string` as a [JSON value](https://json.org/) and returns the value as [`dynamic`](./scalar-data-types/dynamic.md).', - }, - { text: 'toguid', hint: 'Converts input to [`guid`](./scalar-data-types/guid.md) representation.' }, - { text: 'tohex', hint: 'Converts input to a hexadecimal string.' }, - { text: 'toint', hint: 'Converts input to integer (signed 32-bit) number representation.' }, - { text: 'tolong', hint: 'Converts input to long (signed 64-bit) number representation.' }, - { text: 'tolower', hint: 'Converts input string to lower case.' }, - { text: 'toscalar', hint: 'Returns a scalar constant value of the evaluated expression.' }, - { text: 'tostring', hint: 'Converts input to a string representation.' }, - { text: 'totimespan', hint: 'Converts input to [timespan](./scalar-data-types/timespan.md) scalar.' }, - { text: 'toupper', hint: 'Converts a string to upper case.' }, - { - text: 'translate', - hint: - "Replaces a set of characters ('searchList') with another set of characters ('replacementList') in a given a string.\r\nThe function searches for characters in the 'searchList' and replaces them with the corresponding characters in 'replacementList'", - }, - { text: 'treepath', hint: 'Enumerates all the path expressions that identify leaves in a dynamic object.' }, - { text: 'trim', hint: 'Removes all leading and trailing matches of the specified regular expression.' }, - { text: 'trim_end', hint: 'Removes trailing match of the specified regular expression.' }, - { text: 'trim_start', hint: 'Removes leading match of the specified regular expression.' }, - { text: 'url_decode', hint: 'The function converts encoded URL into a to regular URL representation.' }, - { - text: 'url_encode', - hint: 'The function converts characters of the input URL into a format that can be transmitted over the Internet.', - }, - { - text: 'variance', - hint: - 'Calculates the variance of *Expr* across the group, considering the group as a [sample](https://en.wikipedia.org/wiki/Sample_%28statistics%29).', - }, - { - text: 'varianceif', - hint: - 'Calculates the [variance](variance-aggfunction.md) of *Expr* across the group for which *Predicate* evaluates to `true`.', - }, - { - text: 'variancep', - hint: - 'Calculates the variance of *Expr* across the group, considering the group as a [population](https://en.wikipedia.org/wiki/Statistical_population).', - }, - { text: 'weekofyear', hint: 'Returns the integer number represents the week number.' }, - { - text: 'welch_test', - hint: 'Computes the p_value of the [Welch-test function](https://en.wikipedia.org/wiki/Welch%27s_t-test)', - }, - { - text: 'zip', - hint: - 'The `zip` function accepts any number of `dynamic` arrays, and returns an\r\narray whose elements are each an array holding the elements of the input\r\narrays of the same index.', - }, -]; - -export const KEYWORDS = [ - 'by', - 'on', - 'contains', - 'notcontains', - 'containscs', - 'notcontainscs', - 'startswith', - 'has', - 'matches', - 'regex', - 'true', - 'false', - 'and', - 'or', - 'typeof', - 'int', - 'string', - 'date', - 'datetime', - 'time', - 'long', - 'real', - '​boolean', - 'bool', -]; - -export const grafanaMacros = [ - { - text: '$__timeFilter', - display: '$__timeFilter()', - hint: 'Macro that uses the selected timerange in Grafana to filter the query.', - }, - { - text: '$__timeTo', - display: '$__timeTo()', - hint: 'Returns the From datetime from the Grafana picker. Example: datetime(2018-06-05T20:09:58.907Z).', - }, - { - text: '$__timeFrom', - display: '$__timeFrom()', - hint: 'Returns the From datetime from the Grafana picker. Example: datetime(2018-06-05T18:09:58.907Z).', - }, - { - text: '$__escapeMulti', - display: '$__escapeMulti()', - hint: 'Macro to escape multi-value template variables that contain illegal characters.', - }, - { text: '$__contains', display: '$__contains()', hint: 'Macro for multi-value template variables.' }, -]; - -// Kusto operators -// export const OPERATORS = ['+', '-', '*', '/', '>', '<', '==', '<>', '<=', '>=', '~', '!~']; - -export const DURATION = ['SECONDS', 'MINUTES', 'HOURS', 'DAYS', 'WEEKS', 'MONTHS', 'YEARS']; - -const tokenizer = { - comment: { - pattern: /(^|[^\\:])\/\/.*/, - lookbehind: true, - greedy: true, - }, - 'function-context': { - pattern: /[a-z0-9_]+\([^)]*\)?/i, - inside: {}, - }, - duration: { - pattern: new RegExp(`${DURATION.join('?|')}?`, 'i'), - alias: 'number', - }, - builtin: new RegExp(`\\b(?:${functionTokens.map((f) => f.text).join('|')})(?=\\s*\\()`, 'i'), - string: { - pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/, - greedy: true, - }, - keyword: new RegExp(`\\b(?:${KEYWORDS.join('|')}|${operatorTokens.map((f) => f.text).join('|')}|\\*)\\b`, 'i'), - boolean: /\b(?:true|false)\b/, - number: /\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i, - operator: /-|\+|\*|\/|>|<|==|<=?|>=?|<>|!~|~|=|\|/, - punctuation: /[{};(),.:]/, - variable: /(\[\[(.+?)\]\])|(\$(.+?))\b/, -}; - -tokenizer['function-context'].inside = { - argument: { - pattern: /[a-z0-9_]+(?=:)/i, - alias: 'symbol', - }, - duration: tokenizer.duration, - number: tokenizer.number, - builtin: tokenizer.builtin, - string: tokenizer.string, - variable: tokenizer.variable, -}; - -// console.log(tokenizer.builtin); - -export default tokenizer; - -// function escapeRegExp(str: string): string { -// return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string -// } diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx deleted file mode 100644 index 99685f42961..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx +++ /dev/null @@ -1,341 +0,0 @@ -import PluginPrism from 'app/features/explore/slate-plugins/prism'; -import { BracesPlugin, ClearPlugin, RunnerPlugin, NewlinePlugin } from '@grafana/ui'; -import Typeahead from './typeahead'; -import { keybindingSrv } from 'app/core/services/keybindingSrv'; - -import { Block, Document, Text, Value, Editor as CoreEditor } from 'slate'; -import { Editor } from '@grafana/slate-react'; -import Plain from 'slate-plain-serializer'; -import ReactDOM from 'react-dom'; -import React from 'react'; - -function flattenSuggestions(s: any) { - return s ? s.reduce((acc: any, g: any) => acc.concat(g.items), []) : []; -} - -export const makeFragment = (text: string) => { - const lines = text.split('\n').map((line: any) => - Block.create({ - type: 'paragraph', - nodes: [Text.create(line)], - } as any) - ); - - const fragment = Document.create({ - nodes: lines, - }); - return fragment; -}; - -export const getInitialValue = (query: string) => Value.create({ document: makeFragment(query) }); - -class Portal extends React.Component { - node: any; - - constructor(props: any) { - super(props); - const { index = 0, prefix = 'query' } = props; - this.node = document.createElement('div'); - this.node.classList.add(`slate-typeahead`, `slate-typeahead-${prefix}-${index}`); - document.body.appendChild(this.node); - } - - componentWillUnmount() { - document.body.removeChild(this.node); - } - - render() { - return ReactDOM.createPortal(this.props.children, this.node); - } -} - -class QueryField extends React.Component { - menuEl: any; - plugins: any; - resetTimer: any; - - constructor(props: any, context: any) { - super(props, context); - - const { prismDefinition = {}, prismLanguage = 'kusto' } = props; - - this.plugins = [ - BracesPlugin(), - ClearPlugin(), - RunnerPlugin({ handler: props.onPressEnter }), - NewlinePlugin(), - PluginPrism({ definition: prismDefinition, language: prismLanguage }), - ]; - - this.state = { - labelKeys: {}, - labelValues: {}, - suggestions: [], - typeaheadIndex: null, - typeaheadPrefix: '', - value: getInitialValue(props.initialQuery || ''), - }; - } - - componentDidMount() { - this.updateMenu(); - } - - componentWillUnmount() { - this.restoreEscapeKeyBinding(); - clearTimeout(this.resetTimer); - } - - componentDidUpdate() { - this.updateMenu(); - } - - onChange = ({ value }: { value: Value }) => { - const changed = value.document !== this.state.value.document; - this.setState({ value }, () => { - if (changed) { - // call typeahead only if query changed - requestAnimationFrame(() => this.onTypeahead()); - this.onChangeQuery(); - } - }); - }; - - onChangeQuery = () => { - // Send text change to parent - const { onQueryChange } = this.props; - if (onQueryChange) { - onQueryChange(Plain.serialize(this.state.value)); - } - }; - - onKeyDown = (event: Event, editor: CoreEditor, next: Function) => { - const { typeaheadIndex, suggestions } = this.state; - const keyboardEvent = event as KeyboardEvent; - - switch (keyboardEvent.key) { - case 'Escape': { - if (this.menuEl) { - keyboardEvent.preventDefault(); - keyboardEvent.stopPropagation(); - this.resetTypeahead(); - return true; - } - break; - } - - case ' ': { - if (keyboardEvent.ctrlKey) { - keyboardEvent.preventDefault(); - this.onTypeahead(true); - return true; - } - break; - } - - case 'Tab': - case 'Enter': { - if (this.menuEl && typeaheadIndex !== null) { - // Dont blur input - keyboardEvent.preventDefault(); - if (!suggestions || !suggestions.length || keyboardEvent.shiftKey || keyboardEvent.ctrlKey) { - return next(); - } - - // Get the currently selected suggestion - const flattenedSuggestions = flattenSuggestions(suggestions); - const selected = Math.abs(typeaheadIndex); - const selectedIndex = selected % flattenedSuggestions.length || 0; - const suggestion = flattenedSuggestions[selectedIndex]; - - return this.applyTypeahead(editor, suggestion); - } - break; - } - - case 'ArrowDown': { - if (this.menuEl) { - // Select next suggestion - keyboardEvent.preventDefault(); - this.setState({ typeaheadIndex: (typeaheadIndex || 0) + 1 }); - } - break; - } - - case 'ArrowUp': { - if (this.menuEl) { - // Select previous suggestion - keyboardEvent.preventDefault(); - this.setState({ typeaheadIndex: Math.max(0, (typeaheadIndex || 0) - 1) }); - } - break; - } - - default: { - // console.log('default key', event.key, event.which, event.charCode, event.locale, data.key); - break; - } - } - return next(); - }; - - onTypeahead = (change = false, item?: any): boolean | void => { - return change; - }; - - applyTypeahead = ( - editor?: CoreEditor, - suggestion?: { text: any; type: string; deleteBackwards: any } - ): { value: Value } => { - return { value: new Value() }; - }; - - resetTypeahead = (callback?: () => void) => { - this.setState( - { - suggestions: [], - typeaheadIndex: null, - typeaheadPrefix: '', - typeaheadContext: null, - }, - callback - ); - }; - - handleBlur = (event: Event, editor: CoreEditor, next: Function) => { - const { onBlur } = this.props; - // If we dont wait here, menu clicks wont work because the menu - // will be gone. - this.resetTimer = setTimeout(this.resetTypeahead, 100); - if (onBlur) { - onBlur(); - } - this.restoreEscapeKeyBinding(); - return next(); - }; - - handleFocus = (event: Event, editor: CoreEditor, next: Function) => { - const { onFocus } = this.props; - if (onFocus) { - onFocus(); - } - // Don't go back to dashboard if Escape pressed inside the editor. - this.removeEscapeKeyBinding(); - return next(); - }; - - removeEscapeKeyBinding() { - keybindingSrv.unbind('esc', 'keydown'); - } - - restoreEscapeKeyBinding() { - keybindingSrv.initGlobals(); - } - - onClickItem = (item: any) => { - const { suggestions } = this.state; - if (!suggestions || suggestions.length === 0) { - return; - } - - // Manually triggering change - const change = this.applyTypeahead(); - this.onChange(change); - }; - - updateMenu = () => { - const { suggestions } = this.state; - const menu = this.menuEl; - const selection = window.getSelection(); - - // No menu, nothing to do - if (!menu || !selection) { - return; - } - - const node = selection.anchorNode; - - // No suggestions or blur, remove menu - const hasSuggesstions = suggestions && suggestions.length > 0; - if (!hasSuggesstions) { - menu.removeAttribute('style'); - return; - } - - // Align menu overlay to editor node - if (node && node.parentElement) { - // Read from DOM - const rect = node.parentElement.getBoundingClientRect(); - const scrollX = window.scrollX; - const scrollY = window.scrollY; - const screenHeight = window.innerHeight; - - const menuLeft = rect.left + scrollX - 2; - const menuTop = rect.top + scrollY + rect.height + 4; - const menuHeight = screenHeight - menuTop - 10; - - // Write DOM - requestAnimationFrame(() => { - menu.style.opacity = 1; - menu.style.top = `${menuTop}px`; - menu.style.left = `${menuLeft}px`; - menu.style.maxHeight = `${menuHeight}px`; - }); - } - }; - - menuRef = (el: any) => { - this.menuEl = el; - }; - - renderMenu = () => { - const { portalPrefix } = this.props; - const { suggestions, typeaheadIndex } = this.state; - const hasSuggesstions = suggestions && suggestions.length > 0; - if (!hasSuggesstions) { - return null; - } - - // Guard selectedIndex to be within the length of the suggestions - let selectedIndex = Math.max(typeaheadIndex, 0); - const flattenedSuggestions = flattenSuggestions(suggestions); - selectedIndex = selectedIndex % flattenedSuggestions.length || 0; - const selectedKeys = (typeaheadIndex !== null && flattenedSuggestions.length > 0 - ? [flattenedSuggestions[selectedIndex]] - : [] - ).map((i) => (typeof i === 'object' ? i.text : i)); - - // Create typeahead in DOM root so we can later position it absolutely - return ( - - - - ); - }; - - render() { - return ( -
- {this.renderMenu()} - -
- ); - } -} - -export default QueryField; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/typeahead.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/typeahead.tsx deleted file mode 100644 index 83428d3944b..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/typeahead.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React from 'react'; - -function scrollIntoView(el: any) { - if (!el || !el.offsetParent) { - return; - } - const container = el.offsetParent; - if (el.offsetTop > container.scrollTop + container.offsetHeight || el.offsetTop < container.scrollTop) { - container.scrollTop = el.offsetTop - container.offsetTop; - } -} - -class TypeaheadItem extends React.PureComponent { - el: any; - componentDidUpdate(prevProps: any) { - if (this.props.isSelected && !prevProps.isSelected) { - scrollIntoView(this.el); - } - } - - getRef = (el: any) => { - this.el = el; - }; - - render() { - const { hint, isSelected, label, onClickItem } = this.props; - const className = isSelected ? 'typeahead-item typeahead-item__selected' : 'typeahead-item'; - const onClick = () => onClickItem(label); - return ( -
  • - {label} - {hint && isSelected ?
    {hint}
    : null} -
  • - ); - } -} - -class TypeaheadGroup extends React.PureComponent { - render() { - const { items, label, selected, onClickItem } = this.props; - return ( -
  • -
    {label}
    -
      - {items.map((item: any) => { - const text = typeof item === 'object' ? item.text : item; - const label = typeof item === 'object' ? item.display || item.text : item; - return ( - -1} - hint={item.hint} - label={label} - /> - ); - })} -
    -
  • - ); - } -} - -class Typeahead extends React.PureComponent { - render() { - const { groupedItems, menuRef, selectedItems, onClickItem } = this.props; - return ( -
      - {groupedItems.map((g: any) => ( - - ))} -
    - ); - } -} - -export default Typeahead; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html deleted file mode 100644 index ee0a13e6888..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html +++ /dev/null @@ -1,531 +0,0 @@ - -
    -
    - -
    - -
    -
    -
    - - - -
    -
    -
    -
    -
    -
    -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    -
    -
    -
    -
    -
    - - - -
    -
    - - - -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    - - -
    -
    -
    -
    -
    - - - -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -
    - - - -
    -
    - -
    - -
    - - -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    -
    - -
    -
    - - -
    - -
    -
    -
    -
    -
    - -
    -
    -
    - - - -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    -
    - -
    - -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -
    - -
    -
    {{ctrl.lastQuery}}
    -
    -
    -
    -    Format as Table:
    -    - return any set of columns
    -
    -    Format as Time series:
    -    - Requires a column of type datetime
    -    - returns the first column with a numeric datatype as the value
    -    - (Optional: returns the first column with type string to represent the series name. If no column is found the column name of the value column is used as series name)
    -
    -    Example Time Series Query:
    -
    -    AzureActivity
    -    | where $__timeFilter()
    -    | summarize count() by Category, bin(TimeGenerated, 60min)
    -    | order by TimeGenerated asc
    -
    -    Macros:
    -    - $__timeFilter() -> TimeGenerated ≥ datetime(2018-06-05T18:09:58.907Z) and TimeGenerated ≤ datetime(2018-06-05T20:09:58.907Z)
    -    - $__timeFilter(datetimeColumn) ->  datetimeColumn  ≥ datetime(2018-06-05T18:09:58.907Z) and datetimeColumn ≤ datetime(2018-06-05T20:09:58.907Z)
    -    - $__escapeMulti($myTemplateVar) -> $myTemplateVar should be a multi-value template variables that contains illegal characters
    -    - $__contains(aColumn, $myTemplateVar) -> aColumn in ($myTemplateVar)
    -      If using the All option, then check the Include All Option checkbox and in the Custom all value field type in: all. If All is chosen -> 1 == 1
    -
    -    Or build your own conditionals using these built-in variables which just return the values:
    -    - $__timeFrom ->  datetime(2018-06-05T18:09:58.907Z)
    -    - $__timeTo -> datetime(2018-06-05T20:09:58.907Z)
    -    - $__interval -> 5m
    -
    -    Examples:
    -    - ¡ where $__timeFilter
    -    - | where TimeGenerated ≥ $__timeFrom and TimeGenerated ≤ $__timeTo
    -    - | summarize count() by Category, bin(TimeGenerated, $__interval)
    -      
    -
    -
    - -
    -
    - -
    - -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    - - - -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    - - -
    -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    - -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    -
    - -
    -
    {{ctrl.lastQueryError}}
    -
    - -
    -

    - Application Insights and Insights Analytics will be deprecated and merged with Metrics and Logs in an upcoming - release. See - the documentation - for more details. -

    -
    -
    - - -
    - - -
    diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.test.ts deleted file mode 100644 index 1cd32219ac3..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.test.ts +++ /dev/null @@ -1,138 +0,0 @@ -jest.mock('./css/query_editor.css', () => { - return {}; -}); - -import { AzureMonitorQueryCtrl } from './query_ctrl'; -// @ts-ignore -import Q from 'q'; -import { TemplateSrv } from 'app/features/templating/template_srv'; -import { auto } from 'angular'; - -describe('AzureMonitorQueryCtrl', () => { - let queryCtrl: any; - - beforeEach(() => { - AzureMonitorQueryCtrl.prototype.panelCtrl = { - events: { on: () => {} }, - panel: { scopedVars: [], targets: [] }, - }; - AzureMonitorQueryCtrl.prototype.target = {} as any; - AzureMonitorQueryCtrl.prototype.datasource = { - appInsightsDatasource: { isConfigured: () => false }, - azureMonitorDatasource: { isConfigured: () => false }, - }; - - queryCtrl = new AzureMonitorQueryCtrl({}, {} as auto.IInjectorService, new TemplateSrv()); - }); - - describe('init query_ctrl variables', () => { - it('should set default query type to Azure Monitor', () => { - expect(queryCtrl.target.queryType).toBe('Azure Monitor'); - }); - - it('should set default App Insights editor to be builder', () => { - expect(!!(queryCtrl.target.appInsights as any).rawQuery).toBe(false); - }); - - it('should set query parts to select', () => { - // expect(queryCtrl.target.azureMonitor.resourceGroup).toBe('select'); - // expect(queryCtrl.target.azureMonitor.metricDefinition).toBe('select'); - // expect(queryCtrl.target.azureMonitor.resourceName).toBe('select'); - // expect(queryCtrl.target.azureMonitor.metricNamespace).toBe('select'); - // expect(queryCtrl.target.azureMonitor.metricName).toBe('select'); - expect(queryCtrl.target.appInsights.dimension).toMatchObject([]); - }); - }); - - describe('and query type is Application Insights', () => { - describe('and target is in old format', () => { - it('data is migrated', () => { - queryCtrl.target.appInsights.xaxis = 'sample-x'; - queryCtrl.target.appInsights.yaxis = 'sample-y'; - queryCtrl.target.appInsights.spliton = 'sample-split'; - queryCtrl.target.appInsights.groupBy = 'sample-group'; - queryCtrl.target.appInsights.groupByOptions = ['sample-group-1', 'sample-group-2']; - queryCtrl.target.appInsights.filter = 'sample-filter'; - queryCtrl.target.appInsights.metricName = 'sample-metric'; - - queryCtrl.migrateApplicationInsightsKeys(); - - expect(queryCtrl.target.appInsights.xaxis).toBeUndefined(); - expect(queryCtrl.target.appInsights.yaxis).toBeUndefined(); - expect(queryCtrl.target.appInsights.spliton).toBeUndefined(); - expect(queryCtrl.target.appInsights.groupBy).toBeUndefined(); - expect(queryCtrl.target.appInsights.groupByOptions).toBeUndefined(); - expect(queryCtrl.target.appInsights.filter).toBeUndefined(); - - expect(queryCtrl.target.appInsights.timeColumn).toBe('sample-x'); - expect(queryCtrl.target.appInsights.valueColumn).toBe('sample-y'); - expect(queryCtrl.target.appInsights.segmentColumn).toBe('sample-split'); - expect(queryCtrl.target.appInsights.dimension).toBe('sample-group'); - expect(queryCtrl.target.appInsights.dimensions).toEqual(['sample-group-1', 'sample-group-2']); - expect(queryCtrl.target.appInsights.dimensionFilter).toBe('sample-filter'); - expect(queryCtrl.target.appInsights.metricName).toBe('sample-metric'); - }); - }); - - describe('when getOptions for the Metric Names dropdown is called', () => { - const response = [ - { text: 'metric1', value: 'metric1' }, - { text: 'metric2', value: 'metric2' }, - ]; - - beforeEach(() => { - queryCtrl.datasource.appInsightsDatasource.isConfigured = () => true; - queryCtrl.datasource.getAppInsightsMetricNames = () => { - return Promise.resolve(response); - }; - }); - - it('should return a list of Metric Names', () => { - return queryCtrl.getAppInsightsMetricNames().then((result: any) => { - expect(result[0].text).toBe('metric1'); - expect(result[1].text).toBe('metric2'); - }); - }); - }); - - describe('when getOptions for the GroupBy segments dropdown is called', () => { - beforeEach(() => { - queryCtrl.target.appInsights.dimensions = ['opt1', 'opt2']; - }); - - it('should return a list of GroupBy segments', () => { - const result = queryCtrl.getAppInsightsGroupBySegments(''); - expect(result[0].text).toBe('opt1'); - expect(result[0].value).toBe('opt1'); - expect(result[1].text).toBe('opt2'); - expect(result[1].value).toBe('opt2'); - }); - }); - - describe('when onAppInsightsMetricNameChange is triggered for the Metric Names dropdown', () => { - const response = { - primaryAggType: 'avg', - supportedAggTypes: ['avg', 'sum'], - supportedGroupBy: ['client/os', 'client/city'], - }; - - beforeEach(() => { - queryCtrl.target.appInsights.metricName = 'requests/failed'; - queryCtrl.datasource.getAppInsightsMetricMetadata = (metricName: string) => { - expect(metricName).toBe('requests/failed'); - return Promise.resolve(response); - }; - }); - - it('should set the options and default selected value for the Aggregations dropdown', () => { - return queryCtrl.onAppInsightsMetricNameChange().then(() => { - expect(queryCtrl.target.appInsights.aggregation).toBe('avg'); - expect(queryCtrl.target.appInsights.aggOptions).toContain('avg'); - expect(queryCtrl.target.appInsights.aggOptions).toContain('sum'); - expect(queryCtrl.target.appInsights.dimensions).toContain('client/os'); - expect(queryCtrl.target.appInsights.dimensions).toContain('client/city'); - }); - }); - }); - }); -}); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts deleted file mode 100644 index 3c98c8b338e..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts +++ /dev/null @@ -1,569 +0,0 @@ -import { defaultsDeep, find, map, isString } from 'lodash'; -import { QueryCtrl } from 'app/plugins/sdk'; -import TimegrainConverter from './time_grain_converter'; -import './editor/editor_component'; - -import { TemplateSrv } from '@grafana/runtime'; -import { auto } from 'angular'; -import { DataFrame, PanelEvents } from '@grafana/data'; -import { AzureQueryType, AzureMetricQuery, AzureMonitorQuery } from './types'; -import { convertTimeGrainsToMs } from './utils/common'; -import Datasource from './datasource'; - -export interface ResultFormat { - text: string; - value: string; -} - -export class AzureMonitorQueryCtrl extends QueryCtrl { - static templateUrl = 'partials/query.editor.html'; - - defaultDropdownValue = 'select'; - - dummyDiminsionString = '+'; - - queryQueryTypeOptions = [ - { id: AzureQueryType.AzureMonitor, label: 'Metrics' }, - { id: AzureQueryType.LogAnalytics, label: 'Logs' }, - { id: AzureQueryType.ApplicationInsights, label: 'Application Insights' }, - { id: AzureQueryType.InsightsAnalytics, label: 'Insights Analytics' }, - { id: AzureQueryType.AzureResourceGraph, label: 'Azure Resource Graph' }, - ]; - - // Query types that have been migrated to React - reactQueryEditors = [ - AzureQueryType.AzureMonitor, - AzureQueryType.LogAnalytics, - AzureQueryType.ApplicationInsights, - AzureQueryType.InsightsAnalytics, - AzureQueryType.AzureResourceGraph, - ]; - - // target: AzureMonitorQuery; - - declare target: { - // should be: AzureMonitorQuery - refId: string; - queryType: AzureQueryType; - subscription: string; - subscriptions: string[]; - azureMonitor: AzureMetricQuery; - azureLogAnalytics: { - query: string; - resultFormat: string; - workspace: string; - }; - azureResourceGraph: { - query: string; - resultFormat: string; - }; - appInsights: { - // metric style query when rawQuery == false - metricName: string; - dimension: any; - dimensionFilter: string; - dimensions: string[]; - - aggOptions: string[]; - aggregation: string; - - timeGrainType: string; - timeGrainCount: string; - timeGrainUnit: string; - timeGrain: string; - timeGrains: Array<{ text: string; value: string }>; - allowedTimeGrainsMs: number[]; - }; - insightsAnalytics: { - query: any; - resultFormat: string; - }; - }; - - defaults = { - queryType: 'Azure Monitor', - azureMonitor: { - resourceGroup: undefined, - metricDefinition: undefined, - resourceName: undefined, - metricNamespace: undefined, - metricName: undefined, - dimensionFilter: '*', - timeGrain: 'auto', - top: '10', - aggOptions: [] as string[], - timeGrains: [] as string[], - }, - azureLogAnalytics: { - query: [ - '//change this example to create your own time series query', - ' ' + - '//the table to query (e.g. Usage, Heartbeat, Perf)', - '| where $__timeFilter(TimeGenerated) ' + - '//this is a macro used to show the full chart’s time range, choose the datetime column here', - '| summarize count() by , bin(TimeGenerated, $__interval) ' + - '//change “group by column” to a column in your table, such as “Computer”. ' + - 'The $__interval macro is used to auto-select the time grain. Can also use 1h, 5m etc.', - '| order by TimeGenerated asc', - ].join('\n'), - resultFormat: 'time_series', - workspace: - this.datasource && this.datasource.azureLogAnalyticsDatasource - ? this.datasource.azureLogAnalyticsDatasource.defaultOrFirstWorkspace - : '', - }, - azureResourceGraph: { - resultFormat: 'table', - }, - appInsights: { - metricName: this.defaultDropdownValue, - // dimension: [], - timeGrain: 'auto', - }, - insightsAnalytics: { - query: '', - resultFormat: 'time_series', - }, - }; - - resultFormats: ResultFormat[]; - workspaces: any[] = []; - showHelp = false; - showLastQuery = false; - lastQuery = ''; - lastQueryError?: string; - subscriptions: Array<{ text: string; value: string }> = []; - - /** @ngInject */ - constructor($scope: any, $injector: auto.IInjectorService, private templateSrv: TemplateSrv) { - super($scope, $injector); - - defaultsDeep(this.target, this.defaults); - - this.migrateTimeGrains(); - - this.migrateToFromTimes(); - - this.migrateToDefaultNamespace(); - - this.migrateApplicationInsightsKeys(); - - this.migrateApplicationInsightsDimensions(); - - migrateMetricsDimensionFilters(this.target.azureMonitor); - - this.panelCtrl.events.on(PanelEvents.dataReceived, this.onDataReceived.bind(this), $scope); - this.panelCtrl.events.on(PanelEvents.dataError, this.onDataError.bind(this), $scope); - this.resultFormats = [ - { text: 'Time series', value: 'time_series' }, - { text: 'Table', value: 'table' }, - ]; - this.getSubscriptions(); - if (this.target.queryType === 'Azure Log Analytics') { - this.getWorkspaces(); - } - } - - onDataReceived(dataList: DataFrame[]) { - this.lastQueryError = undefined; - this.lastQuery = ''; - - const anySeriesFromQuery: any = find(dataList, { refId: this.target.refId }); - if (anySeriesFromQuery && anySeriesFromQuery.meta) { - this.lastQuery = anySeriesFromQuery.meta.query; - } - } - - onDataError(err: any) { - this.handleQueryCtrlError(err); - } - - handleQueryCtrlError(err: any) { - if (err.query && err.query.refId && err.query.refId !== this.target.refId) { - return; - } - - if (err.error && err.error.data && err.error.data.error && err.error.data.error.innererror) { - if (err.error.data.error.innererror.innererror) { - this.lastQueryError = err.error.data.error.innererror.innererror.message; - } else { - this.lastQueryError = err.error.data.error.innererror.message; - } - } else if (err.error && err.error.data && err.error.data.error) { - this.lastQueryError = err.error.data.error.message; - } else if (err.error && err.error.data) { - this.lastQueryError = err.error.data.message; - } else if (err.data && err.data.error) { - this.lastQueryError = err.data.error.message; - } else if (err.data && err.data.message) { - this.lastQueryError = err.data.message; - } else { - this.lastQueryError = err; - } - } - - migrateTimeGrains() { - if (this.target.azureMonitor.timeGrainUnit) { - if (this.target.azureMonitor.timeGrain !== 'auto') { - this.target.azureMonitor.timeGrain = TimegrainConverter.createISO8601Duration( - this.target.azureMonitor.timeGrain ?? 'auto', - this.target.azureMonitor.timeGrainUnit - ); - } - - delete this.target.azureMonitor.timeGrainUnit; - } - - if (this.target.appInsights.timeGrainUnit) { - if (this.target.appInsights.timeGrain !== 'auto') { - if (this.target.appInsights.timeGrainCount) { - this.target.appInsights.timeGrain = TimegrainConverter.createISO8601Duration( - this.target.appInsights.timeGrainCount, - this.target.appInsights.timeGrainUnit - ); - } else { - this.target.appInsights.timeGrainCount = this.target.appInsights.timeGrain; - this.target.appInsights.timeGrain = TimegrainConverter.createISO8601Duration( - this.target.appInsights.timeGrain, - this.target.appInsights.timeGrainUnit - ); - } - } - } - - const oldAzureTimeGrains = (this.target.azureMonitor as any).timeGrains; - if ( - oldAzureTimeGrains && - oldAzureTimeGrains.length > 0 && - (!this.target.azureMonitor.allowedTimeGrainsMs || this.target.azureMonitor.allowedTimeGrainsMs.length === 0) - ) { - this.target.azureMonitor.allowedTimeGrainsMs = convertTimeGrainsToMs(oldAzureTimeGrains); - } - - if ( - this.target.appInsights.timeGrains && - this.target.appInsights.timeGrains.length > 0 && - (!this.target.appInsights.allowedTimeGrainsMs || this.target.appInsights.allowedTimeGrainsMs.length === 0) - ) { - this.target.appInsights.allowedTimeGrainsMs = convertTimeGrainsToMs(this.target.appInsights.timeGrains); - } - } - - migrateToFromTimes() { - this.target.azureLogAnalytics.query = this.target.azureLogAnalytics.query.replace(/\$__from\s/gi, '$__timeFrom() '); - this.target.azureLogAnalytics.query = this.target.azureLogAnalytics.query.replace(/\$__to\s/gi, '$__timeTo() '); - } - - async migrateToDefaultNamespace() { - if ( - this.target.azureMonitor.metricNamespace && - this.target.azureMonitor.metricNamespace !== this.defaultDropdownValue && - this.target.azureMonitor.metricDefinition - ) { - return; - } - - this.target.azureMonitor.metricNamespace = this.target.azureMonitor.metricDefinition; - } - - migrateApplicationInsightsKeys(): void { - const appInsights = this.target.appInsights as any; - - // Migrate old app insights data keys to match other datasources - const mappings = { - xaxis: 'timeColumn', - yaxis: 'valueColumn', - spliton: 'segmentColumn', - groupBy: 'dimension', - groupByOptions: 'dimensions', - filter: 'dimensionFilter', - } as { [old: string]: string }; - - for (const old in mappings) { - if (appInsights[old]) { - appInsights[mappings[old]] = appInsights[old]; - delete appInsights[old]; - } - } - } - - migrateApplicationInsightsDimensions() { - const { appInsights } = this.target; - - if (!appInsights.dimension) { - appInsights.dimension = []; - } - - if (isString(appInsights.dimension)) { - appInsights.dimension = [appInsights.dimension as string]; - } - } - - replace = (variable: string) => { - return this.templateSrv.replace(variable, this.panelCtrl.panel.scopedVars); - }; - - onQueryTypeChange() { - if (this.target.queryType === 'Azure Log Analytics') { - return this.getWorkspaces(); - } - } - - getSubscriptions() { - if (!this.datasource.azureMonitorDatasource.isConfigured()) { - return; - } - - // assert the type - if (!(this.datasource instanceof Datasource)) { - return; - } - - return this.datasource.azureMonitorDatasource.getSubscriptions().then((subscriptions) => { - // We changed the format in the datasource for the new react stuff, so here we change it back - const subs = subscriptions.map((v) => ({ - text: `${v.text} - ${v.value}`, - value: v.value, - })); - - this.subscriptions = subs; - if (!this.target.subscription && this.target.queryType === 'Azure Monitor') { - this.target.subscription = this.datasource.azureMonitorDatasource.subscriptionId; - } else if (!this.target.subscription && this.target.queryType === 'Azure Log Analytics') { - this.target.subscription = this.datasource.azureLogAnalyticsDatasource.subscriptionId; - } - - if (!this.target.subscription && this.subscriptions.length > 0) { - this.target.subscription = this.subscriptions[0].value; - } - - if (!this.target.subscriptions) { - this.target.subscriptions = subscriptions.map((sub) => sub.value); - } - - return this.subscriptions; - }); - } - - onSubscriptionChange() { - if (this.target.queryType === 'Azure Log Analytics') { - return this.getWorkspaces(); - } - } - - generateAutoUnits(timeGrain: string, timeGrains: Array<{ value: string }>) { - if (timeGrain === 'auto') { - return TimegrainConverter.findClosestTimeGrain( - '1m', - map(timeGrains, (o) => TimegrainConverter.createKbnUnitFromISO8601Duration(o.value)) || [ - '1m', - '5m', - '15m', - '30m', - '1h', - '6h', - '12h', - '1d', - ] - ); - } - - return ''; - } - - getAzureMonitorAutoInterval() { - return this.generateAutoUnits( - this.target.azureMonitor.timeGrain ?? 'auto', - (this.target.azureMonitor as any).timeGrains - ); - } - - getApplicationInsightAutoInterval() { - return this.generateAutoUnits(this.target.appInsights.timeGrain, this.target.appInsights.timeGrains); - } - - azureMonitorAddDimensionFilter() { - this.target.azureMonitor = this.target.azureMonitor ?? {}; - this.target.azureMonitor.dimensionFilters = this.target.azureMonitor.dimensionFilters ?? []; - - this.target.azureMonitor.dimensionFilters.push({ - dimension: '', - operator: 'eq', - filter: '', - }); - } - - azureMonitorRemoveDimensionFilter(index: number) { - this.target.azureMonitor = this.target.azureMonitor ?? {}; - this.target.azureMonitor.dimensionFilters = this.target.azureMonitor.dimensionFilters ?? []; - - this.target.azureMonitor.dimensionFilters.splice(index, 1); - this.refresh(); - } - - /* Azure Log Analytics */ - - getWorkspaces = () => { - return this.datasource.azureLogAnalyticsDatasource - .getWorkspaces(this.target.subscription) - .then((list: any[]) => { - this.workspaces = list; - - if (list.length > 0 && !this.target.azureLogAnalytics.workspace) { - if (this.datasource.azureLogAnalyticsDatasource.defaultOrFirstWorkspace) { - this.target.azureLogAnalytics.workspace = this.datasource.azureLogAnalyticsDatasource.defaultOrFirstWorkspace; - } - - if (!this.target.azureLogAnalytics.workspace) { - this.target.azureLogAnalytics.workspace = list[0].value; - } - } - - return this.workspaces; - }) - .catch(this.handleQueryCtrlError.bind(this)); - }; - - getAzureLogAnalyticsSchema = () => { - return this.getWorkspaces() - .then(() => { - return this.datasource.azureLogAnalyticsDatasource.getSchema(this.target.azureLogAnalytics.workspace); - }) - .catch(this.handleQueryCtrlError.bind(this)); - }; - - onLogAnalyticsQueryChange = (nextQuery: string) => { - this.target.azureLogAnalytics.query = nextQuery; - }; - - onLogAnalyticsQueryExecute = () => { - this.panelCtrl.refresh(); - }; - - get templateVariables() { - return this.templateSrv.getVariables().map((t) => '$' + t.name); - } - - getAppInsightsMetricNames() { - if (!this.datasource.appInsightsDatasource.isConfigured()) { - return; - } - - return this.datasource.getAppInsightsMetricNames().catch(this.handleQueryCtrlError.bind(this)); - } - - getAppInsightsColumns() { - return this.datasource.getAppInsightsColumns(this.target.refId); - } - - onAppInsightsColumnChange() { - return this.refresh(); - } - - onAppInsightsMetricNameChange() { - if (!this.target.appInsights.metricName || this.target.appInsights.metricName === this.defaultDropdownValue) { - return; - } - - return this.datasource - .getAppInsightsMetricMetadata(this.replace(this.target.appInsights.metricName)) - .then((aggData: { supportedAggTypes: string[]; supportedGroupBy: string[]; primaryAggType: string }) => { - this.target.appInsights.aggOptions = aggData.supportedAggTypes; - this.target.appInsights.dimensions = aggData.supportedGroupBy; - this.target.appInsights.aggregation = aggData.primaryAggType; - return this.refresh(); - }) - .catch(this.handleQueryCtrlError.bind(this)); - } - - onInsightsAnalyticsQueryChange = (nextQuery: string) => { - this.target.insightsAnalytics.query = nextQuery; - }; - - onQueryExecute = () => { - return this.refresh(); - }; - - getAppInsightsQuerySchema = () => { - return this.datasource.appInsightsDatasource.getQuerySchema().catch(this.handleQueryCtrlError.bind(this)); - }; - - removeGroupBy = (index: number) => { - const { appInsights } = this.target; - appInsights.dimension.splice(index, 1); - this.refresh(); - }; - - getAppInsightsGroupBySegments(query: any) { - const { appInsights } = this.target; - - // HACK alert... there must be a better way! - if (this.dummyDiminsionString && this.dummyDiminsionString.length && '+' !== this.dummyDiminsionString) { - if (!appInsights.dimension) { - appInsights.dimension = []; - } - appInsights.dimension.push(this.dummyDiminsionString); - this.dummyDiminsionString = '+'; - this.refresh(); - } - - // Return the list of dimensions stored on the query object from the last request :( - return map(appInsights.dimensions, (option: string) => { - return { text: option, value: option }; - }); - } - - resetAppInsightsGroupBy() { - this.target.appInsights.dimension = 'none'; - this.refresh(); - } - - updateTimeGrainType() { - if (this.target.appInsights.timeGrainType === 'specific') { - this.target.appInsights.timeGrainCount = '1'; - this.target.appInsights.timeGrainUnit = 'minute'; - this.target.appInsights.timeGrain = TimegrainConverter.createISO8601Duration( - this.target.appInsights.timeGrainCount, - this.target.appInsights.timeGrainUnit - ); - } else { - this.target.appInsights.timeGrainCount = ''; - this.target.appInsights.timeGrainUnit = ''; - } - } - - updateAppInsightsTimeGrain() { - if (this.target.appInsights.timeGrainUnit && this.target.appInsights.timeGrainCount) { - this.target.appInsights.timeGrain = TimegrainConverter.createISO8601Duration( - this.target.appInsights.timeGrainCount, - this.target.appInsights.timeGrainUnit - ); - } - this.refresh(); - } - - /** - * Receives a full new query object from React and updates it into the Angular controller - */ - handleNewQuery = (newQuery: AzureMonitorQuery) => { - Object.assign(this.target, newQuery); - this.refresh(); - }; -} - -// Modifies the actual query object -export function migrateMetricsDimensionFilters(item: AzureMetricQuery) { - if (!item.dimensionFilters) { - item.dimensionFilters = []; - } - const oldDimension = (item as any).dimension; - if (oldDimension && oldDimension !== 'None') { - item.dimensionFilters.push({ - dimension: oldDimension, - operator: 'eq', - filter: (item as any).dimensionFilter, - }); - delete (item as any).dimension; - delete (item as any).dimensionFilter; - } -}