Alerting: app specific query editors for Loki and Prometheus (#34365)

* Adding simplified version of query editor based on app flag.

* cleaned up the absolute time range.

* changing placeholder text.

* updated snapshot.

* added some tests.

* adding loki query editor tests.

* updating snapshots.
This commit is contained in:
Marcus Andersson 2021-05-19 16:24:27 +02:00 committed by GitHub
parent f48708bb9e
commit 8ddf6de6e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 333 additions and 68 deletions

View File

@ -7,6 +7,7 @@ export enum CoreApp {
Dashboard = 'dashboard', Dashboard = 'dashboard',
Explore = 'explore', Explore = 'explore',
Unknown = 'unknown', Unknown = 'unknown',
CloudAlerting = 'cloud-alerting',
} }
export interface AppRootProps<T = KeyValue> { export interface AppRootProps<T = KeyValue> {

View File

@ -367,6 +367,7 @@ export interface QueryEditorProps<
exploreId?: any; exploreId?: any;
history?: HistoryItem[]; history?: HistoryItem[];
queries?: DataQuery[]; queries?: DataQuery[];
app?: CoreApp;
} }
// TODO: not really needed but used as type in some data sources and in DataQueryRequest // TODO: not really needed but used as type in some data sources and in DataQueryRequest

View File

