AzureMonitor: Remove unused editor component (#39874)

This commit is contained in:
Isabella Siu 2021-10-04 09:17:59 -04:00 committed by GitHub
parent 6572017ec7
commit 42d7c32759
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 0 additions and 2985 deletions

View File

@ -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 =
'<your table>\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);
}
}

View File

@ -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 '';
}

View File

@ -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<any>;
execute?: () => void;
query?: string;
}
class Editor extends Component<EditorProps, any> {
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 (
<div className="gf-form-input" style={{ height: 'auto' }}>
<KustoQueryField
initialQuery={edited ? null : query}
onPressEnter={this.onPressEnter}
onQueryChange={this.onChangeQuery}
prismLanguage="kusto"
prismDefinition={Kusto}
placeholder={placeholder}
templateVariables={variables}
getSchema={getSchema}
/>
</div>
);
}
}
coreModule.directive('kustoEditor', [
'reactDirective',
(reactDirective) => {
return reactDirective(Editor, [
'change',
'database',
'execute',
'query',
'variables',
'placeholder',
['getSchema', { watchDepth: 'reference' }],
]);
},
]);

View File

@ -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
// }

View File

@ -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<any, any> {
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<any, any> {
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 (
<Portal prefix={portalPrefix}>
<Typeahead
menuRef={this.menuRef}
selectedItems={selectedKeys}
onClickItem={this.onClickItem}
groupedItems={suggestions}
/>
</Portal>
);
};
render() {
return (
<div className="slate-query-field">
{this.renderMenu()}
<Editor
autoCorrect={false}
onBlur={this.handleBlur}
onKeyDown={this.onKeyDown}
onChange={this.onChange}
onFocus={this.handleFocus}
placeholder={this.props.placeholder}
plugins={this.plugins}
spellCheck={false}
value={this.state.value}
/>
</div>
);
}
}
export default QueryField;

View File

@ -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<any, any> {
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 (
<li ref={this.getRef} className={className} onClick={onClick}>
{label}
{hint && isSelected ? <div className="typeahead-item-hint">{hint}</div> : null}
</li>
);
}
}
class TypeaheadGroup extends React.PureComponent<any, any> {
render() {
const { items, label, selected, onClickItem } = this.props;
return (
<li className="typeahead-group">
<div className="typeahead-group__title">{label}</div>
<ul className="typeahead-group__list">
{items.map((item: any) => {
const text = typeof item === 'object' ? item.text : item;
const label = typeof item === 'object' ? item.display || item.text : item;
return (
<TypeaheadItem
key={text}
onClickItem={onClickItem}
isSelected={selected.indexOf(text) > -1}
hint={item.hint}
label={label}
/>
);
})}
</ul>
</li>
);
}
}
class Typeahead extends React.PureComponent<any, any> {
render() {
const { groupedItems, menuRef, selectedItems, onClickItem } = this.props;
return (
<ul className="typeahead" ref={menuRef}>
{groupedItems.map((g: any) => (
<TypeaheadGroup key={g.label} onClickItem={onClickItem} selected={selectedItems} {...g} />
))}
</ul>
);
}
}
export default Typeahead;

View File

