Cloud Monitoring: MQL support (#26551)

* cloud monitoring mql support

* reduce nesting

* remove resource type from deep link since. its removed for two reasons. first of all it is not needed for the link to work. secondly, by adding the resource type, the the link will differ from the query in grafana which I think is misleading

* use frame.meta.executedQueryString instead of legacy meta

Co-authored-by: Erik Sundell <erik.sundell87@gmail.com>
This commit is contained in:
Mitsuhiro Tanda
2021-01-18 21:48:43 +09:00
committed by GitHub
parent 84ee414361
commit 717827725f
18 changed files with 1366 additions and 584 deletions

View File

@@ -6,7 +6,7 @@ import { SelectableValue } from '@grafana/data';
import CloudMonitoringDatasource from '../datasource';
import { AnnotationsHelp, LabelFilter, Metrics, Project } from './';
import { toOption } from '../functions';
import { AnnotationTarget, MetricDescriptor } from '../types';
import { AnnotationTarget, EditorMode, MetricDescriptor } from '../types';
const { Input } = LegacyForms;
@@ -25,6 +25,7 @@ interface State extends AnnotationTarget {
}
const DefaultTarget: State = {
editorMode: EditorMode.Visual,
projectName: '',
projects: [],
metricType: '',
@@ -42,7 +43,7 @@ const DefaultTarget: State = {
export class AnnotationQueryEditor extends React.Component<Props, State> {
state: State = DefaultTarget;
async UNSAFE_UNSAFE_componentWillMount() {
async UNSAFE_componentWillMount() {
// Unfortunately, migrations like this need to go UNSAFE_componentWillMount. As soon as there's
// migration hook for this module.ts, we can do the migrations there instead.
const { target, datasource } = this.props;

View File

@@ -0,0 +1,32 @@
import React from 'react';
import { TextArea } from '@grafana/ui';
export interface Props {
onChange: (query: string) => void;
onRunQuery: () => void;
query: string;
}
export function MQLQueryEditor({ query, onChange, onRunQuery }: React.PropsWithChildren<Props>) {
const onKeyDown = (event: any) => {
if (event.key === 'Enter' && (event.shiftKey || event.ctrlKey)) {
event.preventDefault();
onRunQuery();
}
};
return (
<>
<TextArea
name="Query"
className="slate-query-field"
value={query}
rows={10}
placeholder="Enter a Cloud Monitoring MQL query (Run with Shift+Enter)"
onBlur={onRunQuery}
onChange={e => onChange(e.currentTarget.value)}
onKeyDown={onKeyDown}
/>
</>
);
}

View File

@@ -1,9 +1,10 @@
import React, { useState, useEffect } from 'react';
import { Project, Aggregations, Metrics, LabelFilter, GroupBys, Alignments, AlignmentPeriods, AliasBy } from '.';
import { MetricQuery, MetricDescriptor } from '../types';
import { Project, VisualMetricQueryEditor, AliasBy } from '.';
import { MetricQuery, MetricDescriptor, EditorMode } from '../types';
import { getAlignmentPickerData } from '../functions';
import CloudMonitoringDatasource from '../datasource';
import { SelectableValue } from '@grafana/data';
import { MQLQueryEditor } from './MQLQueryEditor';
export interface Props {
refId: string;
@@ -25,6 +26,7 @@ export const defaultState: State = {
};
export const defaultQuery: (dataSource: CloudMonitoringDatasource) => MetricQuery = dataSource => ({
editorMode: EditorMode.Visual,
projectName: dataSource.getDefaultProject(),
metricType: '',
metricKind: '',
@@ -36,13 +38,15 @@ export const defaultQuery: (dataSource: CloudMonitoringDatasource) => MetricQuer
groupBys: [],
filters: [],
aliasBy: '',
query: '',
});
function Editor({
refId,
query,
datasource,
onChange,
onChange: onQueryChange,
onRunQuery,
usedAlignmentPeriod,
variableOptionGroup,
}: React.PropsWithChildren<Props>) {
@@ -56,6 +60,11 @@ function Editor({
}
}, [query.projectName, query.groupBys, query.metricType]);
const onChange = (metricQuery: MetricQuery) => {
onQueryChange({ ...query, ...metricQuery });
onRunQuery();
};
const onMetricTypeChange = async ({ valueType, metricKind, type, unit }: MetricDescriptor) => {
const { perSeriesAligner, alignOptions } = getAlignmentPickerData(
{ valueType, metricKind, perSeriesAligner: state.perSeriesAligner },
@@ -68,9 +77,6 @@ function Editor({
onChange({ ...query, perSeriesAligner, metricType: type, unit, valueType, metricKind });
};
const { labels } = state;
const { perSeriesAligner, alignOptions } = getAlignmentPickerData(query, datasource.templateSrv);
return (
<>
<Project
@@ -81,58 +87,33 @@ function Editor({
onChange({ ...query, projectName });
}}
/>
<Metrics
templateSrv={datasource.templateSrv}
projectName={query.projectName}
metricType={query.metricType}
templateVariableOptions={variableOptionGroup.options}
datasource={datasource}
onChange={onMetricTypeChange}
>
{metric => (
<>
<LabelFilter
labels={labels}
filters={query.filters!}
onChange={filters => onChange({ ...query, filters })}
variableOptionGroup={variableOptionGroup}
/>
<GroupBys
groupBys={Object.keys(labels)}
values={query.groupBys!}
onChange={groupBys => onChange({ ...query, groupBys })}
variableOptionGroup={variableOptionGroup}
/>
<Aggregations
metricDescriptor={metric}
templateVariableOptions={variableOptionGroup.options}
crossSeriesReducer={query.crossSeriesReducer}
groupBys={query.groupBys!}
onChange={crossSeriesReducer => onChange({ ...query, crossSeriesReducer })}
>
{displayAdvancedOptions =>
displayAdvancedOptions && (
<Alignments
alignOptions={alignOptions}
templateVariableOptions={variableOptionGroup.options}
perSeriesAligner={perSeriesAligner || ''}
onChange={perSeriesAligner => onChange({ ...query, perSeriesAligner })}
/>
)
}
</Aggregations>
<AlignmentPeriods
templateSrv={datasource.templateSrv}
templateVariableOptions={variableOptionGroup.options}
alignmentPeriod={query.alignmentPeriod || ''}
perSeriesAligner={query.perSeriesAligner || ''}
usedAlignmentPeriod={usedAlignmentPeriod}
onChange={alignmentPeriod => onChange({ ...query, alignmentPeriod })}
/>
<AliasBy value={query.aliasBy || ''} onChange={aliasBy => onChange({ ...query, aliasBy })} />
</>
)}
</Metrics>
{query.editorMode === EditorMode.Visual && (
<VisualMetricQueryEditor
labels={state.labels}
variableOptionGroup={variableOptionGroup}
usedAlignmentPeriod={usedAlignmentPeriod}
onMetricTypeChange={onMetricTypeChange}
onChange={onChange}
datasource={datasource}
query={query}
/>
)}
{query.editorMode === EditorMode.MQL && (
<MQLQueryEditor
onChange={(q: string) => onQueryChange({ ...query, query: q })}
onRunQuery={onRunQuery}
query={query.query}
></MQLQueryEditor>
)}
<AliasBy
value={query.aliasBy}
onChange={aliasBy => {
onChange({ ...query, aliasBy });
}}
/>
</>
);
}

View File

@@ -1,13 +1,14 @@
import React, { PureComponent } from 'react';
import appEvents from 'app/core/app_events';
import { CoreEvents } from 'app/types';
import { Help, MetricQueryEditor, QueryTypeSelector, SLOQueryEditor } from './';
import { CloudMonitoringQuery, MetricQuery, QueryType, SLOQuery } from '../types';
import { ExploreQueryFieldProps, SelectableValue } from '@grafana/data';
import { Segment } from '@grafana/ui';
import { Help, MetricQueryEditor, SLOQueryEditor } from './';
import { CloudMonitoringQuery, MetricQuery, QueryType, SLOQuery, queryTypes, EditorMode } from '../types';
import { defaultQuery } from './MetricQueryEditor';
import { defaultQuery as defaultSLOQuery } from './SLOQueryEditor';
import { formatCloudMonitoringError, toOption } from '../functions';
import CloudMonitoringDatasource from '../datasource';
import { ExploreQueryFieldProps } from '@grafana/data';
export type Props = ExploreQueryFieldProps<CloudMonitoringDatasource, CloudMonitoringQuery>;
@@ -18,7 +19,7 @@ interface State {
export class QueryEditor extends PureComponent<Props, State> {
state: State = { lastQueryError: '' };
async UNSAFE_UNSAFE_componentWillMount() {
async UNSAFE_componentWillMount() {
const { datasource, query } = this.props;
// Unfortunately, migrations like this need to go UNSAFE_componentWillMount. As soon as there's
@@ -76,21 +77,51 @@ export class QueryEditor extends PureComponent<Props, State> {
return (
<>
<QueryTypeSelector
value={queryType}
templateVariableOptions={variableOptionGroup.options}
onChange={(queryType: QueryType) => {
onChange({ ...query, sloQuery, queryType });
onRunQuery();
}}
></QueryTypeSelector>
<div className="gf-form-inline">
<label className="gf-form-label query-keyword width-9">Query Type</label>
<Segment
value={[...queryTypes, ...variableOptionGroup.options].find(qt => qt.value === queryType)}
options={[
...queryTypes,
{
label: 'Template Variables',
options: variableOptionGroup.options,
},
]}
onChange={({ value }: SelectableValue<QueryType>) => {
onChange({ ...query, sloQuery, queryType: value! });
onRunQuery();
}}
/>
{query.queryType !== QueryType.SLO && (
<button
className="gf-form-label "
onClick={() =>
this.onQueryChange('metricQuery', {
...metricQuery,
editorMode: metricQuery.editorMode === EditorMode.MQL ? EditorMode.Visual : EditorMode.MQL,
})
}
>
<span className="query-keyword">{'<>'}</span>&nbsp;&nbsp;
{metricQuery.editorMode === EditorMode.MQL ? 'Switch to builder' : 'Edit MQL'}
</button>
)}
<div className="gf-form gf-form--grow">
<label className="gf-form-label gf-form-label--grow"></label>
</div>
</div>
{queryType === QueryType.METRICS && (
<MetricQueryEditor
refId={query.refId}
variableOptionGroup={variableOptionGroup}
usedAlignmentPeriod={usedAlignmentPeriod}
onChange={(query: MetricQuery) => this.onQueryChange('metricQuery', query)}
onChange={(metricQuery: MetricQuery) => {
this.props.onChange({ ...this.props.query, metricQuery });
}}
onRunQuery={onRunQuery}
datasource={datasource}
query={metricQuery}
@@ -107,6 +138,7 @@ export class QueryEditor extends PureComponent<Props, State> {
query={sloQuery}
></SLOQueryEditor>
)}
<Help
rawQuery={decodeURIComponent(meta?.executedQueryString ?? '')}
lastQueryError={this.state.lastQueryError}

View File

@@ -0,0 +1,84 @@
import React from 'react';
import { Aggregations, Metrics, LabelFilter, GroupBys, Alignments, AlignmentPeriods } from '.';
import { MetricQuery, MetricDescriptor } from '../types';
import { getAlignmentPickerData } from '../functions';
import CloudMonitoringDatasource from '../datasource';
import { SelectableValue } from '@grafana/data';
export interface Props {
usedAlignmentPeriod?: number;
variableOptionGroup: SelectableValue<string>;
onMetricTypeChange: (query: MetricDescriptor) => void;
onChange: (query: MetricQuery) => void;
query: MetricQuery;
datasource: CloudMonitoringDatasource;
labels: any;
}
function Editor({
query,
labels,
datasource,
onChange,
onMetricTypeChange,
usedAlignmentPeriod,
variableOptionGroup,
}: React.PropsWithChildren<Props>) {
const { perSeriesAligner, alignOptions } = getAlignmentPickerData(query, datasource.templateSrv);
return (
<Metrics
templateSrv={datasource.templateSrv}
projectName={query.projectName}
metricType={query.metricType}
templateVariableOptions={variableOptionGroup.options}
datasource={datasource}
onChange={onMetricTypeChange}
>
{metric => (
<>
<LabelFilter
labels={labels}
filters={query.filters!}
onChange={filters => onChange({ ...query, filters })}
variableOptionGroup={variableOptionGroup}
/>
<GroupBys
groupBys={Object.keys(labels)}
values={query.groupBys!}
onChange={groupBys => onChange({ ...query, groupBys })}
variableOptionGroup={variableOptionGroup}
/>
<Aggregations
metricDescriptor={metric}
templateVariableOptions={variableOptionGroup.options}
crossSeriesReducer={query.crossSeriesReducer}
groupBys={query.groupBys!}
onChange={crossSeriesReducer => onChange({ ...query, crossSeriesReducer })}
>
{displayAdvancedOptions =>
displayAdvancedOptions && (
<Alignments
alignOptions={alignOptions}
templateVariableOptions={variableOptionGroup.options}
perSeriesAligner={perSeriesAligner || ''}
onChange={perSeriesAligner => onChange({ ...query, perSeriesAligner })}
/>
)
}
</Aggregations>
<AlignmentPeriods
templateSrv={datasource.templateSrv}
templateVariableOptions={variableOptionGroup.options}
alignmentPeriod={query.alignmentPeriod || ''}
perSeriesAligner={query.perSeriesAligner || ''}
usedAlignmentPeriod={usedAlignmentPeriod}
onChange={alignmentPeriod => onChange({ ...query, alignmentPeriod })}
/>
</>
)}
</Metrics>
);
}
export const VisualMetricQueryEditor = React.memo(Editor);

View File

@@ -11,5 +11,7 @@ export { Aggregations } from './Aggregations';
export { SimpleSelect } from './SimpleSelect';
export { MetricQueryEditor } from './MetricQueryEditor';
export { SLOQueryEditor } from './SLOQueryEditor';
export { MQLQueryEditor } from './MQLQueryEditor';
export { QueryTypeSelector } from './QueryType';
export { QueryInlineField, QueryField } from './Fields';
export { VisualMetricQueryEditor } from './VisualMetricQueryEditor';

View File

@@ -10,7 +10,7 @@ import {
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { CloudMonitoringOptions, CloudMonitoringQuery, Filter, MetricDescriptor, QueryType } from './types';
import { CloudMonitoringOptions, CloudMonitoringQuery, Filter, MetricDescriptor, QueryType, EditorMode } from './types';
import API from './api';
import { DataSourceWithBackend } from '@grafana/runtime';
import { CloudMonitoringVariableSupport } from './variables';
@@ -114,6 +114,7 @@ export default class CloudMonitoringDatasource extends DataSourceWithBackend<
filters: this.interpolateFilters(metricQuery.filters || [], scopedVars),
groupBys: this.interpolateGroupBys(metricQuery.groupBys || [], scopedVars),
view: metricQuery.view || 'FULL',
editorMode: metricQuery.editorMode,
},
sloQuery: sloQuery && this.interpolateProps(sloQuery, scopedVars),
};
@@ -324,6 +325,10 @@ export default class CloudMonitoringDatasource extends DataSourceWithBackend<
return !!selectorName && !!serviceId && !!sloId && !!projectName;
}
if (query.queryType && query.queryType === QueryType.METRICS && query.metricQuery.editorMode === EditorMode.MQL) {
return !!query.metricQuery.projectName && !!query.metricQuery.query;
}
const { metricType } = query.metricQuery;
return !!metricType;

View File

@@ -35,7 +35,7 @@
{
"path": "cloudmonitoring",
"method": "GET",
"url": "https://content-monitoring.googleapis.com",
"url": "https://monitoring.googleapis.com",
"jwtTokenAuth": {
"scopes": ["https://www.googleapis.com/auth/monitoring.read"],
"params": {

View File

@@ -58,12 +58,18 @@ export enum QueryType {
SLO = 'slo',
}
export enum EditorMode {
Visual = 'visual',
MQL = 'mql',
}
export const queryTypes = [
{ label: 'Metrics', value: QueryType.METRICS },
{ label: 'Service Level Objectives (SLO)', value: QueryType.SLO },
];
export interface MetricQuery {
editorMode: EditorMode;
projectName: string;
unit?: string;
metricType: string;
@@ -76,6 +82,7 @@ export interface MetricQuery {
metricKind?: string;
valueType?: string;
view?: string;
query: string;
}
export interface SLOQuery {