@ -1,5 +1,10 @@
import { TextArea } from '@grafana/ui'; import React, { FC, useCallback, useMemo, useState } from 'react';
import React, { FC } from 'react'; import { noop } from 'lodash';
import { CoreApp, DataQuery } from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime';
import { useAsync } from 'react-use';
import { PromQuery } from 'app/plugins/datasource/prometheus/types';
import { LokiQuery } from 'app/plugins/datasource/loki/types';
interface Props { interface Props {
value?: string; value?: string;
@ -7,13 +12,62 @@ interface Props {
dataSourceName: string; // will be a prometheus or loki datasource dataSourceName: string; // will be a prometheus or loki datasource
} }
// @TODO implement proper prom/loki query editor here
export const ExpressionEditor: FC<Props> = ({ value, onChange, dataSourceName }) => { export const ExpressionEditor: FC<Props> = ({ value, onChange, dataSourceName }) => {
const { mapToValue, mapToQuery } = useQueryMappers(dataSourceName);
const [query, setQuery] = useState(mapToQuery({ refId: 'A', hide: false }, value));
const { error, loading, value: dataSource } = useAsync(() => {
return getDataSourceSrv().get(dataSourceName);
}, [dataSourceName]);
const onChangeQuery = useCallback(
(query: DataQuery) => {
setQuery(query);
onChange(mapToValue(query));
},
[onChange, mapToValue]
);
if (loading || dataSource?.name !== dataSourceName) {
return null;
}
if (error || !dataSource || !dataSource?.components?.QueryEditor) {
const errorMessage = error?.message || 'Data source plugin does not export any Query Editor component';
return <div>Could not load query editor due to: {errorMessage}</div>;
}
const QueryEditor = dataSource?.components?.QueryEditor;
return ( return (
<TextArea <QueryEditor
placeholder="Enter a promql expression" query={query}
value={value} queries={[query]}
onChange={(evt) => onChange((evt.target as HTMLTextAreaElement).value)} app={CoreApp.CloudAlerting}
onChange={onChangeQuery}
onRunQuery={noop}
datasource={dataSource}
/> />
); );
}; };
type QueryMappers<T extends DataQuery = DataQuery> = {
mapToValue: (query: T) => string;
mapToQuery: (existing: T, value: string | undefined) => T;
};
function useQueryMappers(dataSourceName: string): QueryMappers {
return useMemo(() => {
const settings = getDataSourceSrv().getInstanceSettings(dataSourceName);
switch (settings?.type) {
case 'loki':
case 'prometheus':
return {
mapToValue: (query: PromQuery | LokiQuery) => query.expr,
mapToQuery: (existing: DataQuery, value: string | undefined) => ({ ...existing, expr: value }),
};
default:
throw new Error(`${dataSourceName} is not supported as an expression editor`);
}
}, [dataSourceName]);
}

View File

@ -17,12 +17,6 @@ interface Props {
export const LokiAnnotationsQueryEditor = memo(function LokiAnnotationQueryEditor(props: Props) { export const LokiAnnotationsQueryEditor = memo(function LokiAnnotationQueryEditor(props: Props) {
const { expr, maxLines, instant, datasource, onChange } = props; const { expr, maxLines, instant, datasource, onChange } = props;
// Timerange to get existing labels from. Hard coding like this seems to be good enough right now.
const absoluteRange = {
from: Date.now() - 10000,
to: Date.now(),
};
const queryWithRefId: LokiQuery = { const queryWithRefId: LokiQuery = {
refId: '', refId: '',
expr, expr,
@ -38,7 +32,6 @@ export const LokiAnnotationsQueryEditor = memo(function LokiAnnotationQueryEdito
onRunQuery={() => {}} onRunQuery={() => {}}
onBlur={() => {}} onBlur={() => {}}
history={[]} history={[]}
absoluteRange={absoluteRange}
ExtraFieldElement={ ExtraFieldElement={
<LokiOptionFields <LokiOptionFields
queryType={queryWithRefId.instant ? 'instant' : 'range'} queryType={queryWithRefId.instant ? 'instant' : 'range'}

View File

@ -11,8 +11,7 @@ import { LokiOptionFields } from './LokiOptionFields';
type Props = ExploreQueryFieldProps<LokiDatasource, LokiQuery, LokiOptions>; type Props = ExploreQueryFieldProps<LokiDatasource, LokiQuery, LokiOptions>;
export function LokiExploreQueryEditor(props: Props) { export function LokiExploreQueryEditor(props: Props) {
const { range, query, data, datasource, history, onChange, onRunQuery } = props; const { query, data, datasource, history, onChange, onRunQuery } = props;
const absoluteTimeRange = { from: range!.from!.valueOf(), to: range!.to!.valueOf() }; // Range here is never optional
return ( return (
<LokiQueryField <LokiQueryField
@ -23,7 +22,6 @@ export function LokiExploreQueryEditor(props: Props) {
onRunQuery={onRunQuery} onRunQuery={onRunQuery}
history={history} history={history}
data={data} data={data}
absoluteRange={absoluteTimeRange}
ExtraFieldElement={ ExtraFieldElement={
<LokiOptionFields <LokiOptionFields
queryType={query.instant ? 'instant' : 'range'} queryType={query.instant ? 'instant' : 'range'}

View File

@ -1,19 +1,14 @@
// Libraries // Libraries
import React, { memo } from 'react'; import React from 'react';
// Types // Types
import { QueryEditorProps } from '@grafana/data';
import { InlineFormLabel } from '@grafana/ui'; import { InlineFormLabel } from '@grafana/ui';
import { LokiDatasource } from '../datasource';
import { LokiQuery, LokiOptions } from '../types';
import { LokiQueryField } from './LokiQueryField'; import { LokiQueryField } from './LokiQueryField';
import { LokiOptionFields } from './LokiOptionFields'; import { LokiOptionFields } from './LokiOptionFields';
import { LokiQueryEditorProps } from './types';
type Props = QueryEditorProps<LokiDatasource, LokiQuery, LokiOptions>; export function LokiQueryEditor(props: LokiQueryEditorProps) {
const { query, data, datasource, onChange, onRunQuery } = props;
export function LokiQueryEditor(props: Props) {
const { range, query, data, datasource, onChange, onRunQuery } = props;
const absoluteTimeRange = { from: range!.from!.valueOf(), to: range!.to!.valueOf() }; // Range here is never optional
const onLegendChange = (e: React.SyntheticEvent<HTMLInputElement>) => { const onLegendChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
const nextQuery = { ...query, legendFormat: e.currentTarget.value }; const nextQuery = { ...query, legendFormat: e.currentTarget.value };
@ -51,7 +46,7 @@ export function LokiQueryEditor(props: Props) {
onBlur={onRunQuery} onBlur={onRunQuery}
history={[]} history={[]}
data={data} data={data}
absoluteRange={absoluteTimeRange} data-testid={testIds.editor}
ExtraFieldElement={ ExtraFieldElement={
<> <>
<LokiOptionFields <LokiOptionFields
@ -69,4 +64,6 @@ export function LokiQueryEditor(props: Props) {
); );
} }
export default memo(LokiQueryEditor); export const testIds = {
editor: 'loki-editor',
};

View File

@ -0,0 +1,59 @@
import React from 'react';
import { render, RenderResult } from '@testing-library/react';
import { CoreApp } from '@grafana/data';
import { noop } from 'lodash';
import { LokiDatasource } from '../datasource';
import { testIds as alertingTestIds } from './LokiQueryEditorForAlerting';
import { testIds as regularTestIds } from './LokiQueryEditor';
import LokiQueryEditorByApp from './LokiQueryEditorByApp';
function setup(app: CoreApp): RenderResult {
const dataSource = ({
languageProvider: {
start: () => Promise.resolve([]),
getSyntax: () => {},
getLabelKeys: () => [],
metrics: [],
},
} as unknown) as LokiDatasource;
return render(
<LokiQueryEditorByApp
app={app}
onChange={noop}
onRunQuery={noop}
datasource={dataSource}
query={{ refId: 'A', expr: '' }}
/>
);
}
describe('LokiQueryEditorByApp', () => {
it('should render simplified query editor for cloud alerting', () => {
const { getByTestId, queryByTestId } = setup(CoreApp.CloudAlerting);
expect(getByTestId(alertingTestIds.editor)).toBeInTheDocument();
expect(queryByTestId(regularTestIds.editor)).toBeNull();
});
it('should render regular query editor for unkown apps', () => {
const { getByTestId, queryByTestId } = setup(CoreApp.Unknown);
expect(getByTestId(regularTestIds.editor)).toBeInTheDocument();
expect(queryByTestId(alertingTestIds.editor)).toBeNull();
});
it('should render regular query editor for explore', () => {
const { getByTestId, queryByTestId } = setup(CoreApp.Explore);
expect(getByTestId(regularTestIds.editor)).toBeInTheDocument();
expect(queryByTestId(alertingTestIds.editor)).toBeNull();
});
it('should render regular query editor for dashboard', () => {
const { getByTestId, queryByTestId } = setup(CoreApp.Dashboard);
expect(getByTestId(regularTestIds.editor)).toBeInTheDocument();
expect(queryByTestId(alertingTestIds.editor)).toBeNull();
});
});

View File

@ -0,0 +1,18 @@
import React, { memo } from 'react';
import { CoreApp } from '@grafana/data';
import { LokiQueryEditorProps } from './types';
import { LokiQueryEditor } from './LokiQueryEditor';
import { LokiQueryEditorForAlerting } from './LokiQueryEditorForAlerting';
export function LokiQueryEditorByApp(props: LokiQueryEditorProps) {
const { app } = props;
switch (app) {
case CoreApp.CloudAlerting:
return <LokiQueryEditorForAlerting {...props} />;
default:
return <LokiQueryEditor {...props} />;
}
}
export default memo(LokiQueryEditorByApp);

View File

@ -0,0 +1,25 @@
import React from 'react';
import { LokiQueryField } from './LokiQueryField';
import { LokiQueryEditorProps } from './types';
export function LokiQueryEditorForAlerting(props: LokiQueryEditorProps) {
const { query, data, datasource, onChange, onRunQuery } = props;
return (
<LokiQueryField
datasource={datasource}
query={query}
onChange={onChange}
onRunQuery={onRunQuery}
onBlur={onRunQuery}
history={[]}
data={data}
placeholder="Enter a Loki query"
data-testid={testIds.editor}
/>
);
}
export const testIds = {
editor: 'loki-editor-cloud-alerting',
};

View File

@ -18,7 +18,7 @@ import { Plugin, Node } from 'slate';
import { LokiLabelBrowser } from './LokiLabelBrowser'; import { LokiLabelBrowser } from './LokiLabelBrowser';
// Types // Types
import { ExploreQueryFieldProps, AbsoluteTimeRange } from '@grafana/data'; import { ExploreQueryFieldProps } from '@grafana/data';
import { LokiQuery, LokiOptions } from '../types'; import { LokiQuery, LokiOptions } from '../types';
import { LanguageMap, languages as prismLanguages } from 'prismjs'; import { LanguageMap, languages as prismLanguages } from 'prismjs';
import LokiLanguageProvider, { LokiHistoryItem } from '../language_provider'; import LokiLanguageProvider, { LokiHistoryItem } from '../language_provider';
@ -63,8 +63,9 @@ function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadTe
export interface LokiQueryFieldProps extends ExploreQueryFieldProps<LokiDatasource, LokiQuery, LokiOptions> { export interface LokiQueryFieldProps extends ExploreQueryFieldProps<LokiDatasource, LokiQuery, LokiOptions> {
history: LokiHistoryItem[]; history: LokiHistoryItem[];
absoluteRange: AbsoluteTimeRange;
ExtraFieldElement?: ReactNode; ExtraFieldElement?: ReactNode;
placeholder?: string;
'data-testid'?: string;
} }
interface LokiQueryFieldState { interface LokiQueryFieldState {
@ -138,7 +139,13 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
}; };
render() { render() {
const { ExtraFieldElement, query, datasource } = this.props; const {
ExtraFieldElement,
query,
datasource,
placeholder = 'Enter a Loki query (run with Shift+Enter)',
} = this.props;
const { labelsLoaded, labelBrowserVisible } = this.state; const { labelsLoaded, labelBrowserVisible } = this.state;
const lokiLanguageProvider = datasource.languageProvider as LokiLanguageProvider; const lokiLanguageProvider = datasource.languageProvider as LokiLanguageProvider;
const cleanText = datasource.languageProvider ? lokiLanguageProvider.cleanText : undefined; const cleanText = datasource.languageProvider ? lokiLanguageProvider.cleanText : undefined;
@ -148,7 +155,10 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
return ( return (
<> <>
<div className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"> <div
className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"
data-testid={this.props['data-testid']}
>
<button <button
className="gf-form-label query-keyword pointer" className="gf-form-label query-keyword pointer"
onClick={this.onClickChooserButton} onClick={this.onClickChooserButton}
@ -167,7 +177,7 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
onChange={this.onChangeQuery} onChange={this.onChangeQuery}
onBlur={this.props.onBlur} onBlur={this.props.onBlur}
onRunQuery={this.props.onRunQuery} onRunQuery={this.props.onRunQuery}
placeholder="Enter a Loki query (run with Shift+Enter)" placeholder={placeholder}
portalOrigin="loki" portalOrigin="loki"
/> />
</div> </div>

View File

@ -17,12 +17,6 @@ exports[`LokiExploreQueryEditor should render component 1`] = `
queryType="range" queryType="range"
/> />
} }
absoluteRange={
Object {
"from": 1577836800000,
"to": 1577923200000,
}
}
data={ data={
Object { Object {
"request": Object { "request": Object {

View File

@ -43,12 +43,7 @@ exports[`Render LokiQueryEditor with legend should render 1`] = `
</div> </div>
</React.Fragment> </React.Fragment>
} }
absoluteRange={ data-testid="loki-editor"
Object {
"from": 1577836800000,
"to": 1577923200000,
}
}
datasource={Object {}} datasource={Object {}}
history={Array []} history={Array []}
onBlur={[MockFunction]} onBlur={[MockFunction]}
@ -107,12 +102,7 @@ exports[`Render LokiQueryEditor with legend should update timerange 1`] = `
</div> </div>
</React.Fragment> </React.Fragment>
} }
absoluteRange={ data-testid="loki-editor"
Object {
"from": 1546300800000,
"to": 1577923200000,
}
}
datasource={Object {}} datasource={Object {}}
history={Array []} history={Array []}
onBlur={[MockFunction]} onBlur={[MockFunction]}

View File

@ -0,0 +1,5 @@
import { QueryEditorProps } from '@grafana/data';
import LokiDatasource from '../datasource';
import { LokiOptions, LokiQuery } from '../types';
export type LokiQueryEditorProps = QueryEditorProps<LokiDatasource, LokiQuery, LokiOptions>;

View File

@ -3,12 +3,12 @@ import Datasource from './datasource';
import LokiCheatSheet from './components/LokiCheatSheet'; import LokiCheatSheet from './components/LokiCheatSheet';
import LokiExploreQueryEditor from './components/LokiExploreQueryEditor'; import LokiExploreQueryEditor from './components/LokiExploreQueryEditor';
import LokiQueryEditor from './components/LokiQueryEditor'; import LokiQueryEditorByApp from './components/LokiQueryEditorByApp';
import { LokiAnnotationsQueryCtrl } from './LokiAnnotationsQueryCtrl'; import { LokiAnnotationsQueryCtrl } from './LokiAnnotationsQueryCtrl';
import { ConfigEditor } from './configuration/ConfigEditor'; import { ConfigEditor } from './configuration/ConfigEditor';
export const plugin = new DataSourcePlugin(Datasource) export const plugin = new DataSourcePlugin(Datasource)
.setQueryEditor(LokiQueryEditor) .setQueryEditor(LokiQueryEditorByApp)
.setConfigEditor(ConfigEditor) .setConfigEditor(ConfigEditor)
.setExploreQueryField(LokiExploreQueryEditor) .setExploreQueryField(LokiExploreQueryEditor)
.setQueryEditorHelp(LokiCheatSheet) .setQueryEditorHelp(LokiCheatSheet)

View File

@ -3,18 +3,16 @@ import React, { PureComponent } from 'react';
// Types // Types
import { InlineFormLabel, LegacyForms, Select } from '@grafana/ui'; import { InlineFormLabel, LegacyForms, Select } from '@grafana/ui';
import { QueryEditorProps, SelectableValue } from '@grafana/data'; import { SelectableValue } from '@grafana/data';
import { PrometheusDatasource } from '../datasource'; import { PromQuery } from '../types';
import { PromOptions, PromQuery } from '../types';
import PromQueryField from './PromQueryField'; import PromQueryField from './PromQueryField';
import PromLink from './PromLink'; import PromLink from './PromLink';
import { PromExemplarField } from './PromExemplarField'; import { PromExemplarField } from './PromExemplarField';
import { PromQueryEditorProps } from './types';
const { Switch } = LegacyForms; const { Switch } = LegacyForms;
export type Props = QueryEditorProps<PrometheusDatasource, PromQuery, PromOptions>;
const FORMAT_OPTIONS: Array<SelectableValue<string>> = [ const FORMAT_OPTIONS: Array<SelectableValue<string>> = [
{ label: 'Time series', value: 'time_series' }, { label: 'Time series', value: 'time_series' },
{ label: 'Table', value: 'table' }, { label: 'Table', value: 'table' },
@ -35,11 +33,11 @@ interface State {
exemplar: boolean; exemplar: boolean;
} }
export class PromQueryEditor extends PureComponent<Props, State> { export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State> {
// Query target to be modified and used for queries // Query target to be modified and used for queries
query: PromQuery; query: PromQuery;
constructor(props: Props) { constructor(props: PromQueryEditorProps) {
super(props); super(props);
// Use default query to prevent undefined input values // Use default query to prevent undefined input values
const defaultQuery: Partial<PromQuery> = { expr: '', legendFormat: '', interval: '', exemplar: true }; const defaultQuery: Partial<PromQuery> = { expr: '', legendFormat: '', interval: '', exemplar: true };
@ -118,6 +116,7 @@ export class PromQueryEditor extends PureComponent<Props, State> {
onChange={this.onFieldChange} onChange={this.onFieldChange}
history={[]} history={[]}
data={data} data={data}
data-testid={testIds.editor}
ExtraFieldElement={ ExtraFieldElement={
<div className="gf-form-inline"> <div className="gf-form-inline">
<div className="gf-form"> <div className="gf-form">
@ -198,3 +197,7 @@ export class PromQueryEditor extends PureComponent<Props, State> {
); );
} }
} }
export const testIds = {
editor: 'prom-editor',
};

View File

@ -0,0 +1,64 @@
import React from 'react';
import { render, RenderResult } from '@testing-library/react';
import { PromQueryEditorByApp } from './PromQueryEditorByApp';
import { CoreApp } from '@grafana/data';
import { noop } from 'lodash';
import { Observable } from 'rxjs';
import { first } from 'rxjs/operators';
import { PrometheusDatasource } from '../datasource';
import { testIds as alertingTestIds } from './PromQueryEditorForAlerting';
import { testIds as regularTestIds } from './PromQueryEditor';
function setup(app: CoreApp): RenderResult {
const dataSource = ({
createQuery: jest.fn((q) => q),
getPrometheusTime: jest.fn((date, roundup) => 123),
languageProvider: {
start: () => Promise.resolve([]),
syntax: () => {},
getLabelKeys: () => [],
metrics: [],
},
exemplarErrors: new Observable().pipe(first()),
} as unknown) as PrometheusDatasource;
return render(
<PromQueryEditorByApp
app={app}
onChange={noop}
onRunQuery={noop}
datasource={dataSource}
query={{ refId: 'A', expr: '' }}
/>
);
}
describe('PromQueryEditorByApp', () => {
it('should render simplified query editor for cloud alerting', () => {
const { getByTestId, queryByTestId } = setup(CoreApp.CloudAlerting);
expect(getByTestId(alertingTestIds.editor)).toBeInTheDocument();
expect(queryByTestId(regularTestIds.editor)).toBeNull();
});
it('should render regular query editor for unkown apps', () => {
const { getByTestId, queryByTestId } = setup(CoreApp.Unknown);
expect(getByTestId(regularTestIds.editor)).toBeInTheDocument();
expect(queryByTestId(alertingTestIds.editor)).toBeNull();
});
it('should render regular query editor for explore', () => {
const { getByTestId, queryByTestId } = setup(CoreApp.Explore);
expect(getByTestId(regularTestIds.editor)).toBeInTheDocument();
expect(queryByTestId(alertingTestIds.editor)).toBeNull();
});
it('should render regular query editor for dashboard', () => {
const { getByTestId, queryByTestId } = setup(CoreApp.Dashboard);
expect(getByTestId(regularTestIds.editor)).toBeInTheDocument();
expect(queryByTestId(alertingTestIds.editor)).toBeNull();
});
});

View File

@ -0,0 +1,18 @@
import React, { memo } from 'react';
import { CoreApp } from '@grafana/data';
import { PromQueryEditorProps } from './types';
import { PromQueryEditor } from './PromQueryEditor';
import { PromQueryEditorForAlerting } from './PromQueryEditorForAlerting';
export function PromQueryEditorByApp(props: PromQueryEditorProps) {
const { app } = props;
switch (app) {
case CoreApp.CloudAlerting:
return <PromQueryEditorForAlerting {...props} />;
default:
return <PromQueryEditor {...props} />;
}
}
export default memo(PromQueryEditorByApp);

View File

@ -0,0 +1,25 @@
import React from 'react';
import PromQueryField from './PromQueryField';
import { PromQueryEditorProps } from './types';
export function PromQueryEditorForAlerting(props: PromQueryEditorProps) {
const { datasource, query, range, data, onChange, onRunQuery } = props;
return (
<PromQueryField
datasource={datasource}
query={query}
onRunQuery={onRunQuery}
onChange={onChange}
history={[]}
range={range}
data={data}
placeholder="Enter a PromQL query"
data-testid={testIds.editor}
/>
);
}
export const testIds = {
editor: 'prom-editor-cloud-alerting',
};

View File

@ -77,6 +77,8 @@ export function willApplySuggestion(suggestion: string, { typeaheadContext, type
interface PromQueryFieldProps extends ExploreQueryFieldProps<PrometheusDatasource, PromQuery, PromOptions> { interface PromQueryFieldProps extends ExploreQueryFieldProps<PrometheusDatasource, PromQuery, PromOptions> {
history: Array<HistoryItem<PromQuery>>; history: Array<HistoryItem<PromQuery>>;
ExtraFieldElement?: ReactNode; ExtraFieldElement?: ReactNode;
placeholder?: string;
'data-testid'?: string;
} }
interface PromQueryFieldState { interface PromQueryFieldState {
@ -271,7 +273,9 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
datasource: { languageProvider }, datasource: { languageProvider },
query, query,
ExtraFieldElement, ExtraFieldElement,
placeholder = 'Enter a PromQL query (run with Shift+Enter)',
} = this.props; } = this.props;
const { labelBrowserVisible, syntaxLoaded, hint } = this.state; const { labelBrowserVisible, syntaxLoaded, hint } = this.state;
const cleanText = languageProvider ? languageProvider.cleanText : undefined; const cleanText = languageProvider ? languageProvider.cleanText : undefined;
const hasMetrics = languageProvider.metrics.length > 0; const hasMetrics = languageProvider.metrics.length > 0;
@ -280,7 +284,10 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
return ( return (
<> <>
<div className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"> <div
className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"
data-testid={this.props['data-testid']}
>
<button <button
className="gf-form-label query-keyword pointer" className="gf-form-label query-keyword pointer"
onClick={this.onClickChooserButton} onClick={this.onClickChooserButton}
@ -300,7 +307,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
onBlur={this.props.onBlur} onBlur={this.props.onBlur}
onChange={this.onChangeQuery} onChange={this.onChangeQuery}
onRunQuery={this.props.onRunQuery} onRunQuery={this.props.onRunQuery}
placeholder="Enter a PromQL query (run with Shift+Enter)" placeholder={placeholder}
portalOrigin="prometheus" portalOrigin="prometheus"
syntaxLoaded={syntaxLoaded} syntaxLoaded={syntaxLoaded}
/> />

View File

@ -178,6 +178,7 @@ exports[`Render PromQueryEditor with basic options should render 1`] = `
/> />
</div> </div>
} }
data-testid="prom-editor"
datasource={ datasource={
Object { Object {
"createQuery": [MockFunction], "createQuery": [MockFunction],

View File

@ -0,0 +1,5 @@
import { QueryEditorProps } from '@grafana/data';
import { PrometheusDatasource } from '../datasource';
import { PromOptions, PromQuery } from '../types';
export type PromQueryEditorProps = QueryEditorProps<PrometheusDatasource, PromQuery, PromOptions>;

View File

@ -1,7 +1,7 @@
import { DataSourcePlugin } from '@grafana/data'; import { DataSourcePlugin } from '@grafana/data';
import { ANNOTATION_QUERY_STEP_DEFAULT, PrometheusDatasource } from './datasource'; import { ANNOTATION_QUERY_STEP_DEFAULT, PrometheusDatasource } from './datasource';
import { PromQueryEditor } from './components/PromQueryEditor'; import PromQueryEditorByApp from './components/PromQueryEditorByApp';
import PromCheatSheet from './components/PromCheatSheet'; import PromCheatSheet from './components/PromCheatSheet';
import PromExploreQueryEditor from './components/PromExploreQueryEditor'; import PromExploreQueryEditor from './components/PromExploreQueryEditor';
@ -13,7 +13,7 @@ class PrometheusAnnotationsQueryCtrl {
} }
export const plugin = new DataSourcePlugin(PrometheusDatasource) export const plugin = new DataSourcePlugin(PrometheusDatasource)
.setQueryEditor(PromQueryEditor) .setQueryEditor(PromQueryEditorByApp)
.setConfigEditor(ConfigEditor) .setConfigEditor(ConfigEditor)
.setExploreMetricsQueryField(PromExploreQueryEditor) .setExploreMetricsQueryField(PromExploreQueryEditor)
.setAnnotationQueryCtrl(PrometheusAnnotationsQueryCtrl) .setAnnotationQueryCtrl(PrometheusAnnotationsQueryCtrl)

View File

@ -50,11 +50,9 @@ export class TempoQueryField extends React.PureComponent<Props, State> {
}; };
render() { render() {
const { query, onChange, range } = this.props; const { query, onChange } = this.props;
const { linkedDatasource } = this.state; const { linkedDatasource } = this.state;
const absoluteTimeRange = { from: range!.from!.valueOf(), to: range!.to!.valueOf() }; // Range here is never optional
return ( return (
<> <>
<InlineFieldRow> <InlineFieldRow>
@ -87,7 +85,6 @@ export class TempoQueryField extends React.PureComponent<Props, State> {
onRunQuery={this.onRunLinkedQuery} onRunQuery={this.onRunLinkedQuery}
query={this.linkedQuery as any} query={this.linkedQuery as any}
history={[]} history={[]}
absoluteRange={absoluteTimeRange}
/> />
</> </>
)} )}