Commit 2a61d7ff by Ivana Huckova Committed by GitHub

IntelliSense: Fix autocomplete and highlighting for Loki, Prometheus, Cloudwatch (#29381)

* Pass languages directly to SlatePrism plugin

* Update

* Remove unused variables

* Update packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>

* Update public/app/plugins/datasource/prometheus/components/PromQueryField.tsx

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>

* Update public/app/plugins/datasource/prometheus/components/PromQueryField.tsx

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>

* Update public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>

* Update public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>

* Try Prism import instead of languages

* Update webpack

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>
parent 3dcfe54d
...@@ -6,7 +6,7 @@ import { SelectionReference } from './SelectionReference'; ...@@ -6,7 +6,7 @@ import { SelectionReference } from './SelectionReference';
import { Portal, getFormStyles } from '../index'; import { Portal, getFormStyles } from '../index';
// @ts-ignore // @ts-ignore
import Prism from 'prismjs'; import Prism, { Grammar, LanguageMap } from 'prismjs';
import { Editor } from '@grafana/slate-react'; import { Editor } from '@grafana/slate-react';
import { Value } from 'slate'; import { Value } from 'slate';
import Plain from 'slate-plain-serializer'; import Plain from 'slate-plain-serializer';
...@@ -27,11 +27,20 @@ interface DataLinkInputProps { ...@@ -27,11 +27,20 @@ interface DataLinkInputProps {
placeholder?: string; placeholder?: string;
} }
const datalinksSyntax: Grammar = {
builtInVariable: {
pattern: /(\${\S+?})/,
},
};
const plugins = [ const plugins = [
SlatePrism({ SlatePrism(
onlyIn: (node: any) => node.type === 'code_block', {
getSyntax: () => 'links', onlyIn: (node: any) => node.type === 'code_block',
}), getSyntax: () => 'links',
},
{ ...(Prism.languages as LanguageMap), links: datalinksSyntax }
),
]; ];
const getStyles = stylesFactory((theme: GrafanaTheme) => ({ const getStyles = stylesFactory((theme: GrafanaTheme) => ({
...@@ -56,19 +65,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => ({ ...@@ -56,19 +65,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => ({
`, `,
})); }));
export const enableDatalinksPrismSyntax = () => {
Prism.languages['links'] = {
builtInVariable: {
pattern: /(\${\S+?})/,
},
};
};
// This memoised also because rerendering the slate editor grabs focus which created problem in some cases this // This memoised also because rerendering the slate editor grabs focus which created problem in some cases this
// was used and changes to different state were propagated here. // was used and changes to different state were propagated here.
export const DataLinkInput: React.FC<DataLinkInputProps> = memo( export const DataLinkInput: React.FC<DataLinkInputProps> = memo(
({ value, onChange, suggestions, placeholder = 'http://your-grafana.com/d/000000010/annotations' }) => { ({ value, onChange, suggestions, placeholder = 'http://your-grafana.com/d/000000010/annotations' }) => {
enableDatalinksPrismSyntax();
const editorRef = useRef<Editor>() as RefObject<Editor>; const editorRef = useRef<Editor>() as RefObject<Editor>;
const theme = useContext(ThemeContext); const theme = useContext(ThemeContext);
const styles = getStyles(theme); const styles = getStyles(theme);
......
import Prism from 'prismjs'; import Prism, { LanguageMap } from 'prismjs';
import { Block, Text, Decoration } from 'slate'; import { Block, Text, Decoration } from 'slate';
import { Plugin } from '@grafana/slate-react'; import { Plugin } from '@grafana/slate-react';
import Options, { OptionsFormat } from './options'; import Options, { OptionsFormat } from './options';
...@@ -19,7 +19,7 @@ export interface Token { ...@@ -19,7 +19,7 @@ export interface Token {
/** /**
* A Slate plugin to highlight code syntax. * A Slate plugin to highlight code syntax.
*/ */
export function SlatePrism(optsParam: OptionsFormat = {}): Plugin { export function SlatePrism(optsParam: OptionsFormat = {}, prismLanguages = Prism.languages as LanguageMap): Plugin {
const opts: Options = new Options(optsParam); const opts: Options = new Options(optsParam);
return { return {
...@@ -30,7 +30,7 @@ export function SlatePrism(optsParam: OptionsFormat = {}): Plugin { ...@@ -30,7 +30,7 @@ export function SlatePrism(optsParam: OptionsFormat = {}): Plugin {
const block = Block.create(node as Block); const block = Block.create(node as Block);
const grammarName = opts.getSyntax(block); const grammarName = opts.getSyntax(block);
const grammar = Prism.languages[grammarName]; const grammar = prismLanguages[grammarName];
if (!grammar) { if (!grammar) {
// Grammar not loaded // Grammar not loaded
......
...@@ -192,7 +192,7 @@ const CLIQ_EXAMPLES: QueryExample[] = [ ...@@ -192,7 +192,7 @@ const CLIQ_EXAMPLES: QueryExample[] = [
]; ];
function renderHighlightedMarkup(code: string, keyPrefix: string) { function renderHighlightedMarkup(code: string, keyPrefix: string) {
const grammar = Prism.languages['cloudwatch'] ?? tokenizer; const grammar = tokenizer;
const tokens = flattenTokens(Prism.tokenize(code, grammar)); const tokens = flattenTokens(Prism.tokenize(code, grammar));
const spans = tokens const spans = tokens
.filter(token => typeof token !== 'string') .filter(token => typeof token !== 'string')
......
...@@ -24,7 +24,7 @@ import syntax from '../syntax'; ...@@ -24,7 +24,7 @@ import syntax from '../syntax';
import { ExploreQueryFieldProps, AbsoluteTimeRange, SelectableValue, AppEvents } from '@grafana/data'; import { ExploreQueryFieldProps, AbsoluteTimeRange, SelectableValue, AppEvents } from '@grafana/data';
import { CloudWatchQuery, CloudWatchLogsQuery } from '../types'; import { CloudWatchQuery, CloudWatchLogsQuery } from '../types';
import { CloudWatchDatasource } from '../datasource'; import { CloudWatchDatasource } from '../datasource';
import Prism, { Grammar } from 'prismjs'; import { Grammar, LanguageMap, languages as prismLanguages } from 'prismjs';
import { CloudWatchLanguageProvider } from '../language_provider'; import { CloudWatchLanguageProvider } from '../language_provider';
import { css } from 'emotion'; import { css } from 'emotion';
import { ExploreId } from 'app/types'; import { ExploreId } from 'app/types';
...@@ -95,13 +95,15 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs ...@@ -95,13 +95,15 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs
constructor(props: CloudWatchLogsQueryFieldProps, context: React.Context<any>) { constructor(props: CloudWatchLogsQueryFieldProps, context: React.Context<any>) {
super(props, context); super(props, context);
Prism.languages['cloudwatch'] = syntax;
this.plugins = [ this.plugins = [
BracesPlugin(), BracesPlugin(),
SlatePrism({ SlatePrism(
onlyIn: (node: Node) => node.object === 'block' && node.type === 'code_block', {
getSyntax: (node: Node) => 'cloudwatch', onlyIn: (node: Node) => node.object === 'block' && node.type === 'code_block',
}), getSyntax: (node: Node) => 'cloudwatch',
},
{ ...(prismLanguages as LanguageMap), cloudwatch: syntax }
),
]; ];
} }
......
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import Prism, { Grammar } from 'prismjs'; import { Grammar } from 'prismjs';
import { AbsoluteTimeRange } from '@grafana/data'; import { AbsoluteTimeRange } from '@grafana/data';
import { useRefMounted } from 'app/core/hooks/useRefMounted'; import { useRefMounted } from 'app/core/hooks/useRefMounted';
import { CloudWatchLanguageProvider } from './language_provider'; import { CloudWatchLanguageProvider } from './language_provider';
const PRISM_SYNTAX = 'cloudwatch';
/** /**
* Initialise the language provider. Returns a languageProviderInitialized boolean cause there does not seem other way * Initialise the language provider. Returns a languageProviderInitialized boolean cause there does not seem other way
* to know if the provider is already initialised or not. By the initialisation it modifies the provided * to know if the provider is already initialised or not. By the initialisation it modifies the provided
...@@ -44,7 +42,6 @@ const useCloudwatchSyntax = (languageProvider: CloudWatchLanguageProvider, langu ...@@ -44,7 +42,6 @@ const useCloudwatchSyntax = (languageProvider: CloudWatchLanguageProvider, langu
useEffect(() => { useEffect(() => {
if (languageProviderInitialized) { if (languageProviderInitialized) {
const syntax = languageProvider.getSyntax(); const syntax = languageProvider.getSyntax();
Prism.languages[PRISM_SYNTAX] = syntax;
setSyntax(syntax); setSyntax(syntax);
} }
}, [languageProviderInitialized, languageProvider]); }, [languageProviderInitialized, languageProvider]);
......
...@@ -20,7 +20,7 @@ import { Plugin, Node } from 'slate'; ...@@ -20,7 +20,7 @@ import { Plugin, Node } from 'slate';
import { DOMUtil } from '@grafana/ui'; import { DOMUtil } from '@grafana/ui';
import { ExploreQueryFieldProps, AbsoluteTimeRange } from '@grafana/data'; import { ExploreQueryFieldProps, AbsoluteTimeRange } from '@grafana/data';
import { LokiQuery, LokiOptions } from '../types'; import { LokiQuery, LokiOptions } from '../types';
import { Grammar } from 'prismjs'; import { Grammar, LanguageMap, languages as prismLanguages } from 'prismjs';
import LokiLanguageProvider, { LokiHistoryItem } from '../language_provider'; import LokiLanguageProvider, { LokiHistoryItem } from '../language_provider';
import LokiDatasource from '../datasource'; import LokiDatasource from '../datasource';
import LokiOptionFields from './LokiOptionFields'; import LokiOptionFields from './LokiOptionFields';
...@@ -82,10 +82,13 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr ...@@ -82,10 +82,13 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
this.plugins = [ this.plugins = [
BracesPlugin(), BracesPlugin(),
SlatePrism({ SlatePrism(
onlyIn: (node: Node) => node.object === 'block' && node.type === 'code_block', {
getSyntax: (node: Node) => 'promql', onlyIn: (node: Node) => node.object === 'block' && node.type === 'code_block',
}), getSyntax: (node: Node) => 'logql',
},
{ ...(prismLanguages as LanguageMap), logql: this.props.datasource.languageProvider.getSyntax() }
),
]; ];
} }
......
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import Prism, { Grammar } from 'prismjs'; import { Grammar } from 'prismjs';
import { AbsoluteTimeRange } from '@grafana/data'; import { AbsoluteTimeRange } from '@grafana/data';
import LokiLanguageProvider from 'app/plugins/datasource/loki/language_provider'; import LokiLanguageProvider from 'app/plugins/datasource/loki/language_provider';
import { useLokiLabels } from 'app/plugins/datasource/loki/components/useLokiLabels'; import { useLokiLabels } from 'app/plugins/datasource/loki/components/useLokiLabels';
import { useRefMounted } from 'app/core/hooks/useRefMounted'; import { useRefMounted } from 'app/core/hooks/useRefMounted';
const PRISM_SYNTAX = 'promql';
/** /**
* Initialise the language provider. Returns a languageProviderInitialized boolean cause there does not seem other way * Initialise the language provider. Returns a languageProviderInitialized boolean cause there does not seem other way
* to know if the provider is already initialised or not. By the initialisation it modifies the provided * to know if the provider is already initialised or not. By the initialisation it modifies the provided
...@@ -45,7 +43,6 @@ const useLokiSyntax = (languageProvider: LokiLanguageProvider, languageProviderI ...@@ -45,7 +43,6 @@ const useLokiSyntax = (languageProvider: LokiLanguageProvider, languageProviderI
useEffect(() => { useEffect(() => {
if (languageProviderInitialized) { if (languageProviderInitialized) {
const syntax = languageProvider.getSyntax(); const syntax = languageProvider.getSyntax();
Prism.languages[PRISM_SYNTAX] = syntax;
setSyntax(syntax); setSyntax(syntax);
} }
}, [languageProviderInitialized, languageProvider]); }, [languageProviderInitialized, languageProvider]);
......
...@@ -7,7 +7,11 @@ import { PromQuery } from '../types'; ...@@ -7,7 +7,11 @@ import { PromQuery } from '../types';
import { LoadingState, PanelData, toUtc, TimeRange } from '@grafana/data'; import { LoadingState, PanelData, toUtc, TimeRange } from '@grafana/data';
const setup = (renderMethod: any, propOverrides?: object) => { const setup = (renderMethod: any, propOverrides?: object) => {
const datasourceMock: unknown = {}; const datasourceMock: unknown = {
languageProvider: {
syntax: () => {},
},
};
const datasource: PrometheusDatasource = datasourceMock as PrometheusDatasource; const datasource: PrometheusDatasource = datasourceMock as PrometheusDatasource;
const onRunQuery = jest.fn(); const onRunQuery = jest.fn();
const onChange = jest.fn(); const onChange = jest.fn();
......
...@@ -17,6 +17,7 @@ describe('PromQueryField', () => { ...@@ -17,6 +17,7 @@ describe('PromQueryField', () => {
const datasource = ({ const datasource = ({
languageProvider: { languageProvider: {
start: () => Promise.resolve([]), start: () => Promise.resolve([]),
syntax: () => {},
}, },
} as unknown) as DataSourceInstanceSettings<PromOptions>; } as unknown) as DataSourceInstanceSettings<PromOptions>;
...@@ -35,10 +36,16 @@ describe('PromQueryField', () => { ...@@ -35,10 +36,16 @@ describe('PromQueryField', () => {
}); });
it('renders a disabled metrics chooser if lookups are disabled in datasource settings', () => { it('renders a disabled metrics chooser if lookups are disabled in datasource settings', () => {
const datasource = ({
languageProvider: {
start: () => Promise.resolve([]),
syntax: () => {},
},
} as unknown) as DataSourceInstanceSettings<PromOptions>;
const queryField = render( const queryField = render(
<PromQueryField <PromQueryField
// @ts-ignore // @ts-ignore
datasource={{ lookupsDisabled: true }} datasource={{ ...datasource, lookupsDisabled: true }}
query={{ expr: '', refId: '' }} query={{ expr: '', refId: '' }}
onRunQuery={() => {}} onRunQuery={() => {}}
onChange={() => {}} onChange={() => {}}
......
...@@ -12,7 +12,7 @@ import { ...@@ -12,7 +12,7 @@ import {
BracesPlugin, BracesPlugin,
} from '@grafana/ui'; } from '@grafana/ui';
import Prism from 'prismjs'; import { LanguageMap, languages as prismLanguages } from 'prismjs';
// dom also includes Element polyfills // dom also includes Element polyfills
import { PromQuery, PromOptions, PromMetricsMetadata } from '../types'; import { PromQuery, PromOptions, PromMetricsMetadata } from '../types';
...@@ -22,7 +22,6 @@ import { DOMUtil, SuggestionsState } from '@grafana/ui'; ...@@ -22,7 +22,6 @@ import { DOMUtil, SuggestionsState } from '@grafana/ui';
import { PrometheusDatasource } from '../datasource'; import { PrometheusDatasource } from '../datasource';
const HISTOGRAM_GROUP = '__histograms__'; const HISTOGRAM_GROUP = '__histograms__';
const PRISM_SYNTAX = 'promql';
export const RECORDING_RULES_GROUP = '__recording_rules__'; export const RECORDING_RULES_GROUP = '__recording_rules__';
function getChooserText(metricsLookupDisabled: boolean, hasSyntax: boolean, metrics: string[]) { function getChooserText(metricsLookupDisabled: boolean, hasSyntax: boolean, metrics: string[]) {
...@@ -133,10 +132,13 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF ...@@ -133,10 +132,13 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
this.plugins = [ this.plugins = [
BracesPlugin(), BracesPlugin(),
SlatePrism({ SlatePrism(
onlyIn: (node: any) => node.type === 'code_block', {
getSyntax: (node: any) => 'promql', onlyIn: (node: any) => node.type === 'code_block',
}), getSyntax: (node: any) => 'promql',
},
{ ...(prismLanguages as LanguageMap), promql: this.props.datasource.languageProvider.syntax }
),
]; ];
this.state = { this.state = {
...@@ -222,7 +224,6 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF ...@@ -222,7 +224,6 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
datasource: { languageProvider }, datasource: { languageProvider },
} = this.props; } = this.props;
Prism.languages[PRISM_SYNTAX] = languageProvider.syntax;
this.languageProviderInitializationPromise = makePromiseCancelable(languageProvider.start()); this.languageProviderInitializationPromise = makePromiseCancelable(languageProvider.start());
try { try {
......
...@@ -45,7 +45,13 @@ exports[`PromExploreQueryEditor should render component 1`] = ` ...@@ -45,7 +45,13 @@ exports[`PromExploreQueryEditor should render component 1`] = `
}, },
} }
} }
datasource={Object {}} datasource={
Object {
"languageProvider": Object {
"syntax": [Function],
},
}
}
history={Array []} history={Array []}
onBlur={[Function]} onBlur={[Function]}
onChange={[MockFunction]} onChange={[MockFunction]}
......
import { Grammar } from 'prismjs';
import { CompletionItem } from '@grafana/ui'; import { CompletionItem } from '@grafana/ui';
export const RATE_RANGES: CompletionItem[] = [ export const RATE_RANGES: CompletionItem[] = [
...@@ -376,7 +377,7 @@ export const FUNCTIONS = [ ...@@ -376,7 +377,7 @@ export const FUNCTIONS = [
}, },
]; ];
const tokenizer = { const tokenizer: Grammar = {
comment: { comment: {
pattern: /#.*/, pattern: /#.*/,
}, },
......
...@@ -48,6 +48,9 @@ module.exports = { ...@@ -48,6 +48,9 @@ module.exports = {
// storybook v6 bump caused the app to bundle multiple versions of react breaking hooks // storybook v6 bump caused the app to bundle multiple versions of react breaking hooks
// make sure to resolve only from the project: https://github.com/facebook/react/issues/13991#issuecomment-435587809 // make sure to resolve only from the project: https://github.com/facebook/react/issues/13991#issuecomment-435587809
react: path.resolve(__dirname, '../../node_modules/react'), react: path.resolve(__dirname, '../../node_modules/react'),
// some of data source pluginis use global Prism object to add the language definition
// we want to have same Prism object in core and in grafana/ui
prismjs: path.resolve(__dirname, '../../node_modules/prismjs'),
}, },
modules: [ modules: [
'node_modules', 'node_modules',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment