mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AzureMonitor: Azure Monitor Cheat sheet (#75931)
This commit is contained in:
parent
efec1d976a
commit
0a6d78f35e
@ -248,4 +248,8 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
|
|||||||
private isValidConfigField(field: string | undefined): boolean {
|
private isValidConfigField(field: string | undefined): boolean {
|
||||||
return typeof field === 'string' && field.length > 0;
|
return typeof field === 'string' && field.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAzureLogAnalyticsCheatsheetQueries() {
|
||||||
|
return await this.getResource(`${this.resourcePath}/v1/metadata`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,263 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||||
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Collapse,
|
||||||
|
CustomScrollbar,
|
||||||
|
Field,
|
||||||
|
Input,
|
||||||
|
LoadingPlaceholder,
|
||||||
|
Select,
|
||||||
|
useStyles2,
|
||||||
|
} from '@grafana/ui';
|
||||||
|
|
||||||
|
import AzureLogAnalyticsDatasource from '../azure_log_analytics/azure_log_analytics_datasource';
|
||||||
|
import {
|
||||||
|
AzureMonitorQuery,
|
||||||
|
AzureQueryType,
|
||||||
|
Category,
|
||||||
|
CheatsheetQueries,
|
||||||
|
CheatsheetQuery,
|
||||||
|
DropdownCategories,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
|
import { RawQuery } from './RawQuery';
|
||||||
|
import tokenizer from './syntax';
|
||||||
|
|
||||||
|
export interface AzureCheatSheetProps {
|
||||||
|
onChange: (query: AzureMonitorQuery) => void;
|
||||||
|
query: AzureMonitorQuery;
|
||||||
|
datasource: AzureLogAnalyticsDatasource;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AzureCheatSheet = (props: AzureCheatSheetProps) => {
|
||||||
|
const [cheatsheetQueries, setCheatsheetQueries] = useState<CheatsheetQueries | null>(null);
|
||||||
|
const [areDropdownsOpen, setAreDropdownsOpen] = useState<DropdownCategories>({});
|
||||||
|
const [visibleQueries, setVisibleQueries] = useState<CheatsheetQueries | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [searchInputValue, setSearchInputValue] = useState('');
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
const lang = { grammar: tokenizer, name: 'kql' };
|
||||||
|
const dropdownMenu = useMemo(() => {
|
||||||
|
if (cheatsheetQueries) {
|
||||||
|
return Object.keys(cheatsheetQueries).map((category): SelectableValue<string> => {
|
||||||
|
return {
|
||||||
|
label: category,
|
||||||
|
value: category,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}, [cheatsheetQueries]);
|
||||||
|
|
||||||
|
const getCheatsheetQueries = async () => {
|
||||||
|
await props.datasource.getAzureLogAnalyticsCheatsheetQueries().then((result) => {
|
||||||
|
result.categories.sort((a: Category, b: Category) => {
|
||||||
|
return a.displayName.toLowerCase() === b.displayName.toLowerCase()
|
||||||
|
? 0
|
||||||
|
: a.displayName.toLowerCase() < b.displayName.toLowerCase()
|
||||||
|
? -1
|
||||||
|
: 1;
|
||||||
|
});
|
||||||
|
const alphabetizedQueries = result.categories.reduce(
|
||||||
|
(queriesByCategory: CheatsheetQueries, category: Category) => {
|
||||||
|
const categoryQueries = category.related.queries.map((queryId: string) => {
|
||||||
|
return result.queries.find((query: CheatsheetQuery) => query.id === queryId);
|
||||||
|
});
|
||||||
|
queriesByCategory[category.displayName] = categoryQueries;
|
||||||
|
setAreDropdownsOpen({ ...areDropdownsOpen, [category.id]: false });
|
||||||
|
return queriesByCategory;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
setCheatsheetQueries(alphabetizedQueries);
|
||||||
|
setVisibleQueries(alphabetizedQueries);
|
||||||
|
setIsLoading(false);
|
||||||
|
return alphabetizedQueries;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!cheatsheetQueries) {
|
||||||
|
getCheatsheetQueries();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const filterQueriesBySearch = (searchValue: string) => {
|
||||||
|
const visibleQueriesCategories = Object.keys(visibleQueries!);
|
||||||
|
if (searchValue.length > 0 && cheatsheetQueries) {
|
||||||
|
const filteredQueries: CheatsheetQueries = Object.keys(cheatsheetQueries).reduce(
|
||||||
|
(filteredQueriesBySearch: CheatsheetQueries, category) => {
|
||||||
|
const filters = cheatsheetQueries![category]!.filter((query) => {
|
||||||
|
return query.displayName.toLowerCase().includes(searchValue.toLowerCase());
|
||||||
|
});
|
||||||
|
if (visibleQueriesCategories.includes(category)) {
|
||||||
|
filteredQueriesBySearch[category] = filters;
|
||||||
|
}
|
||||||
|
return filteredQueriesBySearch;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
setVisibleQueries(filteredQueries);
|
||||||
|
return filteredQueries;
|
||||||
|
} else {
|
||||||
|
if (Object.keys(visibleQueries!).length !== Object.keys(cheatsheetQueries!).length) {
|
||||||
|
setVisibleQueries(visibleQueries);
|
||||||
|
return visibleQueries;
|
||||||
|
} else {
|
||||||
|
setVisibleQueries(cheatsheetQueries);
|
||||||
|
return cheatsheetQueries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterQueriesByCategory = (categories: SelectableValue<string>) => {
|
||||||
|
if (categories.length > 0) {
|
||||||
|
const selectedCategories = categories.map((selectedCategory: SelectableValue) => selectedCategory.label);
|
||||||
|
const updatedVisibleQueries = selectedCategories.reduce(
|
||||||
|
(updatedVisibleQueries: CheatsheetQueries, queryCategory: string) => {
|
||||||
|
updatedVisibleQueries[queryCategory] = cheatsheetQueries![queryCategory]!;
|
||||||
|
return updatedVisibleQueries;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
setVisibleQueries(updatedVisibleQueries);
|
||||||
|
} else {
|
||||||
|
setVisibleQueries(cheatsheetQueries);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{!isLoading && visibleQueries ? (
|
||||||
|
<div>
|
||||||
|
<div className={styles.filterAlignment}>
|
||||||
|
<Input
|
||||||
|
value={searchInputValue}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSearchInputValue(e.currentTarget.value);
|
||||||
|
const filteredQueries = filterQueriesBySearch(e.currentTarget.value);
|
||||||
|
setVisibleQueries(filteredQueries);
|
||||||
|
}}
|
||||||
|
placeholder="Search Logs queries"
|
||||||
|
width={40}
|
||||||
|
/>
|
||||||
|
<Field label="Categories" className={styles.categoryDropdown}>
|
||||||
|
<Select
|
||||||
|
options={dropdownMenu}
|
||||||
|
value={''}
|
||||||
|
onChange={(a) => filterQueriesByCategory(a)}
|
||||||
|
allowCustomValue={false}
|
||||||
|
backspaceRemovesValue={true}
|
||||||
|
placeholder="All categories"
|
||||||
|
isClearable={true}
|
||||||
|
noOptionsMessage="Unable to list all categories"
|
||||||
|
formatCreateLabel={(input: string) => `Category: ${input}`}
|
||||||
|
isSearchable={true}
|
||||||
|
isMulti={true}
|
||||||
|
width={40}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
<div className={styles.spacing}>
|
||||||
|
Query results:{' '}
|
||||||
|
{Object.keys(visibleQueries).reduce((totalQueries: number, category) => {
|
||||||
|
totalQueries = visibleQueries[category]!.length + totalQueries;
|
||||||
|
return totalQueries;
|
||||||
|
}, 0)}
|
||||||
|
</div>
|
||||||
|
<CustomScrollbar showScrollIndicators={true} autoHeightMax="350px">
|
||||||
|
{Object.keys(visibleQueries).map((category: string) => {
|
||||||
|
if (visibleQueries[category]!.length) {
|
||||||
|
return (
|
||||||
|
<Collapse
|
||||||
|
label={category + ' ' + `(${visibleQueries[category]!.length})`}
|
||||||
|
collapsible={true}
|
||||||
|
isOpen={areDropdownsOpen[category]}
|
||||||
|
onToggle={(isOpen) => setAreDropdownsOpen({ ...areDropdownsOpen, [category]: isOpen })}
|
||||||
|
key={category}
|
||||||
|
>
|
||||||
|
{visibleQueries[category]!.map((query) => {
|
||||||
|
return (
|
||||||
|
<Card className={styles.card} key={query.id}>
|
||||||
|
<Card.Heading>{query.displayName}</Card.Heading>
|
||||||
|
<CustomScrollbar showScrollIndicators={true} autoHeightMax="100px">
|
||||||
|
<RawQuery
|
||||||
|
aria-label={`${query.displayName} raw query`}
|
||||||
|
query={query.body}
|
||||||
|
lang={lang}
|
||||||
|
className={styles.rawQuery}
|
||||||
|
/>
|
||||||
|
</CustomScrollbar>
|
||||||
|
<Card.Actions>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
aria-label="use this query button"
|
||||||
|
onClick={() => {
|
||||||
|
props.onChange({
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.LogAnalytics,
|
||||||
|
azureLogAnalytics: { query: query.body },
|
||||||
|
datasource: props.datasource,
|
||||||
|
});
|
||||||
|
reportInteraction('grafana_azure_cheatsheet_logs_query_selected', {
|
||||||
|
id: query.id,
|
||||||
|
queryName: query.displayName,
|
||||||
|
query: query.body,
|
||||||
|
queryCategories: query.related.categories,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Use this query
|
||||||
|
</Button>
|
||||||
|
</Card.Actions>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Collapse>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
})}
|
||||||
|
</CustomScrollbar>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<LoadingPlaceholder text="Loading..." />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AzureCheatSheet;
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
card: css({
|
||||||
|
width: '90%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}),
|
||||||
|
rawQuery: css({
|
||||||
|
backgroundColor: `${theme.colors.background.primary}`,
|
||||||
|
padding: `${theme.spacing(1)}`,
|
||||||
|
marginTop: `${theme.spacing(1)}`,
|
||||||
|
}),
|
||||||
|
spacing: css({
|
||||||
|
marginBottom: `${theme.spacing(1)}`,
|
||||||
|
}),
|
||||||
|
filterAlignment: css({
|
||||||
|
display: 'flex',
|
||||||
|
}),
|
||||||
|
categoryDropdown: css({
|
||||||
|
margin: '0 0 10px 10px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { CoreApp } from '@grafana/data';
|
||||||
|
import { Modal } from '@grafana/ui';
|
||||||
|
|
||||||
|
import AzureLogAnalyticsDatasource from '../../azure_log_analytics/azure_log_analytics_datasource';
|
||||||
|
import { AzureMonitorQuery } from '../../dataquery.gen';
|
||||||
|
import AzureCheatSheet from '../AzureCheatSheet';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean;
|
||||||
|
app?: CoreApp;
|
||||||
|
onClose: () => void;
|
||||||
|
onChange: (query: AzureMonitorQuery) => void;
|
||||||
|
onAddQuery?: (query: AzureMonitorQuery) => void;
|
||||||
|
datasource: AzureLogAnalyticsDatasource;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AzureCheatSheetModal = (props: Props) => {
|
||||||
|
const { isOpen, onClose, datasource, onChange } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal aria-label="Kick start your query modal" isOpen={isOpen} title="Kick start your query" onDismiss={onClose}>
|
||||||
|
<AzureCheatSheet
|
||||||
|
onChange={(a) => {
|
||||||
|
onChange(a);
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
query={{ refId: 'A' }}
|
||||||
|
datasource={datasource}
|
||||||
|
></AzureCheatSheet>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
@ -1,8 +1,10 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { QueryEditorProps } from '@grafana/data';
|
import { QueryEditorProps } from '@grafana/data';
|
||||||
import { Alert, CodeEditor } from '@grafana/ui';
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
|
import { Alert, Button, CodeEditor } from '@grafana/ui';
|
||||||
|
|
||||||
import AzureMonitorDatasource from '../../datasource';
|
import AzureMonitorDatasource from '../../datasource';
|
||||||
import {
|
import {
|
||||||
@ -15,6 +17,7 @@ import {
|
|||||||
import useLastError from '../../utils/useLastError';
|
import useLastError from '../../utils/useLastError';
|
||||||
import ArgQueryEditor from '../ArgQueryEditor';
|
import ArgQueryEditor from '../ArgQueryEditor';
|
||||||
import LogsQueryEditor from '../LogsQueryEditor';
|
import LogsQueryEditor from '../LogsQueryEditor';
|
||||||
|
import { AzureCheatSheetModal } from '../LogsQueryEditor/AzureCheatSheetModal';
|
||||||
import NewMetricsQueryEditor from '../MetricsQueryEditor/MetricsQueryEditor';
|
import NewMetricsQueryEditor from '../MetricsQueryEditor/MetricsQueryEditor';
|
||||||
import { QueryHeader } from '../QueryHeader';
|
import { QueryHeader } from '../QueryHeader';
|
||||||
import { Space } from '../Space';
|
import { Space } from '../Space';
|
||||||
@ -38,6 +41,7 @@ const QueryEditor = ({
|
|||||||
}: AzureMonitorQueryEditorProps) => {
|
}: AzureMonitorQueryEditorProps) => {
|
||||||
const [errorMessage, setError] = useLastError();
|
const [errorMessage, setError] = useLastError();
|
||||||
const onRunQuery = useMemo(() => debounce(baseOnRunQuery, 500), [baseOnRunQuery]);
|
const onRunQuery = useMemo(() => debounce(baseOnRunQuery, 500), [baseOnRunQuery]);
|
||||||
|
const [azureLogsCheatSheetModalOpen, setAzureLogsCheatSheetModalOpen] = useState(false);
|
||||||
|
|
||||||
const onQueryChange = useCallback(
|
const onQueryChange = useCallback(
|
||||||
(newQuery: AzureMonitorQuery) => {
|
(newQuery: AzureMonitorQuery) => {
|
||||||
@ -57,8 +61,32 @@ const QueryEditor = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid="azure-monitor-query-editor">
|
<div data-testid="azure-monitor-query-editor">
|
||||||
<QueryHeader query={query} onQueryChange={onQueryChange} />
|
<AzureCheatSheetModal
|
||||||
|
datasource={datasource.azureLogAnalyticsDatasource}
|
||||||
|
isOpen={azureLogsCheatSheetModalOpen}
|
||||||
|
onClose={() => setAzureLogsCheatSheetModalOpen(false)}
|
||||||
|
onChange={(a) => onChange({ ...a, queryType: AzureQueryType.LogAnalytics })}
|
||||||
|
/>
|
||||||
|
<div className={css({ display: 'flex', alignItems: 'center' })}>
|
||||||
|
<QueryHeader query={query} onQueryChange={onQueryChange} />
|
||||||
|
{query.queryType === AzureQueryType.LogAnalytics && (
|
||||||
|
<Button
|
||||||
|
aria-label="Azure logs kick start your query button"
|
||||||
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setAzureLogsCheatSheetModalOpen((prevValue) => !prevValue);
|
||||||
|
|
||||||
|
reportInteraction('grafana_azure_logs_query_patterns_opened', {
|
||||||
|
version: 'v2',
|
||||||
|
editorMode: query.azureLogAnalytics,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Kick start your query
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<EditorForQueryType
|
<EditorForQueryType
|
||||||
data={data}
|
data={data}
|
||||||
subscriptionId={subscriptionId}
|
subscriptionId={subscriptionId}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import { css, cx } from '@emotion/css';
|
||||||
|
import Prism, { Grammar } from 'prismjs';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data/src';
|
||||||
|
import { useTheme2 } from '@grafana/ui/src';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
query: string;
|
||||||
|
lang: {
|
||||||
|
grammar: Grammar;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
export function RawQuery({ query, lang, className }: Props) {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = getStyles(theme);
|
||||||
|
const highlighted = Prism.highlight(query, lang.grammar, lang.name);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cx(styles.editorField, 'prism-syntax-highlight', className)}
|
||||||
|
aria-label="selector"
|
||||||
|
dangerouslySetInnerHTML={{ __html: highlighted }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
editorField: css({
|
||||||
|
fontFamily: theme.typography.fontFamilyMonospace,
|
||||||
|
fontSize: theme.typography.bodySmall.fontSize,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
366
public/app/plugins/datasource/azuremonitor/components/syntax.ts
Normal file
366
public/app/plugins/datasource/azuremonitor/components/syntax.ts
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
import { Grammar } from 'prismjs';
|
||||||
|
|
||||||
|
import { CompletionItem } from '@grafana/ui';
|
||||||
|
|
||||||
|
export const QUERY_COMMANDS: CompletionItem[] = [
|
||||||
|
{
|
||||||
|
label: 'fields',
|
||||||
|
documentation: 'Retrieves the specified fields from log events',
|
||||||
|
},
|
||||||
|
{ label: 'display', documentation: 'Specifies which fields to display in the query results' },
|
||||||
|
{
|
||||||
|
label: 'filter',
|
||||||
|
documentation: 'Filters the results of a query based on one or more conditions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'stats',
|
||||||
|
documentation: 'Calculates aggregate statistics based on the values of log fields',
|
||||||
|
},
|
||||||
|
{ label: 'sort', documentation: 'Sorts the retrieved log events' },
|
||||||
|
{ label: 'limit', documentation: 'Specifies the number of log events returned by the query' },
|
||||||
|
{
|
||||||
|
label: 'parse',
|
||||||
|
documentation:
|
||||||
|
'Extracts data from a log field, creating one or more ephemeral fields that you can process further in the query',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const COMPARISON_OPERATORS = ['=', '!=', '<', '<=', '>', '>='];
|
||||||
|
export const ARITHMETIC_OPERATORS = ['+', '-', '*', '/', '^', '%'];
|
||||||
|
|
||||||
|
export const NUMERIC_OPERATORS = [
|
||||||
|
{
|
||||||
|
label: 'abs',
|
||||||
|
detail: 'abs(a)',
|
||||||
|
documentation: 'Absolute value.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ceil',
|
||||||
|
detail: 'ceil(a)',
|
||||||
|
documentation: 'Round to ceiling (the smallest integer that is greater than the value of a).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'floor',
|
||||||
|
detail: 'floor(a)',
|
||||||
|
documentation: 'Round to floor (the largest integer that is smaller than the value of a).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'greatest',
|
||||||
|
detail: 'greatest(a,b, ... z)',
|
||||||
|
documentation: 'Returns the largest value.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'least',
|
||||||
|
detail: 'least(a, b, ... z)',
|
||||||
|
documentation: 'Returns the smallest value.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'log',
|
||||||
|
detail: 'log(a)',
|
||||||
|
documentation: 'Natural logarithm.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'sqrt',
|
||||||
|
detail: 'sqrt(a)',
|
||||||
|
documentation: 'Square root.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const GENERAL_FUNCTIONS = [
|
||||||
|
{
|
||||||
|
label: 'ispresent',
|
||||||
|
detail: 'ispresent(fieldname)',
|
||||||
|
documentation: 'Returns true if the field exists.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'coalesce',
|
||||||
|
detail: 'coalesce(fieldname1, fieldname2, ... fieldnamex)',
|
||||||
|
documentation: 'Returns the first non-null value from the list.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const STRING_FUNCTIONS = [
|
||||||
|
{
|
||||||
|
label: 'isempty',
|
||||||
|
detail: 'isempty(fieldname)',
|
||||||
|
documentation: 'Returns true if the field is missing or is an empty string.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'isblank',
|
||||||
|
detail: 'isblank(fieldname)',
|
||||||
|
documentation: 'Returns true if the field is missing, an empty string, or contains only white space.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'concat',
|
||||||
|
detail: 'concat(string1, string2, ... stringz)',
|
||||||
|
documentation: 'Concatenates the strings.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ltrim',
|
||||||
|
detail: 'ltrim(string) or ltrim(string1, string2)',
|
||||||
|
documentation:
|
||||||
|
'Remove white space from the left of the string. If the function has a second string argument, it removes the characters of string2 from the left of string1.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'rtrim',
|
||||||
|
detail: 'rtrim(string) or rtrim(string1, string2)',
|
||||||
|
documentation:
|
||||||
|
'Remove white space from the right of the string. If the function has a second string argument, it removes the characters of string2 from the right of string1.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'trim',
|
||||||
|
detail: 'trim(string) or trim(string1, string2)',
|
||||||
|
documentation:
|
||||||
|
'Remove white space from both ends of the string. If the function has a second string argument, it removes the characters of string2 from both sides of string1.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'strlen',
|
||||||
|
detail: 'strlen(string)',
|
||||||
|
documentation: 'Returns the length of the string in Unicode code points.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'toupper',
|
||||||
|
detail: 'toupper(string)',
|
||||||
|
documentation: 'Converts the string to uppercase.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'tolower',
|
||||||
|
detail: 'tolower(string)',
|
||||||
|
documentation: 'Converts the string to lowercase.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'substr',
|
||||||
|
detail: 'substr(string1, x), or substr(string1, x, y)',
|
||||||
|
documentation:
|
||||||
|
'Returns a substring from the index specified by the number argument to the end of the string. If the function has a second number argument, it contains the length of the substring to be retrieved.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'replace',
|
||||||
|
detail: 'replace(string1, string2, string3)',
|
||||||
|
documentation: 'Replaces all instances of string2 in string1 with string3.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'strcontains',
|
||||||
|
detail: 'strcontains(string1, string2)',
|
||||||
|
documentation: 'Returns 1 if string1 contains string2 and 0 otherwise.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const DATETIME_FUNCTIONS = [
|
||||||
|
{
|
||||||
|
label: 'bin',
|
||||||
|
detail: 'bin(period)',
|
||||||
|
documentation: 'Rounds the value of @timestamp to the given period and then truncates.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'datefloor',
|
||||||
|
detail: 'datefloor(a, period)',
|
||||||
|
documentation: 'Truncates the timestamp to the given period.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'dateceil',
|
||||||
|
detail: 'dateceil(a, period)',
|
||||||
|
documentation: 'Rounds up the timestamp to the given period and then truncates.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'fromMillis',
|
||||||
|
detail: 'fromMillis(fieldname)',
|
||||||
|
documentation:
|
||||||
|
'Interprets the input field as the number of milliseconds since the Unix epoch and converts it to a timestamp.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'toMillis',
|
||||||
|
detail: 'toMillis(fieldname)',
|
||||||
|
documentation:
|
||||||
|
'Converts the timestamp found in the named field into a number representing the milliseconds since the Unix epoch.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const IP_FUNCTIONS = [
|
||||||
|
{
|
||||||
|
label: 'isValidIp',
|
||||||
|
detail: 'isValidIp(fieldname)',
|
||||||
|
documentation: 'Returns true if the field is a valid v4 or v6 IP address.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'isValidIpV4',
|
||||||
|
detail: 'isValidIpV4(fieldname)',
|
||||||
|
documentation: 'Returns true if the field is a valid v4 IP address.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'isValidIpV6',
|
||||||
|
detail: 'isValidIpV6(fieldname)',
|
||||||
|
documentation: 'Returns true if the field is a valid v6 IP address.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'isIpInSubnet',
|
||||||
|
detail: 'isIpInSubnet(fieldname, string)',
|
||||||
|
documentation: 'Returns true if the field is a valid v4 or v6 IP address within the specified v4 or v6 subnet.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'isIpv4InSubnet',
|
||||||
|
detail: 'isIpv4InSubnet(fieldname, string)',
|
||||||
|
documentation: 'Returns true if the field is a valid v4 IP address within the specified v4 subnet.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'isIpv6InSubnet',
|
||||||
|
detail: 'isIpv6InSubnet(fieldname, string)',
|
||||||
|
documentation: 'Returns true if the field is a valid v6 IP address within the specified v6 subnet.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const BOOLEAN_FUNCTIONS = [
|
||||||
|
{
|
||||||
|
label: 'ispresent',
|
||||||
|
detail: 'ispresent(fieldname)',
|
||||||
|
documentation: 'Returns true if the field exists.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'isempty',
|
||||||
|
detail: 'isempty(fieldname)',
|
||||||
|
documentation: 'Returns true if the field is missing or is an empty string.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'isblank',
|
||||||
|
detail: 'isblank(fieldname)',
|
||||||
|
documentation: 'Returns true if the field is missing, an empty string, or contains only white space.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'strcontains',
|
||||||
|
detail: 'strcontains(string1, string2)',
|
||||||
|
documentation: 'Returns 1 if string1 contains string2 and 0 otherwise.',
|
||||||
|
},
|
||||||
|
...IP_FUNCTIONS,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const AGGREGATION_FUNCTIONS_STATS = [
|
||||||
|
{
|
||||||
|
label: 'avg',
|
||||||
|
detail: 'avg(NumericFieldname)',
|
||||||
|
documentation: 'The average of the values in the specified field.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'count',
|
||||||
|
detail: 'count(fieldname) or count(*)',
|
||||||
|
documentation: 'Counts the log records.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'count_distinct',
|
||||||
|
detail: 'count_distinct(fieldname)',
|
||||||
|
documentation: 'Returns the number of unique values for the field.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'max',
|
||||||
|
detail: 'max(fieldname)',
|
||||||
|
documentation: 'The maximum of the values for this log field in the queried logs.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'min',
|
||||||
|
detail: 'min(fieldname)',
|
||||||
|
documentation: 'The minimum of the values for this log field in the queried logs.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'pct',
|
||||||
|
detail: 'pct(fieldname, value)',
|
||||||
|
documentation: 'A percentile indicates the relative standing of a value in a datas.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'stddev',
|
||||||
|
detail: 'stddev(NumericFieldname)',
|
||||||
|
documentation: 'The standard deviation of the values in the specified field.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'sum',
|
||||||
|
detail: 'sum(NumericFieldname)',
|
||||||
|
documentation: 'The sum of the values in the specified field.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const NON_AGGREGATION_FUNCS_STATS = [
|
||||||
|
{
|
||||||
|
label: 'earliest',
|
||||||
|
detail: 'earliest(fieldname)',
|
||||||
|
documentation:
|
||||||
|
'Returns the value of fieldName from the log event that has the earliest time stamp in the queried logs.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'latest',
|
||||||
|
detail: 'latest(fieldname)',
|
||||||
|
documentation:
|
||||||
|
'Returns the value of fieldName from the log event that has the latest time stamp in the queried logs.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'sortsFirst',
|
||||||
|
detail: 'sortsFirst(fieldname)',
|
||||||
|
documentation: 'Returns the value of fieldName that sorts first in the queried logs.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'sortsLast',
|
||||||
|
detail: 'sortsLast(fieldname)',
|
||||||
|
documentation: 'Returns the value of fieldName that sorts last in the queried logs.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const STATS_FUNCS = [...AGGREGATION_FUNCTIONS_STATS, ...NON_AGGREGATION_FUNCS_STATS];
|
||||||
|
|
||||||
|
export const KEYWORDS = ['as', 'like', 'by', 'in', 'desc', 'asc'];
|
||||||
|
export const FIELD_AND_FILTER_FUNCTIONS = [
|
||||||
|
...NUMERIC_OPERATORS,
|
||||||
|
...GENERAL_FUNCTIONS,
|
||||||
|
...STRING_FUNCTIONS,
|
||||||
|
...DATETIME_FUNCTIONS,
|
||||||
|
...IP_FUNCTIONS,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const FUNCTIONS = [...FIELD_AND_FILTER_FUNCTIONS, ...STATS_FUNCS];
|
||||||
|
|
||||||
|
const tokenizer: Grammar = {
|
||||||
|
comment: {
|
||||||
|
pattern: /^#.*/,
|
||||||
|
greedy: true,
|
||||||
|
},
|
||||||
|
backticks: {
|
||||||
|
pattern: /`.*?`/,
|
||||||
|
alias: 'string',
|
||||||
|
greedy: true,
|
||||||
|
},
|
||||||
|
quote: {
|
||||||
|
pattern: /".*?"/,
|
||||||
|
alias: 'string',
|
||||||
|
greedy: true,
|
||||||
|
},
|
||||||
|
regex: {
|
||||||
|
pattern: /\/.*?\/(?=\||\s*$|,)/,
|
||||||
|
greedy: true,
|
||||||
|
},
|
||||||
|
'query-command': {
|
||||||
|
pattern: new RegExp(`\\b(?:${QUERY_COMMANDS.map((command) => command.label).join('|')})\\b`, 'i'),
|
||||||
|
alias: 'function',
|
||||||
|
},
|
||||||
|
function: {
|
||||||
|
pattern: new RegExp(`\\b(?:${FUNCTIONS.map((f) => f.label).join('|')})\\b`, 'i'),
|
||||||
|
},
|
||||||
|
keyword: {
|
||||||
|
pattern: new RegExp(`(\\s+)(${KEYWORDS.join('|')})(?=\\s+)`, 'i'),
|
||||||
|
lookbehind: true,
|
||||||
|
},
|
||||||
|
'log-group-name': {
|
||||||
|
pattern: /[\.\-_/#A-Za-z0-9]+/,
|
||||||
|
},
|
||||||
|
'field-name': {
|
||||||
|
pattern: /(@?[_a-zA-Z]+[_.0-9a-zA-Z]*)|(`((\\`)|([^`]))*?`)/,
|
||||||
|
greedy: true,
|
||||||
|
},
|
||||||
|
number: /\b-?\d+((\.\d*)?([eE][+-]?\d+)?)?\b/,
|
||||||
|
'command-separator': {
|
||||||
|
pattern: /\|/,
|
||||||
|
alias: 'punctuation',
|
||||||
|
},
|
||||||
|
'comparison-operator': {
|
||||||
|
pattern: /([<>]=?)|(!?=)/,
|
||||||
|
},
|
||||||
|
punctuation: /[{}()`,.]/,
|
||||||
|
whitespace: /\s+/,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default tokenizer;
|
@ -420,3 +420,41 @@ interface MetricMetadataValue {
|
|||||||
name: AzureMonitorLocalizedValue;
|
name: AzureMonitorLocalizedValue;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Category = {
|
||||||
|
displayName: string;
|
||||||
|
id: string;
|
||||||
|
related: {
|
||||||
|
queries: string[];
|
||||||
|
tables: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CheatsheetQuery = {
|
||||||
|
body: string;
|
||||||
|
description: string;
|
||||||
|
displayName: string;
|
||||||
|
id: string;
|
||||||
|
properties: {
|
||||||
|
ExampleQuery: boolean;
|
||||||
|
QueryAttributes: {
|
||||||
|
isMultiResource: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
related: {
|
||||||
|
categories: string[];
|
||||||
|
resourceTypes: string[];
|
||||||
|
tables: string[];
|
||||||
|
};
|
||||||
|
tags: {
|
||||||
|
Topic: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CheatsheetQueries = {
|
||||||
|
[key: string]: CheatsheetQuery[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DropdownCategories = {
|
||||||
|
[key: string]: boolean;
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user