mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Stackdriver: Support meta labels (#21373)
* Rewrite angular segments for filter and group by in react * wip: refactoring * Update metric find queries * Remove old maps used to create labels - use one map for all types instead * Use value as label (again) for filters ang groupby * Remove old filter * Remove not used code * Fixes after pr feedback * Fix broken tests and add new metadata tests * Add index file to make imports cleaner * Cleanup. Remove old angular filter code * Fix broken tests * Use type switching instead of if statements * Use globals for regex * Updates after pr feedback * Make sure it's possible to filter using the same key multiple times * Replace metric select with segment component * Pass template vars as props * Refactor meta labels code * Reorder template variables * Fix broken tests * Reset metric value when changing service * Fix lint issue. * Make tests independant of element order * Include kubernetes.io in regex * Add instruction in help section
This commit is contained in:
@@ -16,6 +16,7 @@ const props: Props = {
|
||||
crossSeriesReducer: '',
|
||||
groupBys: [],
|
||||
children: renderProps => <div />,
|
||||
templateVariableOptions: [],
|
||||
};
|
||||
|
||||
describe('Aggregations', () => {
|
||||
@@ -32,7 +33,7 @@ describe('Aggregations', () => {
|
||||
wrapper = shallow(<Aggregations {...newProps} />);
|
||||
});
|
||||
it('', () => {
|
||||
const options = wrapper.state().aggOptions[0].options;
|
||||
const options = wrapper.state().aggOptions;
|
||||
expect(options.length).toEqual(11);
|
||||
expect(options.map((o: any) => o.value)).toEqual(
|
||||
expect.not.arrayContaining(['REDUCE_COUNT_TRUE', 'REDUCE_COUNT_FALSE'])
|
||||
@@ -49,8 +50,7 @@ describe('Aggregations', () => {
|
||||
wrapper = shallow(<Aggregations {...newProps} />);
|
||||
});
|
||||
it('', () => {
|
||||
const options = wrapper.state().aggOptions[0].options;
|
||||
|
||||
const options = wrapper.state().aggOptions;
|
||||
expect(options.length).toEqual(10);
|
||||
expect(options.map((o: any) => o.value)).toEqual(expect.arrayContaining(['REDUCE_NONE']));
|
||||
});
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { MetricSelect } from 'app/core/components/Select/MetricSelect';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Segment } from '@grafana/ui';
|
||||
import { getAggregationOptionsByMetric } from '../functions';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { ValueTypes, MetricKind } from '../constants';
|
||||
|
||||
export interface Props {
|
||||
onChange: (metricDescriptor: any) => void;
|
||||
templateSrv: TemplateSrv;
|
||||
metricDescriptor: {
|
||||
valueType: string;
|
||||
metricKind: string;
|
||||
@@ -16,6 +15,7 @@ export interface Props {
|
||||
crossSeriesReducer: string;
|
||||
groupBys: string[];
|
||||
children?: (renderProps: any) => JSX.Element;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
@@ -40,19 +40,13 @@ export class Aggregations extends React.Component<Props, State> {
|
||||
setAggOptions({ metricDescriptor }: Props) {
|
||||
let aggOptions: any[] = [];
|
||||
if (metricDescriptor) {
|
||||
aggOptions = [
|
||||
{
|
||||
label: 'Aggregations',
|
||||
expanded: true,
|
||||
options: getAggregationOptionsByMetric(
|
||||
metricDescriptor.valueType as ValueTypes,
|
||||
metricDescriptor.metricKind as MetricKind
|
||||
).map(a => ({
|
||||
...a,
|
||||
label: a.text,
|
||||
})),
|
||||
},
|
||||
];
|
||||
aggOptions = getAggregationOptionsByMetric(
|
||||
metricDescriptor.valueType as ValueTypes,
|
||||
metricDescriptor.metricKind as MetricKind
|
||||
).map(a => ({
|
||||
...a,
|
||||
label: a.text,
|
||||
}));
|
||||
}
|
||||
this.setState({ aggOptions });
|
||||
}
|
||||
@@ -65,22 +59,28 @@ export class Aggregations extends React.Component<Props, State> {
|
||||
|
||||
render() {
|
||||
const { displayAdvancedOptions, aggOptions } = this.state;
|
||||
const { templateSrv, onChange, crossSeriesReducer } = this.props;
|
||||
const { templateVariableOptions, onChange, crossSeriesReducer } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<label className="gf-form-label query-keyword width-9">Aggregation</label>
|
||||
<MetricSelect
|
||||
onChange={onChange}
|
||||
value={crossSeriesReducer}
|
||||
variables={templateSrv.variables}
|
||||
options={aggOptions}
|
||||
placeholder="Select Reducer"
|
||||
className="width-15"
|
||||
/>
|
||||
</div>
|
||||
<label className="gf-form-label query-keyword width-9">Aggregation</label>
|
||||
<Segment
|
||||
onChange={({ value }) => onChange(value)}
|
||||
value={[...aggOptions, ...templateVariableOptions].find(s => s.value === crossSeriesReducer)}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
{
|
||||
label: 'Aggregations',
|
||||
expanded: true,
|
||||
options: aggOptions,
|
||||
},
|
||||
]}
|
||||
placeholder="Select Reducer"
|
||||
></Segment>
|
||||
<div className="gf-form gf-form--grow">
|
||||
<label className="gf-form-label gf-form-label--grow">
|
||||
<a onClick={this.onToggleDisplayAdvanced}>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import React, { FC } from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import { MetricSelect } from 'app/core/components/Select/MetricSelect';
|
||||
import { alignmentPeriods, alignOptions } from '../constants';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Segment } from '@grafana/ui';
|
||||
import { alignmentPeriods, alignOptions } from '../constants';
|
||||
|
||||
export interface Props {
|
||||
onChange: (alignmentPeriod: any) => void;
|
||||
templateSrv: TemplateSrv;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
alignmentPeriod: string;
|
||||
perSeriesAligner: string;
|
||||
usedAlignmentPeriod: string;
|
||||
@@ -17,36 +19,38 @@ export interface Props {
|
||||
export const AlignmentPeriods: FC<Props> = ({
|
||||
alignmentPeriod,
|
||||
templateSrv,
|
||||
templateVariableOptions,
|
||||
onChange,
|
||||
perSeriesAligner,
|
||||
usedAlignmentPeriod,
|
||||
}) => {
|
||||
const alignment = alignOptions.find(ap => ap.value === templateSrv.replace(perSeriesAligner));
|
||||
const formatAlignmentText = `${kbn.secondsToHms(usedAlignmentPeriod)} interval (${alignment ? alignment.text : ''})`;
|
||||
const options = alignmentPeriods.map(ap => ({
|
||||
...ap,
|
||||
label: ap.text,
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<label className="gf-form-label query-keyword width-9">Alignment Period</label>
|
||||
<MetricSelect
|
||||
onChange={onChange}
|
||||
value={alignmentPeriod}
|
||||
variables={templateSrv.variables}
|
||||
options={[
|
||||
{
|
||||
label: 'Alignment options',
|
||||
expanded: true,
|
||||
options: alignmentPeriods.map(ap => ({
|
||||
...ap,
|
||||
label: ap.text,
|
||||
})),
|
||||
},
|
||||
]}
|
||||
placeholder="Select Alignment"
|
||||
className="width-15"
|
||||
/>
|
||||
</div>
|
||||
<label className="gf-form-label query-keyword width-9">Alignment Period</label>
|
||||
<Segment
|
||||
onChange={({ value }) => onChange(value)}
|
||||
value={[...options, ...templateVariableOptions].find(s => s.value === alignmentPeriod)}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
{
|
||||
label: 'Aggregations',
|
||||
expanded: true,
|
||||
options: options,
|
||||
},
|
||||
]}
|
||||
placeholder="Select Alignment"
|
||||
></Segment>
|
||||
<div className="gf-form gf-form--grow">
|
||||
{usedAlignmentPeriod && <label className="gf-form-label gf-form-label--grow">{formatAlignmentText}</label>}
|
||||
</div>
|
||||
|
||||
@@ -1,30 +1,37 @@
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import { MetricSelect } from 'app/core/components/Select/MetricSelect';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Segment } from '@grafana/ui';
|
||||
|
||||
export interface Props {
|
||||
onChange: (perSeriesAligner: any) => void;
|
||||
templateSrv: TemplateSrv;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
alignOptions: Array<SelectableValue<string>>;
|
||||
perSeriesAligner: string;
|
||||
}
|
||||
|
||||
export const Alignments: FC<Props> = ({ perSeriesAligner, templateSrv, onChange, alignOptions }) => {
|
||||
export const Alignments: FC<Props> = ({ perSeriesAligner, templateVariableOptions, onChange, alignOptions }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form-group">
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form offset-width-9">
|
||||
<label className="gf-form-label query-keyword width-15">Aligner</label>
|
||||
<MetricSelect
|
||||
onChange={onChange}
|
||||
value={perSeriesAligner}
|
||||
variables={templateSrv.variables}
|
||||
options={alignOptions}
|
||||
<Segment
|
||||
onChange={({ value }) => onChange(value)}
|
||||
value={[...alignOptions, ...templateVariableOptions].find(s => s.value === perSeriesAligner)}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
{
|
||||
label: 'Alignment options',
|
||||
expanded: true,
|
||||
options: alignOptions,
|
||||
},
|
||||
]}
|
||||
placeholder="Select Alignment"
|
||||
className="width-15"
|
||||
/>
|
||||
></Segment>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -2,12 +2,12 @@ import React from 'react';
|
||||
import { Input } from '@grafana/ui';
|
||||
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
import StackdriverDatasource from '../datasource';
|
||||
import { Metrics } from './Metrics';
|
||||
import { Filter } from './Filter';
|
||||
import { Metrics, Filters, AnnotationsHelp } from './';
|
||||
import { toOption } from '../functions';
|
||||
import { AnnotationTarget, MetricDescriptor } from '../types';
|
||||
import { AnnotationsHelp } from './AnnotationsHelp';
|
||||
|
||||
export interface Props {
|
||||
onQueryChange: (target: AnnotationTarget) => void;
|
||||
@@ -17,6 +17,9 @@ export interface Props {
|
||||
}
|
||||
|
||||
interface State extends AnnotationTarget {
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
variableOptions: Array<SelectableValue<string>>;
|
||||
labels: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@@ -29,19 +32,32 @@ const DefaultTarget: State = {
|
||||
refId: 'annotationQuery',
|
||||
title: '',
|
||||
text: '',
|
||||
labels: {},
|
||||
variableOptionGroup: {},
|
||||
variableOptions: [],
|
||||
};
|
||||
|
||||
export class AnnotationQueryEditor extends React.Component<Props, State> {
|
||||
state: State = DefaultTarget;
|
||||
|
||||
componentDidMount() {
|
||||
const { target, datasource } = this.props;
|
||||
const variableOptionGroup = {
|
||||
label: 'Template Variables',
|
||||
options: datasource.variables.map(toOption),
|
||||
};
|
||||
|
||||
this.setState({
|
||||
...this.props.target,
|
||||
variableOptionGroup,
|
||||
variableOptions: variableOptionGroup.options,
|
||||
...target,
|
||||
});
|
||||
|
||||
datasource.getLabels(target.metricType, target.refId).then(labels => this.setState({ labels }));
|
||||
}
|
||||
|
||||
onMetricTypeChange = ({ valueType, metricKind, type, unit }: MetricDescriptor) => {
|
||||
const { onQueryChange } = this.props;
|
||||
const { onQueryChange, datasource } = this.props;
|
||||
this.setState(
|
||||
{
|
||||
metricType: type,
|
||||
@@ -53,6 +69,7 @@ export class AnnotationQueryEditor extends React.Component<Props, State> {
|
||||
onQueryChange(this.state);
|
||||
}
|
||||
);
|
||||
datasource.getLabels(type, this.state.refId).then(labels => this.setState({ labels }));
|
||||
};
|
||||
|
||||
onChange(prop: string, value: string | string[]) {
|
||||
@@ -62,28 +79,35 @@ export class AnnotationQueryEditor extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { defaultProject, metricType, filters, refId, title, text } = this.state;
|
||||
const { datasource, templateSrv } = this.props;
|
||||
const {
|
||||
defaultProject,
|
||||
metricType,
|
||||
filters,
|
||||
title,
|
||||
text,
|
||||
variableOptionGroup,
|
||||
labels,
|
||||
variableOptions,
|
||||
} = this.state;
|
||||
const { datasource } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Metrics
|
||||
defaultProject={defaultProject}
|
||||
metricType={metricType}
|
||||
templateSrv={templateSrv}
|
||||
templateSrv={datasource.templateSrv}
|
||||
datasource={datasource}
|
||||
onChange={this.onMetricTypeChange}
|
||||
templateVariableOptions={variableOptions}
|
||||
onChange={metric => this.onMetricTypeChange(metric)}
|
||||
>
|
||||
{metric => (
|
||||
<>
|
||||
<Filter
|
||||
filtersChanged={value => this.onChange('filters', value)}
|
||||
<Filters
|
||||
labels={labels}
|
||||
filters={filters}
|
||||
refId={refId}
|
||||
hideGroupBys={true}
|
||||
templateSrv={templateSrv}
|
||||
datasource={datasource}
|
||||
metricType={metric ? metric.type : ''}
|
||||
onChange={value => this.onChange('filters', value)}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import appEvents from 'app/core/app_events';
|
||||
|
||||
import { QueryMeta } from '../types';
|
||||
import { getAngularLoader, AngularComponent } from '@grafana/runtime';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import StackdriverDatasource from '../datasource';
|
||||
import '../query_filter_ctrl';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
|
||||
export interface Props {
|
||||
filtersChanged: (filters: string[]) => void;
|
||||
groupBysChanged?: (groupBys: string[]) => void;
|
||||
metricType: string;
|
||||
templateSrv: TemplateSrv;
|
||||
groupBys?: string[];
|
||||
filters: string[];
|
||||
datasource: StackdriverDatasource;
|
||||
refId: string;
|
||||
hideGroupBys: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
labelData: QueryMeta;
|
||||
loading: Promise<any>;
|
||||
}
|
||||
|
||||
const labelData: any = {
|
||||
metricLabels: {},
|
||||
resourceLabels: {},
|
||||
resourceTypes: [],
|
||||
};
|
||||
|
||||
export class Filter extends React.Component<Props, State> {
|
||||
element: any;
|
||||
component: AngularComponent;
|
||||
|
||||
async componentDidMount() {
|
||||
if (!this.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { groupBys, filters, hideGroupBys } = this.props;
|
||||
const loader = getAngularLoader();
|
||||
|
||||
const filtersChanged = (filters: string[]) => {
|
||||
this.props.filtersChanged(filters);
|
||||
};
|
||||
|
||||
const groupBysChanged = (groupBys: string[]) => {
|
||||
this.props.groupBysChanged(groupBys);
|
||||
};
|
||||
|
||||
const scopeProps: any = {
|
||||
loading: null,
|
||||
labelData,
|
||||
groupBys,
|
||||
filters,
|
||||
filtersChanged,
|
||||
groupBysChanged,
|
||||
hideGroupBys,
|
||||
};
|
||||
const loading = this.loadLabels(scopeProps);
|
||||
scopeProps.loading = loading;
|
||||
const template = `<stackdriver-filter
|
||||
filters="filters"
|
||||
group-bys="groupBys"
|
||||
label-data="labelData"
|
||||
loading="loading"
|
||||
filters-changed="filtersChanged(filters)"
|
||||
group-bys-changed="groupBysChanged(groupBys)"
|
||||
hide-group-bys="hideGroupBys"/>`;
|
||||
this.component = loader.load(this.element, scopeProps, template);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
if (!this.element) {
|
||||
return;
|
||||
}
|
||||
const scope = this.component.getScope();
|
||||
if (prevProps.metricType !== this.props.metricType) {
|
||||
scope.loading = this.loadLabels(scope);
|
||||
}
|
||||
scope.filters = this.props.filters;
|
||||
scope.groupBys = this.props.groupBys;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.component) {
|
||||
this.component.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
async loadLabels(scope: any) {
|
||||
return new Promise(async resolve => {
|
||||
try {
|
||||
if (!this.props.metricType) {
|
||||
scope.labelData = labelData;
|
||||
} else {
|
||||
const { meta } = await this.props.datasource.getLabels(this.props.metricType, this.props.refId);
|
||||
scope.labelData = meta;
|
||||
}
|
||||
resolve();
|
||||
} catch (error) {
|
||||
appEvents.emit(AppEvents.alertError, ['Error', 'Error loading metric labels for ' + this.props.metricType]);
|
||||
scope.labelData = labelData;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div ref={element => (this.element = element)} style={{ width: '100%' }} />;
|
||||
}
|
||||
}
|
||||
103
public/app/plugins/datasource/stackdriver/components/Filters.tsx
Normal file
103
public/app/plugins/datasource/stackdriver/components/Filters.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import React, { FunctionComponent, Fragment } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Segment } from '@grafana/ui';
|
||||
import { labelsToGroupedOptions, toOption } from '../functions';
|
||||
import { Filter } from '../types';
|
||||
|
||||
export interface Props {
|
||||
labels: { [key: string]: string[] };
|
||||
filters: string[];
|
||||
onChange: (filters: string[]) => void;
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
}
|
||||
|
||||
const removeText = '-- remove filter --';
|
||||
const removeOption: SelectableValue<string> = { label: removeText, value: removeText, icon: 'fa fa-remove' };
|
||||
const operators = ['=', '!=', '=~', '!=~'];
|
||||
const filtersToStringArray = (filters: Filter[]) =>
|
||||
_.flatten(filters.map(({ key, operator, value, condition }) => [key, operator, value, condition]));
|
||||
|
||||
const stringArrayToFilters = (filterArray: string[]) =>
|
||||
_.chunk(filterArray, 4).map(([key, operator, value, condition = 'AND']) => ({
|
||||
key,
|
||||
operator,
|
||||
value,
|
||||
condition,
|
||||
}));
|
||||
|
||||
export const Filters: FunctionComponent<Props> = ({
|
||||
labels = {},
|
||||
filters: filterArray,
|
||||
onChange,
|
||||
variableOptionGroup,
|
||||
}) => {
|
||||
const filters = stringArrayToFilters(filterArray);
|
||||
|
||||
const options = [removeOption, variableOptionGroup, ...labelsToGroupedOptions(Object.keys(labels))];
|
||||
|
||||
return (
|
||||
<div className="gf-form-inline">
|
||||
<label className="gf-form-label query-keyword width-9">Filter</label>
|
||||
{filters.map(({ key, operator, value, condition }, index) => (
|
||||
<Fragment key={index}>
|
||||
<Segment
|
||||
allowCustomValue
|
||||
value={key}
|
||||
options={options}
|
||||
onChange={({ value: key }) => {
|
||||
if (key === removeText) {
|
||||
onChange(filtersToStringArray(filters.filter((_, i) => i !== index)));
|
||||
} else {
|
||||
onChange(
|
||||
filtersToStringArray(
|
||||
filters.map((f, i) => (i === index ? { key, operator, condition, value: '' } : f))
|
||||
)
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Segment
|
||||
value={operator}
|
||||
className="gf-form-label query-segment-operator"
|
||||
options={operators.map(toOption)}
|
||||
onChange={({ value: operator }) =>
|
||||
onChange(filtersToStringArray(filters.map((f, i) => (i === index ? { ...f, operator } : f))))
|
||||
}
|
||||
/>
|
||||
<Segment
|
||||
allowCustomValue
|
||||
value={value}
|
||||
placeholder="add filter value"
|
||||
options={
|
||||
labels.hasOwnProperty(key) ? [variableOptionGroup, ...labels[key].map(toOption)] : [variableOptionGroup]
|
||||
}
|
||||
onChange={({ value }) =>
|
||||
onChange(filtersToStringArray(filters.map((f, i) => (i === index ? { ...f, value } : f))))
|
||||
}
|
||||
/>
|
||||
{filters.length > 1 && index + 1 !== filters.length && (
|
||||
<label className="gf-form-label query-keyword">{condition}</label>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
{Object.values(filters).every(({ value }) => value) && (
|
||||
<Segment
|
||||
allowCustomValue
|
||||
Component={
|
||||
<a className="gf-form-label query-part">
|
||||
<i className="fa fa-plus" />
|
||||
</a>
|
||||
}
|
||||
options={[variableOptionGroup, ...labelsToGroupedOptions(Object.keys(labels))]}
|
||||
onChange={({ value: key }) =>
|
||||
onChange(filtersToStringArray([...filters, { key, operator: '=', condition: 'AND', value: '' } as Filter]))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<div className="gf-form gf-form--grow">
|
||||
<label className="gf-form-label gf-form-label--grow"></label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Segment } from '@grafana/ui';
|
||||
import { labelsToGroupedOptions } from '../functions';
|
||||
import { systemLabels } from '../constants';
|
||||
|
||||
export interface Props {
|
||||
values: string[];
|
||||
onChange: (values: string[]) => void;
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
groupBys: string[];
|
||||
}
|
||||
|
||||
const removeText = '-- remove group by --';
|
||||
const removeOption: SelectableValue<string> = { label: removeText, value: removeText };
|
||||
|
||||
export const GroupBys: FunctionComponent<Props> = ({ groupBys = [], values = [], onChange, variableOptionGroup }) => {
|
||||
const options = [removeOption, variableOptionGroup, ...labelsToGroupedOptions([...groupBys, ...systemLabels])];
|
||||
return (
|
||||
<div className="gf-form-inline">
|
||||
<label className="gf-form-label query-keyword width-9">Group By</label>
|
||||
{values &&
|
||||
values.map((value, index) => (
|
||||
<Segment
|
||||
allowCustomValue
|
||||
key={value + index}
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={({ value }) =>
|
||||
onChange(
|
||||
value === removeText
|
||||
? values.filter((_, i) => i !== index)
|
||||
: values.map((v, i) => (i === index ? value : v))
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{values.length !== groupBys.length && (
|
||||
<Segment
|
||||
Component={
|
||||
<a className="gf-form-label query-part">
|
||||
<i className="fa fa-plus" />
|
||||
</a>
|
||||
}
|
||||
allowCustomValue
|
||||
onChange={({ value }) => onChange([...values, value])}
|
||||
options={[
|
||||
variableOptionGroup,
|
||||
...labelsToGroupedOptions([...groupBys.filter(groupBy => !values.includes(groupBy)), ...systemLabels]),
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<div className="gf-form gf-form--grow">
|
||||
<label className="gf-form-label gf-form-label--grow"></label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -97,6 +97,14 @@ export class Help extends React.Component<Props, State> {
|
||||
<li>
|
||||
<code>{`${'{{resource.label.label_name}}'}`}</code> = Resource label metadata e.g. resource.label.zone
|
||||
</li>
|
||||
<li>
|
||||
<code>{`${'{{metadata.system_labels.name}}'}`}</code> = Meta data system labels e.g.
|
||||
metadata.system_labels.name. For this to work, the needs to be included in the group by
|
||||
</li>
|
||||
<li>
|
||||
<code>{`${'{{metadata.user_labels.name}}'}`}</code> = Meta data user labels e.g.
|
||||
metadata.user_labels.name. For this to work, the needs to be included in the group by
|
||||
</li>
|
||||
<li>
|
||||
<code>{`${'{{bucket}}'}`}</code> = bucket boundary for distribution metrics when using a heatmap in
|
||||
Grafana
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import StackdriverDatasource from '../datasource';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { Segment } from '@grafana/ui';
|
||||
import { MetricDescriptor } from '../types';
|
||||
import { MetricSelect } from 'app/core/components/Select/MetricSelect';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { CoreEvents } from 'app/types';
|
||||
|
||||
export interface Props {
|
||||
onChange: (metricDescriptor: MetricDescriptor) => void;
|
||||
templateSrv: TemplateSrv;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
datasource: StackdriverDatasource;
|
||||
defaultProject: string;
|
||||
metricType: string;
|
||||
@@ -104,9 +106,9 @@ export class Metrics extends React.Component<Props, State> {
|
||||
return metricsByService;
|
||||
}
|
||||
|
||||
onServiceChange = (service: any) => {
|
||||
onServiceChange = ({ value: service }: any) => {
|
||||
const { metricDescriptors } = this.state;
|
||||
const { templateSrv, metricType } = this.props;
|
||||
const { metricType, templateSrv } = this.props;
|
||||
|
||||
const metrics = metricDescriptors
|
||||
.filter(m => m.service === templateSrv.replace(service))
|
||||
@@ -120,11 +122,11 @@ export class Metrics extends React.Component<Props, State> {
|
||||
this.setState({ service, metrics });
|
||||
|
||||
if (metrics.length > 0 && !metrics.some(m => m.value === templateSrv.replace(metricType))) {
|
||||
this.onMetricTypeChange(metrics[0].value);
|
||||
this.onMetricTypeChange(metrics[0]);
|
||||
}
|
||||
};
|
||||
|
||||
onMetricTypeChange = (value: any) => {
|
||||
onMetricTypeChange = ({ value }: any) => {
|
||||
const metricDescriptor = this.getSelectedMetricDescriptor(value);
|
||||
this.setState({ metricDescriptor });
|
||||
this.props.onChange({ ...metricDescriptor, type: value });
|
||||
@@ -139,55 +141,46 @@ export class Metrics extends React.Component<Props, State> {
|
||||
return services.length > 0 ? _.uniqBy(services, s => s.value) : [];
|
||||
}
|
||||
|
||||
getTemplateVariablesGroup() {
|
||||
return {
|
||||
label: 'Template Variables',
|
||||
options: this.props.templateSrv.variables.map(v => ({
|
||||
label: `$${v.name}`,
|
||||
value: `$${v.name}`,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { services, service, metrics } = this.state;
|
||||
const { metricType, templateSrv } = this.props;
|
||||
const { metricType, templateVariableOptions } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-9 query-keyword">Service</span>
|
||||
<MetricSelect
|
||||
onChange={this.onServiceChange}
|
||||
value={service}
|
||||
options={services}
|
||||
placeholder="Select Services"
|
||||
className="width-15"
|
||||
/>
|
||||
</div>
|
||||
<span className="gf-form-label width-9 query-keyword">Service</span>
|
||||
<Segment
|
||||
onChange={this.onServiceChange}
|
||||
value={[...services, ...templateVariableOptions].find(s => s.value === service)}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
...services,
|
||||
]}
|
||||
placeholder="Select Services"
|
||||
></Segment>
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label gf-form-label--grow" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-9 query-keyword">Metric</span>
|
||||
<MetricSelect
|
||||
onChange={this.onMetricTypeChange}
|
||||
value={metricType}
|
||||
variables={templateSrv.variables}
|
||||
options={[
|
||||
{
|
||||
label: 'Metrics',
|
||||
expanded: true,
|
||||
options: metrics,
|
||||
},
|
||||
]}
|
||||
placeholder="Select Metric"
|
||||
className="width-26"
|
||||
/>
|
||||
</div>
|
||||
<span className="gf-form-label width-9 query-keyword">Metric</span>
|
||||
|
||||
<Segment
|
||||
className="query-part"
|
||||
onChange={this.onMetricTypeChange}
|
||||
value={[...metrics, ...templateVariableOptions].find(s => s.value === metricType)}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
...metrics,
|
||||
]}
|
||||
placeholder="Select Metric"
|
||||
></Segment>
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label gf-form-label--grow" />
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,8 @@ const props: Props = {
|
||||
datasource: {
|
||||
getDefaultProject: () => Promise.resolve('project'),
|
||||
getMetricTypes: () => Promise.resolve([]),
|
||||
getLabels: () => Promise.resolve([]),
|
||||
variables: [],
|
||||
} as any,
|
||||
templateSrv: new TemplateSrv(),
|
||||
};
|
||||
|
||||
@@ -3,15 +3,9 @@ import _ from 'lodash';
|
||||
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
|
||||
import { Metrics } from './Metrics';
|
||||
import { Filter } from './Filter';
|
||||
import { Aggregations } from './Aggregations';
|
||||
import { Alignments } from './Alignments';
|
||||
import { AlignmentPeriods } from './AlignmentPeriods';
|
||||
import { AliasBy } from './AliasBy';
|
||||
import { Help } from './Help';
|
||||
import { Aggregations, Metrics, Filters, GroupBys, Alignments, AlignmentPeriods, AliasBy, Help } from './';
|
||||
import { StackdriverQuery, MetricDescriptor } from '../types';
|
||||
import { getAlignmentPickerData } from '../functions';
|
||||
import { getAlignmentPickerData, toOption } from '../functions';
|
||||
import StackdriverDatasource from '../datasource';
|
||||
import { TimeSeries, SelectableValue } from '@grafana/data';
|
||||
import { PanelEvents } from '@grafana/data';
|
||||
@@ -26,9 +20,12 @@ export interface Props {
|
||||
}
|
||||
|
||||
interface State extends StackdriverQuery {
|
||||
variableOptions: Array<SelectableValue<string>>;
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
alignOptions: Array<SelectableValue<string>>;
|
||||
lastQuery: string;
|
||||
lastQueryError: string;
|
||||
labels: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@@ -45,26 +42,39 @@ export const DefaultTarget: State = {
|
||||
perSeriesAligner: 'ALIGN_MEAN',
|
||||
groupBys: [],
|
||||
filters: [],
|
||||
filter: [],
|
||||
aliasBy: '',
|
||||
alignOptions: [],
|
||||
lastQuery: '',
|
||||
lastQueryError: '',
|
||||
usedAlignmentPeriod: '',
|
||||
labels: {},
|
||||
variableOptionGroup: {},
|
||||
variableOptions: [],
|
||||
};
|
||||
|
||||
export class QueryEditor extends React.Component<Props, State> {
|
||||
state: State = DefaultTarget;
|
||||
|
||||
componentDidMount() {
|
||||
const { events, target, templateSrv } = this.props;
|
||||
async componentDidMount() {
|
||||
const { events, target, templateSrv, datasource } = this.props;
|
||||
events.on(PanelEvents.dataReceived, this.onDataReceived.bind(this));
|
||||
events.on(PanelEvents.dataError, this.onDataError.bind(this));
|
||||
const { perSeriesAligner, alignOptions } = getAlignmentPickerData(target, templateSrv);
|
||||
const variableOptionGroup = {
|
||||
label: 'Template Variables',
|
||||
expanded: false,
|
||||
options: datasource.variables.map(toOption),
|
||||
};
|
||||
this.setState({
|
||||
...this.props.target,
|
||||
alignOptions,
|
||||
perSeriesAligner,
|
||||
variableOptionGroup,
|
||||
variableOptions: variableOptionGroup.options,
|
||||
});
|
||||
|
||||
datasource.getLabels(target.metricType, target.refId, target.groupBys).then(labels => this.setState({ labels }));
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -102,12 +112,13 @@ export class QueryEditor extends React.Component<Props, State> {
|
||||
this.setState({ lastQuery, lastQueryError });
|
||||
}
|
||||
|
||||
onMetricTypeChange = ({ valueType, metricKind, type, unit }: MetricDescriptor) => {
|
||||
const { templateSrv, onQueryChange, onExecuteQuery } = this.props;
|
||||
onMetricTypeChange = async ({ valueType, metricKind, type, unit }: MetricDescriptor) => {
|
||||
const { templateSrv, onQueryChange, onExecuteQuery, target } = this.props;
|
||||
const { perSeriesAligner, alignOptions } = getAlignmentPickerData(
|
||||
{ valueType, metricKind, perSeriesAligner: this.state.perSeriesAligner },
|
||||
templateSrv
|
||||
);
|
||||
const labels = await this.props.datasource.getLabels(type, target.refId, target.groupBys);
|
||||
this.setState(
|
||||
{
|
||||
alignOptions,
|
||||
@@ -116,6 +127,7 @@ export class QueryEditor extends React.Component<Props, State> {
|
||||
unit,
|
||||
valueType,
|
||||
metricKind,
|
||||
labels,
|
||||
},
|
||||
() => {
|
||||
onQueryChange(this.state);
|
||||
@@ -124,6 +136,15 @@ export class QueryEditor extends React.Component<Props, State> {
|
||||
);
|
||||
};
|
||||
|
||||
onGroupBysChange(value: string[]) {
|
||||
const { target, datasource } = this.props;
|
||||
this.setState({ groupBys: value }, () => {
|
||||
this.props.onQueryChange(this.state);
|
||||
this.props.onExecuteQuery();
|
||||
});
|
||||
datasource.getLabels(target.metricType, target.refId, value).then(labels => this.setState({ labels }));
|
||||
}
|
||||
|
||||
onPropertyChange(prop: string, value: string[]) {
|
||||
this.setState({ [prop]: value }, () => {
|
||||
this.props.onQueryChange(this.state);
|
||||
@@ -145,35 +166,39 @@ export class QueryEditor extends React.Component<Props, State> {
|
||||
aliasBy,
|
||||
lastQuery,
|
||||
lastQueryError,
|
||||
refId,
|
||||
labels,
|
||||
variableOptionGroup,
|
||||
variableOptions,
|
||||
} = this.state;
|
||||
const { datasource, templateSrv } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Metrics
|
||||
templateSrv={templateSrv}
|
||||
defaultProject={defaultProject}
|
||||
metricType={metricType}
|
||||
templateSrv={templateSrv}
|
||||
templateVariableOptions={variableOptions}
|
||||
datasource={datasource}
|
||||
onChange={this.onMetricTypeChange}
|
||||
>
|
||||
{metric => (
|
||||
<>
|
||||
<Filter
|
||||
filtersChanged={value => this.onPropertyChange('filters', value)}
|
||||
groupBysChanged={value => this.onPropertyChange('groupBys', value)}
|
||||
<Filters
|
||||
labels={labels}
|
||||
filters={filters}
|
||||
groupBys={groupBys}
|
||||
refId={refId}
|
||||
hideGroupBys={false}
|
||||
templateSrv={templateSrv}
|
||||
datasource={datasource}
|
||||
metricType={metric ? metric.type : ''}
|
||||
onChange={value => this.onPropertyChange('filters', value)}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
/>
|
||||
<GroupBys
|
||||
groupBys={Object.keys(labels)}
|
||||
values={groupBys}
|
||||
onChange={this.onGroupBysChange.bind(this)}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
/>
|
||||
<Aggregations
|
||||
metricDescriptor={metric}
|
||||
templateSrv={templateSrv}
|
||||
templateVariableOptions={variableOptions}
|
||||
crossSeriesReducer={crossSeriesReducer}
|
||||
groupBys={groupBys}
|
||||
onChange={value => this.onPropertyChange('crossSeriesReducer', value)}
|
||||
@@ -182,7 +207,7 @@ export class QueryEditor extends React.Component<Props, State> {
|
||||
displayAdvancedOptions && (
|
||||
<Alignments
|
||||
alignOptions={alignOptions}
|
||||
templateSrv={templateSrv}
|
||||
templateVariableOptions={variableOptions}
|
||||
perSeriesAligner={perSeriesAligner}
|
||||
onChange={value => this.onPropertyChange('perSeriesAligner', value)}
|
||||
/>
|
||||
@@ -191,6 +216,7 @@ export class QueryEditor extends React.Component<Props, State> {
|
||||
</Aggregations>
|
||||
<AlignmentPeriods
|
||||
templateSrv={templateSrv}
|
||||
templateVariableOptions={variableOptions}
|
||||
alignmentPeriod={alignmentPeriod}
|
||||
perSeriesAligner={perSeriesAligner}
|
||||
usedAlignmentPeriod={usedAlignmentPeriod}
|
||||
|
||||
@@ -7,7 +7,7 @@ interface Props {
|
||||
label: string;
|
||||
}
|
||||
|
||||
const SimpleSelect: FC<Props> = props => {
|
||||
export const SimpleSelect: FC<Props> = props => {
|
||||
const { label, onValueChange, value, options } = props;
|
||||
return (
|
||||
<div className="gf-form max-width-21">
|
||||
@@ -24,5 +24,3 @@ const SimpleSelect: FC<Props> = props => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SimpleSelect;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { ChangeEvent, PureComponent } from 'react';
|
||||
import { VariableQueryProps } from 'app/types/plugins';
|
||||
import SimpleSelect from './SimpleSelect';
|
||||
import { SimpleSelect } from './';
|
||||
import { getMetricTypes, getLabelKeys, extractServicesFromMetricDescriptors } from '../functions';
|
||||
import { MetricFindQueryTypes, VariableQueryData } from '../types';
|
||||
|
||||
|
||||
@@ -5,99 +5,20 @@ Array [
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label query-keyword width-9"
|
||||
>
|
||||
Aggregation
|
||||
</label>
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<label
|
||||
className="gf-form-label query-keyword width-9"
|
||||
<a
|
||||
className="gf-form-label query-part query-placeholder"
|
||||
>
|
||||
Aggregation
|
||||
</label>
|
||||
<div>
|
||||
<div
|
||||
className="css-0 gf-form-input gf-form-input--form-dropdown width-15"
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__control"
|
||||
onMouseDown={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__value-container"
|
||||
>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__placeholder"
|
||||
>
|
||||
Select Reducer
|
||||
</div>
|
||||
<div
|
||||
className="css-0"
|
||||
>
|
||||
<div
|
||||
className="gf-form-select-box__input"
|
||||
style={
|
||||
Object {
|
||||
"display": "inline-block",
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
autoCapitalize="none"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
disabled={false}
|
||||
id="react-select-2-input"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
spellCheck="false"
|
||||
style={
|
||||
Object {
|
||||
"background": 0,
|
||||
"border": 0,
|
||||
"boxSizing": "content-box",
|
||||
"color": "inherit",
|
||||
"fontSize": "inherit",
|
||||
"opacity": 1,
|
||||
"outline": 0,
|
||||
"padding": 0,
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex="0"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"height": 0,
|
||||
"left": 0,
|
||||
"overflow": "scroll",
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"visibility": "hidden",
|
||||
"whiteSpace": "pre",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__indicators"
|
||||
>
|
||||
<span
|
||||
className="gf-form-select-box__select-arrow "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Select Reducer
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
|
||||
@@ -5,99 +5,20 @@ Array [
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<span
|
||||
className="gf-form-label width-9 query-keyword"
|
||||
>
|
||||
Service
|
||||
</span>
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span
|
||||
className="gf-form-label width-9 query-keyword"
|
||||
<a
|
||||
className="gf-form-label query-part query-placeholder"
|
||||
>
|
||||
Service
|
||||
</span>
|
||||
<div>
|
||||
<div
|
||||
className="css-0 gf-form-input gf-form-input--form-dropdown width-15"
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__control"
|
||||
onMouseDown={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__value-container"
|
||||
>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__placeholder"
|
||||
>
|
||||
Select Services
|
||||
</div>
|
||||
<div
|
||||
className="css-0"
|
||||
>
|
||||
<div
|
||||
className="gf-form-select-box__input"
|
||||
style={
|
||||
Object {
|
||||
"display": "inline-block",
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
autoCapitalize="none"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
disabled={false}
|
||||
id="react-select-2-input"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
spellCheck="false"
|
||||
style={
|
||||
Object {
|
||||
"background": 0,
|
||||
"border": 0,
|
||||
"boxSizing": "content-box",
|
||||
"color": "inherit",
|
||||
"fontSize": "inherit",
|
||||
"opacity": 1,
|
||||
"outline": 0,
|
||||
"padding": 0,
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex="0"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"height": 0,
|
||||
"left": 0,
|
||||
"overflow": "scroll",
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"visibility": "hidden",
|
||||
"whiteSpace": "pre",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__indicators"
|
||||
>
|
||||
<span
|
||||
className="gf-form-select-box__select-arrow "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Select Services
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
@@ -110,99 +31,20 @@ Array [
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<span
|
||||
className="gf-form-label width-9 query-keyword"
|
||||
>
|
||||
Metric
|
||||
</span>
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span
|
||||
className="gf-form-label width-9 query-keyword"
|
||||
<a
|
||||
className="gf-form-label query-part query-placeholder query-part"
|
||||
>
|
||||
Metric
|
||||
</span>
|
||||
<div>
|
||||
<div
|
||||
className="css-0 gf-form-input gf-form-input--form-dropdown width-26"
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__control"
|
||||
onMouseDown={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__value-container"
|
||||
>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__placeholder"
|
||||
>
|
||||
Select Metric
|
||||
</div>
|
||||
<div
|
||||
className="css-0"
|
||||
>
|
||||
<div
|
||||
className="gf-form-select-box__input"
|
||||
style={
|
||||
Object {
|
||||
"display": "inline-block",
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
autoCapitalize="none"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
disabled={false}
|
||||
id="react-select-3-input"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
spellCheck="false"
|
||||
style={
|
||||
Object {
|
||||
"background": 0,
|
||||
"border": 0,
|
||||
"boxSizing": "content-box",
|
||||
"color": "inherit",
|
||||
"fontSize": "inherit",
|
||||
"opacity": 1,
|
||||
"outline": 0,
|
||||
"padding": 0,
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex="0"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"height": 0,
|
||||
"left": 0,
|
||||
"overflow": "scroll",
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"visibility": "hidden",
|
||||
"whiteSpace": "pre",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__indicators"
|
||||
>
|
||||
<span
|
||||
className="gf-form-select-box__select-arrow "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Select Metric
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
@@ -212,109 +54,67 @@ Array [
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>,
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label query-keyword width-9"
|
||||
>
|
||||
Filter
|
||||
</label>
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<a
|
||||
className="gf-form-label query-part"
|
||||
>
|
||||
<i
|
||||
className="fa fa-plus"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label query-keyword width-9"
|
||||
className="gf-form-label gf-form-label--grow"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label query-keyword width-9"
|
||||
>
|
||||
Group By
|
||||
</label>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label gf-form-label--grow"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label query-keyword width-9"
|
||||
>
|
||||
Aggregation
|
||||
</label>
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<a
|
||||
className="gf-form-label query-part query-placeholder"
|
||||
>
|
||||
Aggregation
|
||||
</label>
|
||||
<div>
|
||||
<div
|
||||
className="css-0 gf-form-input gf-form-input--form-dropdown width-15"
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__control"
|
||||
onMouseDown={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__value-container"
|
||||
>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__placeholder"
|
||||
>
|
||||
Select Reducer
|
||||
</div>
|
||||
<div
|
||||
className="css-0"
|
||||
>
|
||||
<div
|
||||
className="gf-form-select-box__input"
|
||||
style={
|
||||
Object {
|
||||
"display": "inline-block",
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
autoCapitalize="none"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
disabled={false}
|
||||
id="react-select-4-input"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
spellCheck="false"
|
||||
style={
|
||||
Object {
|
||||
"background": 0,
|
||||
"border": 0,
|
||||
"boxSizing": "content-box",
|
||||
"color": "inherit",
|
||||
"fontSize": "inherit",
|
||||
"opacity": 1,
|
||||
"outline": 0,
|
||||
"padding": 0,
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex="0"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"height": 0,
|
||||
"left": 0,
|
||||
"overflow": "scroll",
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"visibility": "hidden",
|
||||
"whiteSpace": "pre",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__indicators"
|
||||
>
|
||||
<span
|
||||
className="gf-form-select-box__select-arrow "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Select Reducer
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
@@ -336,103 +136,20 @@ Array [
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label query-keyword width-9"
|
||||
>
|
||||
Alignment Period
|
||||
</label>
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<label
|
||||
className="gf-form-label query-keyword width-9"
|
||||
<a
|
||||
className="gf-form-label query-part"
|
||||
>
|
||||
Alignment Period
|
||||
</label>
|
||||
<div>
|
||||
<div
|
||||
className="css-0 gf-form-input gf-form-input--form-dropdown width-15"
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__control"
|
||||
onMouseDown={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__value-container gf-form-select-box__value-container--has-value"
|
||||
>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__single-value"
|
||||
>
|
||||
<div
|
||||
className="css-38iae9-singleValue"
|
||||
>
|
||||
stackdriver auto
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="css-0"
|
||||
>
|
||||
<div
|
||||
className="gf-form-select-box__input"
|
||||
style={
|
||||
Object {
|
||||
"display": "inline-block",
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
autoCapitalize="none"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
disabled={false}
|
||||
id="react-select-5-input"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
spellCheck="false"
|
||||
style={
|
||||
Object {
|
||||
"background": 0,
|
||||
"border": 0,
|
||||
"boxSizing": "content-box",
|
||||
"color": "inherit",
|
||||
"fontSize": "inherit",
|
||||
"opacity": 1,
|
||||
"outline": 0,
|
||||
"padding": 0,
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex="0"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"height": 0,
|
||||
"left": 0,
|
||||
"overflow": "scroll",
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"visibility": "hidden",
|
||||
"whiteSpace": "pre",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="css-0 gf-form-select-box__indicators"
|
||||
>
|
||||
<span
|
||||
className="gf-form-select-box__select-arrow "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
stackdriver auto
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export { Project } from './Project';
|
||||
export { Metrics } from './Metrics';
|
||||
export { Help } from './Help';
|
||||
export { GroupBys } from './GroupBys';
|
||||
export { Filters } from './Filters';
|
||||
export { AnnotationsHelp } from './AnnotationsHelp';
|
||||
export { Alignments } from './Alignments';
|
||||
export { AlignmentPeriods } from './AlignmentPeriods';
|
||||
export { AliasBy } from './AliasBy';
|
||||
export { Aggregations } from './Aggregations';
|
||||
export { SimpleSelect } from './SimpleSelect';
|
||||
Reference in New Issue
Block a user