diff --git a/public/app/features/alerting/unified/components/receivers/TemplateData.ts b/public/app/features/alerting/unified/components/receivers/TemplateData.ts new file mode 100644 index 00000000000..4f00e4931e0 --- /dev/null +++ b/public/app/features/alerting/unified/components/receivers/TemplateData.ts @@ -0,0 +1,149 @@ +export interface TemplateDataItem { + name: string; + type: 'string' | '[]Alert' | 'KeyValue' | 'time.Time'; + notes: string; +} + +interface TemplateFunctionItem { + name: string; + args?: '[]string'; + returns: 'KeyValue' | '[]string'; + notes?: string; +} + +export const GlobalTemplateData: TemplateDataItem[] = [ + { + name: 'Receiver', + type: 'string', + notes: 'Name of the contact point that the notification is being sent to.', + }, + { + name: 'Status', + type: 'string', + notes: 'firing if at least one alert is firing, otherwise resolved', + }, + { + name: 'Alerts', + type: '[]Alert', + notes: 'List of alert objects that are included in this notification.', + }, + { + name: 'Alerts.Firing', + type: '[]Alert', + notes: 'List of firing alerts', + }, + { + name: 'Alerts.Resolved', + type: '[]Alert', + notes: 'List of resolved alerts', + }, + { + name: 'GroupLabels', + type: 'KeyValue', + notes: 'Labels these alerts were grouped by.', + }, + { + name: 'CommonLabels', + type: 'KeyValue', + notes: 'Labels common to all the alerts included in this notification.', + }, + { + name: 'CommonAnnotations', + type: 'KeyValue', + notes: 'Annotations common to all the alerts included in this notification.', + }, + { + name: 'ExternalURL', + type: 'string', + notes: 'Back link to the Grafana that sent the notification.', + }, +]; + +export const AlertTemplateData: TemplateDataItem[] = [ + { + name: 'Status', + type: 'string', + notes: 'firing or resolved.', + }, + { + name: 'Labels', + type: 'KeyValue', + notes: 'Set of labels attached to the alert.', + }, + { + name: 'Annotations', + type: 'KeyValue', + notes: 'Set of annotations attached to the alert.', + }, + { + name: 'StartsAt', + type: 'time.Time', + notes: 'Time the alert started firing.', + }, + { + name: 'EndsAt', + type: 'time.Time', + notes: + 'Only set if the end time of an alert is known. Otherwise set to a configurable timeout period from the time since the last alert was received.', + }, + { + name: 'GeneratorURL', + type: 'string', + notes: 'A back link to Grafana or external Alertmanager.', + }, + { + name: 'SilenceURL', + type: 'string', + notes: 'Link to Grafana silence for with labels for this alert pre-filled. Only for Grafana managed alerts.', + }, + { + name: 'DashboardURL', + type: 'string', + notes: 'Link to Grafana dashboard, if alert rule belongs to one. Only for Grafana managed alerts.', + }, + { + name: 'PanelURL', + type: 'string', + notes: 'Link to Grafana dashboard panel, if alert rule belongs to one. Only for Grafana managed alerts.', + }, + { + name: 'Fingerprint', + type: 'string', + notes: 'Fingerprint that can be used to identify the alert.', + }, + { + name: 'ValueString', + type: 'string', + notes: 'String that contains the labels and value of each reduced expression in the alert.', + }, +]; + +export const KeyValueTemplateFunctions: TemplateFunctionItem[] = [ + { + name: 'SortedPairs', + returns: 'KeyValue', + notes: 'Returns sorted list of key & value string pairs', + }, + { + name: 'Remove', + args: '[]string', + returns: 'KeyValue', + notes: 'Returns a copy of the Key/Value map without the given keys.', + }, + { + name: 'Names', + returns: '[]string', + notes: 'List of label names', + }, + { + name: 'Values', + returns: '[]string', + notes: 'List of label values', + }, +]; + +export const KeyValueCodeSnippet = `{ + "summary": "alert summary", + "description": "alert description" +} +`; diff --git a/public/app/features/alerting/unified/components/receivers/TemplateDataDocs.tsx b/public/app/features/alerting/unified/components/receivers/TemplateDataDocs.tsx new file mode 100644 index 00000000000..808faab9012 --- /dev/null +++ b/public/app/features/alerting/unified/components/receivers/TemplateDataDocs.tsx @@ -0,0 +1,164 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Stack, useStyles2 } from '@grafana/ui'; + +import { HoverCard } from '../HoverCard'; + +import { + AlertTemplateData, + GlobalTemplateData, + KeyValueCodeSnippet, + KeyValueTemplateFunctions, + TemplateDataItem, +} from './TemplateData'; + +export function TemplateDataDocs() { + const styles = useStyles2(getTemplateDataDocsStyles); + + const AlertTemplateDataTable = ( + + Alert template data Available only when in the context of an Alert (e.g. inside .Alerts loop) + + } + dataItems={AlertTemplateData} + /> + ); + + return ( + + Template Data} + dataItems={GlobalTemplateData} + typeRenderer={(type) => + type === '[]Alert' ? ( + +
{type}
+
+ ) : type === 'KeyValue' ? ( + }> +
{type}
+
+ ) : ( + type + ) + } + /> +
+ ); +} + +const getTemplateDataDocsStyles = (theme: GrafanaTheme2) => ({ + header: css` + color: ${theme.colors.text.primary}; + + span { + color: ${theme.colors.text.secondary}; + font-size: ${theme.typography.bodySmall.fontSize}; + } + `, + interactiveType: css` + color: ${theme.colors.text.link}; + `, +}); + +interface TemplateDataTableProps { + dataItems: TemplateDataItem[]; + caption: JSX.Element | string; + typeRenderer?: (type: TemplateDataItem['type']) => React.ReactNode; +} + +function TemplateDataTable({ dataItems, caption, typeRenderer }: TemplateDataTableProps) { + const styles = useStyles2(getTemplateDataTableStyles); + + return ( + + + + + + + + + + + {dataItems.map(({ name, type, notes }, index) => ( + + + + + + ))} + +
{caption}
NameTypeNotes
{name}{typeRenderer ? typeRenderer(type) : type}{notes}
+ ); +} + +function KeyValueTemplateDataTable() { + const tableStyles = useStyles2(getTemplateDataTableStyles); + + return ( +
+ KeyValue is a set of key/value string pairs that represent labels and annotations. +
+        {KeyValueCodeSnippet}
+      
+ + + + + + + + + + + + {KeyValueTemplateFunctions.map(({ name, args, returns, notes }) => ( + + + + + + + ))} + +
Key-value methods
NameArgumentsReturnsNotes
{name}{args}{returns}{notes}
+
+ ); +} + +const getTemplateDataTableStyles = (theme: GrafanaTheme2) => ({ + table: css` + border-collapse: collapse; + width: 100%; + + caption { + caption-side: top; + } + + td, + th { + padding: ${theme.spacing(1, 1)}; + } + + thead { + font-weight: ${theme.typography.fontWeightBold}; + } + + tbody tr:nth-child(2n + 1) { + background-color: ${theme.colors.background.secondary}; + } + + tbody td:nth-child(1) { + font-weight: ${theme.typography.fontWeightBold}; + } + + tbody td:nth-child(2) { + font-style: italic; + } + `, +}); diff --git a/public/app/features/alerting/unified/components/receivers/TemplateEditor.tsx b/public/app/features/alerting/unified/components/receivers/TemplateEditor.tsx index 556b29b2ac5..1ada702eecf 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplateEditor.tsx +++ b/public/app/features/alerting/unified/components/receivers/TemplateEditor.tsx @@ -3,25 +3,23 @@ * * It includes auto-complete for template data and syntax highlighting */ -import { editor } from 'monaco-editor'; -import React, { FC } from 'react'; +import { editor, IDisposable } from 'monaco-editor'; +import React, { FC, useEffect, useRef } from 'react'; import { CodeEditor } from '@grafana/ui'; import { CodeEditorProps } from '@grafana/ui/src/components/Monaco/types'; +import { registerGoTemplateAutocomplete } from './editor/autocomplete'; import goTemplateLanguageDefinition, { GO_TEMPLATE_LANGUAGE_ID } from './editor/definition'; import { registerLanguage } from './editor/register'; -const getSuggestions = () => { - return []; -}; - type TemplateEditorProps = Omit & { autoHeight?: boolean; }; const TemplateEditor: FC = (props) => { const shouldAutoHeight = Boolean(props.autoHeight); + const disposeSuggestions = useRef(null); const onEditorDidMount = (editor: editor.IStandaloneCodeEditor) => { if (shouldAutoHeight) { @@ -35,15 +33,21 @@ const TemplateEditor: FC = (props) => { } }; + useEffect(() => { + return () => { + disposeSuggestions.current?.dispose(); + }; + }, []); + return ( { registerLanguage(monaco, goTemplateLanguageDefinition); + disposeSuggestions.current = registerGoTemplateAutocomplete(monaco); }} language={GO_TEMPLATE_LANGUAGE_ID} /> diff --git a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx index 2535b08010d..efb7ed39027 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx @@ -4,7 +4,7 @@ import { useForm, Validate } from 'react-hook-form'; import AutoSizer from 'react-virtualized-auto-sizer'; import { GrafanaTheme2 } from '@grafana/data'; -import { Alert, Button, Field, FieldSet, Input, LinkButton, useStyles2 } from '@grafana/ui'; +import { Alert, Button, Field, FieldSet, Input, LinkButton, Stack, useStyles2 } from '@grafana/ui'; import { useCleanup } from 'app/core/hooks/useCleanup'; import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; import { useDispatch } from 'app/types'; @@ -16,7 +16,9 @@ import { initialAsyncRequestState } from '../../utils/redux'; import { ensureDefine } from '../../utils/templates'; import { ProvisionedResource, ProvisioningAlert } from '../Provisioning'; +import { TemplateDataDocs } from './TemplateDataDocs'; import { TemplateEditor } from './TemplateEditor'; +import { snippets } from './editor/templateDataSuggestions'; interface Values { name: string; @@ -121,77 +123,104 @@ export const TemplateForm: FC = ({ existing, alertManagerSourceName, conf autoFocus={true} /> - - You can use the{' '} - - Go templating language - - .{' '} - - More info about alertmanager templates - - - } - label="Content" - error={errors?.content?.message} - invalid={!!errors.content?.message} - required - > -
- - {({ width, height }) => ( - setValue('content', value)} - /> + +
+
+ +
+ + {({ width, height }) => ( + setValue('content', value)} + /> + )} + +
+
+
+ {loading && ( + )} - + {!loading && ( + + )} + + Cancel + +
- -
- {loading && ( - - )} - {!loading && ( - - )} - - Cancel - +
); }; +function TemplatingGuideline() { + const styles = useStyles2(getStyles); + + return ( + + +
+ Grafana uses Go templating language to create notification messages. +
+ To find out more about templating please visit our documentation. +
+
+ + Templating documentation + +
+
+ +
+ To make templating easier, we provide a few snippets in the content editor to help you speed up your workflow. +
+ {Object.values(snippets) + .map((s) => s.label) + .join(', ')} +
+
+
+ ); +} + const getStyles = (theme: GrafanaTheme2) => ({ - externalLink: css` + contentContainer: css` + display: flex; + gap: ${theme.spacing(2)}; + flex-direction: row; + align-items: flex-start; + flex-wrap: wrap; + ${theme.breakpoints.up('xxl')} { + flex-wrap: nowrap; + } + `, + snippets: css` + margin-top: ${theme.spacing(2)}; + font-size: ${theme.typography.bodySmall.fontSize}; + `, + code: css` color: ${theme.colors.text.secondary}; - text-decoration: underline; + font-weight: ${theme.typography.fontWeightBold}; `, buttons: css` & > * + * { diff --git a/public/app/features/alerting/unified/components/receivers/editor/alertManagerSuggestions.ts b/public/app/features/alerting/unified/components/receivers/editor/alertManagerSuggestions.ts new file mode 100644 index 00000000000..4a217a10cac --- /dev/null +++ b/public/app/features/alerting/unified/components/receivers/editor/alertManagerSuggestions.ts @@ -0,0 +1,52 @@ +import type { Monaco } from '@grafana/ui'; + +import { AlertmanagerTemplateFunction } from './language'; +import { SuggestionDefinition } from './suggestionDefinition'; + +export function getAlertManagerSuggestions(monaco: Monaco): SuggestionDefinition[] { + const kind = monaco.languages.CompletionItemKind.Function; + return [ + { + label: AlertmanagerTemplateFunction.toUpper, + detail: 'function(s string)', + kind, + }, + { + label: AlertmanagerTemplateFunction.toLower, + detail: 'function(s string)', + kind, + }, + { + label: AlertmanagerTemplateFunction.title, + documentation: 'Capitalizes the first letter of each word', + detail: 'function(s string)', + kind, + }, + { + label: AlertmanagerTemplateFunction.join, + documentation: { value: 'Joins an array of strings using the separator provided.' }, + detail: 'function(separator string, s []string)', + kind, + }, + { + label: AlertmanagerTemplateFunction.match, + detail: 'function', + kind, + }, + { + label: AlertmanagerTemplateFunction.safeHtml, + detail: 'function(pattern, repl, text)', + kind, + }, + { + label: AlertmanagerTemplateFunction.reReplaceAll, + detail: 'function(pattern, repl, text)', + kind, + }, + { + label: AlertmanagerTemplateFunction.stringSlice, + detail: 'function(s ...string)', + kind, + }, + ]; +} diff --git a/public/app/features/alerting/unified/components/receivers/editor/autocomplete.ts b/public/app/features/alerting/unified/components/receivers/editor/autocomplete.ts new file mode 100644 index 00000000000..a844123d5d8 --- /dev/null +++ b/public/app/features/alerting/unified/components/receivers/editor/autocomplete.ts @@ -0,0 +1,119 @@ +import { concat } from 'lodash'; +import type { languages, editor, Position, IRange, IDisposable } from 'monaco-editor/esm/vs/editor/editor.api'; + +import type { Monaco } from '@grafana/ui'; + +import { getAlertManagerSuggestions } from './alertManagerSuggestions'; +import { SuggestionDefinition } from './suggestionDefinition'; +import { + getAlertsSuggestions, + getAlertSuggestions, + getGlobalSuggestions, + getKeyValueSuggestions, + getSnippetsSuggestions, +} from './templateDataSuggestions'; + +export function registerGoTemplateAutocomplete(monaco: Monaco): IDisposable { + const goTemplateAutocompleteProvider: languages.CompletionItemProvider = { + triggerCharacters: ['.'], + provideCompletionItems(model, position, context): languages.ProviderResult { + const word = model.getWordUntilPosition(position); + const range = { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: word.startColumn, + endColumn: word.endColumn, + }; + + const completionProvider = new CompletionProvider(monaco, range); + + const insideExpression = isInsideGoExpression(model, position); + if (!insideExpression) { + return completionProvider.getSnippetsSuggestions(); + } + + if (context.triggerKind === monaco.languages.CompletionTriggerKind.Invoke && !context.triggerCharacter) { + return completionProvider.getFunctionsSuggestions(); + } + + const wordBeforeDot = model.getWordUntilPosition({ + lineNumber: position.lineNumber, + column: position.column - 1, + }); + + return completionProvider.getTemplateDataSuggestions(wordBeforeDot.word); + }, + }; + + return monaco.languages.registerCompletionItemProvider('go-template', goTemplateAutocompleteProvider); +} + +function isInsideGoExpression(model: editor.ITextModel, position: Position) { + const searchRange = { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: model.getLineMinColumn(position.lineNumber), + endColumn: model.getLineMaxColumn(position.lineNumber), + }; + + const goSyntaxRegex = '\\{\\{[a-zA-Z0-9._() "]+\\}\\}'; + const matches = model.findMatches(goSyntaxRegex, searchRange, true, false, null, true); + + return matches.some((match) => match.range.containsPosition(position)); +} + +export class CompletionProvider { + constructor(private readonly monaco: Monaco, private readonly range: IRange) {} + + getSnippetsSuggestions = (): languages.ProviderResult => { + return this.getCompletionsFromDefinitions(getSnippetsSuggestions(this.monaco)); + }; + + getFunctionsSuggestions = (): languages.ProviderResult => { + return this.getCompletionsFromDefinitions(getAlertManagerSuggestions(this.monaco)); + }; + + getTemplateDataSuggestions = (wordContext: string): languages.ProviderResult => { + switch (wordContext) { + case '': + return this.getCompletionsFromDefinitions(getGlobalSuggestions(this.monaco), getAlertSuggestions(this.monaco)); + case 'Alerts': + return this.getCompletionsFromDefinitions(getAlertsSuggestions(this.monaco)); + case 'GroupLabels': + case 'CommonLabels': + case 'CommonAnnotations': + case 'Labels': + case 'Annotations': + return this.getCompletionsFromDefinitions(getKeyValueSuggestions(this.monaco)); + default: + return { suggestions: [] }; + } + }; + + private getCompletionsFromDefinitions = (...args: SuggestionDefinition[][]): languages.CompletionList => { + const allDefinitions = concat(...args); + + return { + suggestions: allDefinitions.map((definition) => buildAutocompleteSuggestion(definition, this.range)), + }; + }; +} + +function buildAutocompleteSuggestion( + { label, detail, documentation, kind, insertText }: SuggestionDefinition, + range: IRange +): languages.CompletionItem { + const insertFallback = typeof label === 'string' ? label : label.label; + const labelObject = typeof label === 'string' ? { label: label, description: detail } : { ...label }; + + labelObject.description ??= detail; + + return { + label: labelObject, + kind: kind, + insertText: insertText ?? insertFallback, + range, + documentation: documentation, + detail: detail, + }; +} diff --git a/public/app/features/alerting/unified/components/receivers/editor/language.ts b/public/app/features/alerting/unified/components/receivers/editor/language.ts index 04418d052d0..c62e9df4eaa 100644 --- a/public/app/features/alerting/unified/components/receivers/editor/language.ts +++ b/public/app/features/alerting/unified/components/receivers/editor/language.ts @@ -15,16 +15,18 @@ enum TokenType { // list of available functions in Alertmanager templates // see https://cs.github.com/prometheus/alertmanager/blob/805e505288ce82c3e2b625a3ca63aaf2b0aa9cea/template/template.go?q=join#L132-L151 -export const availableAlertManagerFunctions = [ - 'toUpper', - 'toLower', - 'title', - 'join', - 'match', - 'safeHtml', - 'reReplaceAll', - 'stringSlice', -]; +export enum AlertmanagerTemplateFunction { + toUpper = 'toUpper', + toLower = 'toLower', + title = 'title', + join = 'join', + match = 'match', + safeHtml = 'safeHtml', + reReplaceAll = 'reReplaceAll', + stringSlice = 'stringSlice', +} + +export const availableAlertManagerFunctions = Object.values(AlertmanagerTemplateFunction); // boolean functions const booleanFunctions = ['eq', 'ne', 'lt', 'le', 'gt', 'ge']; @@ -77,7 +79,7 @@ export const language: monacoType.languages.IMonarchLanguage = { [/{{-?/, TokenType.Delimiter], [/-?}}/, TokenType.Delimiter], // variables - // [/\.([A-Za-z]+)?/, TokenType.Variable], + [/\.([A-Za-z]+)?/, TokenType.Variable], // identifiers and keywords [ /[a-zA-Z_]\w*/, diff --git a/public/app/features/alerting/unified/components/receivers/editor/snippets.ts b/public/app/features/alerting/unified/components/receivers/editor/snippets.ts new file mode 100644 index 00000000000..4038eb9857f --- /dev/null +++ b/public/app/features/alerting/unified/components/receivers/editor/snippets.ts @@ -0,0 +1,42 @@ +export const alertsLoopSnippet = ` +{{ range .Alerts }} + Status: {{ .Status }} + Starts at: {{ .StartsAt }} +{{ end }} +`; + +export const alertDetailsSnippet = ` +[{{.Status}}] {{ .Labels.alertname }} + +Labels: +{{ range .Labels.SortedPairs }} + {{ .Name }}: {{ .Value }} +{{ end }} + +{{ if gt (len .Annotations) 0 }} +Annotations: +{{ range .Annotations.SortedPairs }} + {{ .Name }}: {{ .Value }} +{{ end }} +{{ end }} + +{{ if gt (len .SilenceURL ) 0 }} + Silence alert: {{ .SilenceURL }} +{{ end }} +{{ if gt (len .DashboardURL ) 0 }} + Go to dashboard: {{ .DashboardURL }} +{{ end }} +`; + +export const groupLabelsLoopSnippet = getKeyValueTemplate('GroupLabels'); +export const commonLabelsLoopSnippet = getKeyValueTemplate('CommonLabels'); +export const commonAnnotationsLoopSnippet = getKeyValueTemplate('CommonAnnotations'); +export const labelsLoopSnippet = getKeyValueTemplate('Labels'); +export const annotationsLoopSnippet = getKeyValueTemplate('Annotations'); + +function getKeyValueTemplate(arrayName: string) { + return ` +{{ range .${arrayName} }} + {{ .Name }} = {{ .Value }} +{{ end }}`; +} diff --git a/public/app/features/alerting/unified/components/receivers/editor/suggestionDefinition.ts b/public/app/features/alerting/unified/components/receivers/editor/suggestionDefinition.ts new file mode 100644 index 00000000000..bbe4e7a5412 --- /dev/null +++ b/public/app/features/alerting/unified/components/receivers/editor/suggestionDefinition.ts @@ -0,0 +1,5 @@ +import { languages } from 'monaco-editor'; + +export interface SuggestionDefinition extends Omit { + insertText?: languages.CompletionItem['insertText']; +} diff --git a/public/app/features/alerting/unified/components/receivers/editor/templateDataSuggestions.ts b/public/app/features/alerting/unified/components/receivers/editor/templateDataSuggestions.ts new file mode 100644 index 00000000000..6dedd8f9fe3 --- /dev/null +++ b/public/app/features/alerting/unified/components/receivers/editor/templateDataSuggestions.ts @@ -0,0 +1,235 @@ +import type { Monaco } from '@grafana/ui'; + +import { + alertDetailsSnippet, + alertsLoopSnippet, + annotationsLoopSnippet, + commonAnnotationsLoopSnippet, + commonLabelsLoopSnippet, + groupLabelsLoopSnippet, + labelsLoopSnippet, +} from './snippets'; +import { SuggestionDefinition } from './suggestionDefinition'; + +// Suggestions available at the top level of a template +export function getGlobalSuggestions(monaco: Monaco): SuggestionDefinition[] { + const kind = monaco.languages.CompletionItemKind.Field; + + return [ + { + label: 'Alerts', + kind, + detail: 'Alert[]', + documentation: { value: 'An Array containing all alerts' }, + }, + { label: 'Receiver', kind, detail: 'string' }, + { label: 'Status', kind, detail: 'string' }, + { label: 'GroupLabels', kind, detail: '[]KeyValue' }, + { label: 'CommonLabels', kind, detail: '[]KeyValue' }, + { label: 'CommonAnnotations', kind, detail: '[]KeyValue' }, + { label: 'ExternalURL', kind, detail: 'string' }, + ]; +} + +// Suggestions that are valid only in the scope of an alert (e.g. in the .Alerts loop) +export function getAlertSuggestions(monaco: Monaco): SuggestionDefinition[] { + const kind = monaco.languages.CompletionItemKind.Field; + + return [ + { + label: { label: 'Status', detail: '(Alert)', description: 'string' }, + kind, + detail: 'string', + documentation: { value: 'Status of the alert. It can be `firing` or `resolved`' }, + }, + { + label: { label: 'Labels', detail: '(Alert)' }, + kind, + detail: '[]KeyValue', + documentation: { value: 'A set of labels attached to the alert.' }, + }, + { + label: { label: 'Annotations', detail: '(Alert)' }, + kind, + detail: '[]KeyValue', + documentation: 'A set of annotations attached to the alert.', + }, + { + label: { label: 'StartsAt', detail: '(Alert)' }, + kind, + detail: 'time.Time', + documentation: 'Time the alert started firing.', + }, + { + label: { label: 'EndsAt', detail: '(Alert)' }, + kind, + detail: 'time.Time', + documentation: + 'Only set if the end time of an alert is known. Otherwise set to a configurable timeout period from the time since the last alert was received.', + }, + { + label: { label: 'GeneratorURL', detail: '(Alert)' }, + kind, + detail: 'string', + documentation: 'Back link to Grafana or external Alertmanager.', + }, + { + label: { label: 'SilenceURL', detail: '(Alert)' }, + kind, + detail: 'string', + documentation: + 'Link to Grafana silence for with labels for this alert pre-filled. Only for Grafana managed alerts.', + }, + { + label: { label: 'DashboardURL', detail: '(Alert)' }, + kind, + detail: 'string', + documentation: 'Link to Grafana dashboard, if alert rule belongs to one. Only for Grafana managed alerts.', + }, + { + label: { label: 'PanelURL', detail: '(Alert)' }, + kind, + detail: 'string', + documentation: 'Link to Grafana dashboard panel, if alert rule belongs to one. Only for Grafana managed alerts.', + }, + { + label: { label: 'Fingerprint', detail: '(Alert)' }, + kind, + detail: 'string', + documentation: 'Fingerprint that can be used to identify the alert.', + }, + { + label: { label: 'ValueString', detail: '(Alert)' }, + kind, + detail: 'string', + documentation: 'String that contains labels and values of each reduced expression in the alert.', + }, + ]; +} + +// Suggestions for .Alerts +export function getAlertsSuggestions(monaco: Monaco): SuggestionDefinition[] { + const kind = monaco.languages.CompletionItemKind.Field; + + return [ + { label: 'Firing', kind, detail: 'Alert[]' }, + { label: 'Resolved', kind, detail: 'Alert[]' }, + ]; +} + +// Suggestions for the KeyValue types +export function getKeyValueSuggestions(monaco: Monaco): SuggestionDefinition[] { + const kind = monaco.languages.CompletionItemKind.Field; + + return [ + { label: 'SortedPairs', kind, detail: '[]KeyValue' }, + { label: 'Names', kind, detail: '[]string' }, + { label: 'Values', kind, detail: '[]string' }, + { + label: 'Remove', + detail: 'KeyValue[] function(keys []string)', + kind: monaco.languages.CompletionItemKind.Method, + }, + ]; +} + +export const snippets = { + alerts: { + label: 'alertsloop', + description: 'Renders a loop through alerts', + snippet: alertsLoopSnippet, + }, + alertDetails: { + label: 'alertdetails', + description: 'Renders all information available about the alert', + snippet: alertDetailsSnippet, + }, + groupLabels: { + label: 'grouplabelsloop', + description: 'Renders a loop through group labels', + snippet: groupLabelsLoopSnippet, + }, + commonLabels: { + label: 'commonlabelsloop', + description: 'Renders a loop through common labels', + snippet: commonLabelsLoopSnippet, + }, + commonAnnotations: { + label: 'commonannotationsloop', + description: 'Renders a loop through common annotations', + snippet: commonAnnotationsLoopSnippet, + }, + labels: { + label: 'labelsloop', + description: 'Renders a loop through labels', + snippet: labelsLoopSnippet, + }, + annotations: { + label: 'annotationsloop', + description: 'Renders a loop through annotations', + snippet: annotationsLoopSnippet, + }, +}; + +// Snippets +export function getSnippetsSuggestions(monaco: Monaco): SuggestionDefinition[] { + const snippetKind = monaco.languages.CompletionItemKind.Snippet; + const snippetInsertRule = monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet; + + const { alerts, alertDetails, groupLabels, commonLabels, commonAnnotations, labels, annotations } = snippets; + + return [ + { + label: alerts.label, + documentation: alerts.description, + kind: snippetKind, + insertText: alerts.snippet, + insertTextRules: snippetInsertRule, + }, + { + label: { + label: alertDetails.label, + detail: '(Alert)', + }, + documentation: alertDetails.description, + kind: snippetKind, + insertText: alertDetails.snippet, + insertTextRules: snippetInsertRule, + }, + { + label: groupLabels.label, + documentation: groupLabels.description, + kind: snippetKind, + insertText: groupLabels.snippet, + insertTextRules: snippetInsertRule, + }, + { + label: commonLabels.label, + documentation: commonLabels.description, + kind: snippetKind, + insertText: commonLabels.snippet, + insertTextRules: snippetInsertRule, + }, + { + label: commonAnnotations.label, + documentation: commonAnnotations.description, + kind: snippetKind, + insertText: commonAnnotations.snippet, + insertTextRules: snippetInsertRule, + }, + { + label: { label: labels.label, detail: '(Alert)' }, + documentation: labels.description, + kind: snippetKind, + insertText: labels.snippet, + insertTextRules: snippetInsertRule, + }, + { + label: { label: annotations.label, detail: '(Alert)' }, + documentation: annotations.description, + kind: snippetKind, + insertText: annotations.snippet, + insertTextRules: snippetInsertRule, + }, + ]; +}