@ -1,531 +0,0 @@
<query-editor-row
query-ctrl="ctrl"
can-collapse="false"
ng-if="!ctrl.reactQueryEditors.includes(ctrl.target.queryType)"
>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Service</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
<select
class="gf-form-input service-dropdown min-width-12"
ng-model="ctrl.target.queryType"
ng-options="f.id as f.label for f in ctrl.queryQueryTypeOptions"
ng-change="ctrl.onQueryTypeChange()"
></select>
</div>
</div>
<div
class="gf-form"
ng-if="ctrl.target.queryType === 'Azure Monitor' || ctrl.target.queryType === 'Azure Log Analytics'"
>
<label class="gf-form-label query-keyword width-9">Subscription</label>
<gf-form-dropdown
model="ctrl.target.subscription"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getSubscriptions()"
on-change="ctrl.onSubscriptionChange()"
css-class="min-width-12"
>
</gf-form-dropdown>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div ng-if="ctrl.target.queryType === 'Azure Monitor'">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Resource Group</label>
<gf-form-dropdown
model="ctrl.target.azureMonitor.resourceGroup"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getResourceGroups($query)"
on-change="ctrl.onResourceGroupChange()"
css-class="min-width-12"
>
</gf-form-dropdown>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Namespace</label>
<gf-form-dropdown
model="ctrl.target.azureMonitor.metricDefinition"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getMetricDefinitions($query)"
on-change="ctrl.onMetricDefinitionChange()"
css-class="min-width-20"
>
</gf-form-dropdown>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Resource Name</label>
<gf-form-dropdown
model="ctrl.target.azureMonitor.resourceName"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getResourceNames($query)"
on-change="ctrl.onResourceNameChange()"
css-class="min-width-12"
>
</gf-form-dropdown>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Metric Namespace</label>
<gf-form-dropdown
model="ctrl.target.azureMonitor.metricNamespace"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getMetricNamespaces($query)"
on-change="ctrl.onMetricNamespacesChange()"
css-class="min-width-12"
>
</gf-form-dropdown>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Metric</label>
<gf-form-dropdown
model="ctrl.target.azureMonitor.metricName"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getMetricNames($query)"
on-change="ctrl.onMetricNameChange()"
css-class="min-width-12"
>
</gf-form-dropdown>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Aggregation</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
<select
class="gf-form-input width-11"
ng-model="ctrl.target.azureMonitor.aggregation"
ng-options="f as f for f in ctrl.target.azureMonitor.aggOptions"
ng-change="ctrl.refresh()"
></select>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Time Grain</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent timegrainunit-dropdown-wrapper">
<select
class="gf-form-input"
ng-model="ctrl.target.azureMonitor.timeGrain"
ng-options="f.value as f.text for f in ctrl.target.azureMonitor.timeGrains"
ng-change="ctrl.refresh()"
></select>
</div>
</div>
<div class="gf-form" ng-show="ctrl.target.azureMonitor.timeGrain.trim() === 'auto'">
<label class="gf-form-label">Auto Interval</label>
<label class="gf-form-label">{{ctrl.getAzureMonitorAutoInterval()}}</label>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<!-- NO Filters-->
<ng-container ng-if="ctrl.target.azureMonitor.dimensionFilters.length < 1">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Dimension</label>
</div>
<div class="gf-form">
<a ng-click="ctrl.azureMonitorAddDimensionFilter()" class="gf-form-label query-part"
><icon name="'plus'"></icon
></a>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</ng-container>
<!-- YES Filters-->
<ng-container ng-if="ctrl.target.azureMonitor.dimensionFilters.length > 0">
<div ng-repeat="dim in ctrl.target.azureMonitor.dimensionFilters track by $index" class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Dimension</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
<select
class="gf-form-input min-width-12"
ng-model="dim.dimension"
ng-options="f.value as f.text for f in ctrl.target.azureMonitor.dimensions"
ng-change="ctrl.refresh()"
></select>
</div>
<label class="gf-form-label query-keyword width-3">eq</label>
<input
type="text"
class="gf-form-input width-17"
ng-model="dim.filter"
spellcheck="false"
placeholder="Anything (*)"
ng-blur="ctrl.refresh()"
/>
</div>
<div class="gf-form">
<a ng-click="ctrl.azureMonitorRemoveDimensionFilter($index)" class="gf-form-label query-part"
><icon name="'minus'"></icon
></a>
</div>
<div class="gf-form" ng-if="$last">
<a ng-click="ctrl.azureMonitorAddDimensionFilter()" class="gf-form-label query-part"
><icon name="'plus'"></icon
></a>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Top</label>
<input
type="text"
class="gf-form-input width-3"
ng-model="ctrl.target.azureMonitor.top"
spellcheck="false"
placeholder="10"
ng-blur="ctrl.refresh()"
/>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</ng-container>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Legend Format</label>
<input
type="text"
class="gf-form-input width-30"
ng-model="ctrl.target.azureMonitor.alias"
spellcheck="false"
placeholder="alias patterns (see help for more info)"
ng-blur="ctrl.refresh()"
/>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</div>
<div ng-if="ctrl.target.queryType === 'Azure Log Analytics'">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Workspace</label>
<gf-form-dropdown
model="ctrl.target.azureLogAnalytics.workspace"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getWorkspaces()"
on-change="ctrl.refresh()"
css-class="min-width-12"
>
</gf-form-dropdown>
<div class="gf-form">
<div class="width-1"></div>
</div>
<div class="gf-form">
<button class="btn btn-primary width-10" ng-click="ctrl.refresh()">Run</button>
</div>
<div class="gf-form">
<label class="gf-form-label">(Run Query: Shift+Enter, Trigger Suggestion: Ctrl+Space)</label>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</div>
<div class="gf-form gf-form--grow">
<kusto-editor
class="gf-form gf-form--grow"
query="ctrl.target.azureLogAnalytics.query"
change="ctrl.onLogAnalyticsQueryChange"
execute="ctrl.onLogAnalyticsQueryExecute"
variables="ctrl.templateVariables"
getSchema="ctrl.getAzureLogAnalyticsSchema"
/>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">Format As</label>
<div class="gf-form-select-wrapper">
<select
class="gf-form-input gf-size-auto"
ng-model="ctrl.target.azureLogAnalytics.resultFormat"
ng-options="f.value as f.text for f in ctrl.resultFormats"
ng-change="ctrl.refresh()"
></select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword" ng-click="ctrl.showHelp = !ctrl.showHelp">
Show Help
<icon name="'angle-down'" ng-show="ctrl.showHelp" style="margin-top: 3px"></icon>
<icon name="'angle-right'" ng-hide="ctrl.showHelp" style="margin-top: 3px"></icon>
</label>
</div>
<div class="gf-form" ng-show="ctrl.lastQuery">
<label class="gf-form-label query-keyword" ng-click="ctrl.showLastQuery = !ctrl.showLastQuery">
Raw Query
<icon name="'angle-down'" ng-show="ctrl.showLastQuery"></icon>
<icon name="'angle-right'" ng-hide="ctrl.showLastQuery"></icon>
</label>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form" ng-show="ctrl.showLastQuery">
<pre class="gf-form-pre">{{ctrl.lastQuery}}</pre>
</div>
<div class="gf-form" ng-show="ctrl.showHelp">
<pre class="gf-form-pre alert alert-info">
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() -&gt; TimeGenerated &ge; datetime(2018-06-05T18:09:58.907Z) and TimeGenerated &le; datetime(2018-06-05T20:09:58.907Z)
- $__timeFilter(datetimeColumn) -&gt; datetimeColumn &ge; datetime(2018-06-05T18:09:58.907Z) and datetimeColumn &le; datetime(2018-06-05T20:09:58.907Z)
- $__escapeMulti($myTemplateVar) -&gt; $myTemplateVar should be a multi-value template variables that contains illegal characters
- $__contains(aColumn, $myTemplateVar) -&gt; 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 -&gt; 1 == 1
Or build your own conditionals using these built-in variables which just return the values:
- $__timeFrom -&gt; datetime(2018-06-05T18:09:58.907Z)
- $__timeTo -&gt; datetime(2018-06-05T20:09:58.907Z)
- $__interval -&gt; 5m
Examples:
- ¡ where $__timeFilter
- | where TimeGenerated &ge; $__timeFrom and TimeGenerated &le; $__timeTo
- | summarize count() by Category, bin(TimeGenerated, $__interval)
</pre>
</div>
</div>
<div ng-if="ctrl.target.queryType === 'Insights Analytics'">
<div class="gf-form gf-form--grow">
<kusto-editor
class="gf-form gf-form--grow"
query="ctrl.target.insightsAnalytics.query"
placeholder="'Application Insights Query'"
change="ctrl.onInsightsAnalyticsQueryChange"
execute="ctrl.onQueryExecute"
variables="ctrl.templateVariables"
getSchema="ctrl.getAppInsightsQuerySchema"
/>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">Format As</label>
<div class="gf-form-select-wrapper">
<select
class="gf-form-input gf-size-auto"
ng-model="ctrl.target.insightsAnalytics.resultFormat"
ng-options="f.value as f.text for f in ctrl.resultFormats"
ng-change="ctrl.refresh()"
></select>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</div>
<div ng-if="ctrl.target.queryType === 'Application Insights'">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Metric</label>
<gf-form-dropdown
model="ctrl.target.appInsights.metricName"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getAppInsightsMetricNames($query)"
on-change="ctrl.onAppInsightsMetricNameChange()"
css-class="min-width-20"
>
</gf-form-dropdown>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Aggregation</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
<select
class="gf-form-input"
ng-model="ctrl.target.appInsights.aggregation"
ng-options="f as f for f in ctrl.target.appInsights.aggOptions"
ng-change="ctrl.refresh()"
></select>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Group By</label>
</div>
<div
ng-repeat="d in ctrl.target.appInsights.dimension track by $index"
class="gf-form"
ng-click="ctrl.removeGroupBy($index);"
onmouseover="this.style['text-decoration'] = 'line-through';"
onmouseout="this.style['text-decoration'] = '';"
>
<label class="gf-form-label" style="cursor: pointer">{{d}} <icon name="'times'"></icon></label>
</div>
<div>
<gf-form-dropdown
allow-custom="true"
lookup-text="true"
placeholder="Add"
model="ctrl.dummyDiminsionString"
get-options="ctrl.getAppInsightsGroupBySegments($query)"
on-change="ctrl.getAppInsightsGroupBySegments"
css-class="min-width-5"
>
</gf-form-dropdown>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword">Filter</label>
<input
type="text"
class="gf-form-input width-17"
ng-model="ctrl.target.appInsights.dimensionFilter"
spellcheck="false"
placeholder="your/groupby eq 'a_value'"
ng-blur="ctrl.refresh()"
/>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Time Grain</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
<select
class="gf-form-input"
ng-model="ctrl.target.appInsights.timeGrainType"
ng-options="f as f for f in ['auto', 'none', 'specific']"
ng-change="ctrl.updateTimeGrainType()"
></select>
</div>
</div>
<div
class="gf-form"
ng-hide="ctrl.target.appInsights.timeGrainType === 'auto' || ctrl.target.appInsights.timeGrainType === 'none'"
>
<input
type="text"
class="gf-form-input width-3"
ng-model="ctrl.target.appInsights.timeGrainCount"
spellcheck="false"
placeholder=""
ng-blur="ctrl.updateAppInsightsTimeGrain()"
/>
</div>
<div
class="gf-form"
ng-hide="ctrl.target.appInsights.timeGrainType === 'auto' || ctrl.target.appInsights.timeGrainType === 'none'"
>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent timegrainunit-dropdown-wrapper">
<select
class="gf-form-input"
ng-model="ctrl.target.appInsights.timeGrainUnit"
ng-options="f as f for f in ['minute', 'hour', 'day', 'month', 'year']"
ng-change="ctrl.updateAppInsightsTimeGrain()"
></select>
</div>
</div>
<div class="gf-form" ng-hide="ctrl.target.appInsights.timeGrainType !== 'auto'">
<label class="gf-form-label">Auto Interval (see query options)</label>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Legend Format</label>
<input
type="text"
class="gf-form-input width-30"
ng-model="ctrl.target.appInsights.alias"
spellcheck="false"
placeholder="alias patterns (see help for more info)"
ng-blur="ctrl.refresh()"
/>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</div>
<div class="gf-form" ng-show="ctrl.lastQueryError">
<pre class="gf-form-pre alert alert-error">{{ctrl.lastQueryError}}</pre>
</div>
<div
class="gf-form"
ng-if="ctrl.target.queryType === 'Insights Analytics'"
>
<p class="gf-form-pre alert alert-info">
Application Insights and Insights Analytics will be deprecated and merged with Metrics and Logs in an upcoming
release. See
<a
class="text-link"
href="https://grafana.com/docs/grafana/latest/datasources/azuremonitor/#deprecating-application-insights-and-insights-analytics"
>the documentation</a
>
for more details.
</p>
</div>
</query-editor-row>
<!-- Partial migration to React -->
<div ng-if="ctrl.reactQueryEditors.includes(ctrl.target.queryType)">
<azure-monitor-query-editor query="ctrl.target" datasource="ctrl.datasource" on-change="ctrl.handleNewQuery">
</azure-monitor-query-editor>
</div>

View File

@ -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');
});
});
});
});
});

View File

@ -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',
'<table name> ' +
'//the table to query (e.g. Usage, Heartbeat, Perf)',
'| where $__timeFilter(TimeGenerated) ' +
'//this is a macro used to show the full charts time range, choose the datetime column here',
'| summarize count() by <group by column>, 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;
}
}