mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
CloudMonitor: Remove cloudMonitoringExperimentalUI feature flag (#55054)
* CloudMonitor: remove cloudMonitoringExperimentalUI * fix: address typecheck errors * fix: fix SLO import and width cleanup * fix wrong metricType when switching datasources * fix: remove legacy SLO and fix queryType check
This commit is contained in:
parent
13014dc0df
commit
92857ef331
@ -5846,30 +5846,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||
],
|
||||
"public/app/plugins/datasource/cloud-monitoring/components/Experimental/Aggregation.test.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
|
||||
],
|
||||
"public/app/plugins/datasource/cloud-monitoring/components/Experimental/Aggregation.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"]
|
||||
],
|
||||
"public/app/plugins/datasource/cloud-monitoring/components/Experimental/AliasBy.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||
],
|
||||
"public/app/plugins/datasource/cloud-monitoring/components/Experimental/GroupBy.test.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/plugins/datasource/cloud-monitoring/components/Experimental/MetricQueryEditor.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||
],
|
||||
"public/app/plugins/datasource/cloud-monitoring/components/Experimental/Metrics.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/plugins/datasource/cloud-monitoring/components/Experimental/VisualMetricQueryEditor.tsx:5381": [
|
||||
"public/app/plugins/datasource/cloud-monitoring/components/GroupBy.test.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/plugins/datasource/cloud-monitoring/components/MQLQueryEditor.tsx:5381": [
|
||||
@ -5880,10 +5857,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||
],
|
||||
"public/app/plugins/datasource/cloud-monitoring/components/Metrics.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"]
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
|
@ -76,7 +76,6 @@ export interface QueryResultMeta {
|
||||
/**
|
||||
* Legacy data source specific, should be moved to custom
|
||||
* */
|
||||
alignmentPeriod?: number; // used by cloud monitoring
|
||||
searchWords?: string[]; // used by log models and loki
|
||||
limit?: number; // used by log models and loki
|
||||
json?: boolean; // used to keep track of old json doc values
|
||||
|
@ -57,7 +57,6 @@ export interface FeatureToggles {
|
||||
canvasPanelNesting?: boolean;
|
||||
scenes?: boolean;
|
||||
useLegacyHeatmapPanel?: boolean;
|
||||
cloudMonitoringExperimentalUI?: boolean;
|
||||
disableSecretsCompatibility?: boolean;
|
||||
logRequestsInstrumentedAsUnknown?: boolean;
|
||||
dataConnectionsConsole?: boolean;
|
||||
|
@ -232,12 +232,6 @@ var (
|
||||
Description: "Continue to use the angular/flot based heatmap panel",
|
||||
State: FeatureStateStable,
|
||||
},
|
||||
{
|
||||
Name: "cloudMonitoringExperimentalUI",
|
||||
Description: "Use grafana-experimental UI in Cloud Monitoring",
|
||||
State: FeatureStateAlpha,
|
||||
FrontendOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "disableSecretsCompatibility",
|
||||
Description: "Disable duplicated secret storage in legacy tables",
|
||||
|
@ -171,10 +171,6 @@ const (
|
||||
// Continue to use the angular/flot based heatmap panel
|
||||
FlagUseLegacyHeatmapPanel = "useLegacyHeatmapPanel"
|
||||
|
||||
// FlagCloudMonitoringExperimentalUI
|
||||
// Use grafana-experimental UI in Cloud Monitoring
|
||||
FlagCloudMonitoringExperimentalUI = "cloudMonitoringExperimentalUI"
|
||||
|
||||
// FlagDisableSecretsCompatibility
|
||||
// Disable duplicated secret storage in legacy tables
|
||||
FlagDisableSecretsCompatibility = "disableSecretsCompatibility"
|
||||
|
@ -1,13 +1,11 @@
|
||||
import React, { FC, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select } from '@grafana/ui';
|
||||
import { EditorField, Select } from '@grafana/ui';
|
||||
|
||||
import { getAggregationOptionsByMetric } from '../functions';
|
||||
import { MetricDescriptor, MetricKind, ValueTypes } from '../types';
|
||||
|
||||
import { QueryEditorField } from '.';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
onChange: (metricDescriptor: string) => void;
|
||||
@ -22,14 +20,9 @@ export const Aggregation: FC<Props> = (props) => {
|
||||
const selected = useSelectedFromOptions(aggOptions, props);
|
||||
|
||||
return (
|
||||
<QueryEditorField
|
||||
labelWidth={18}
|
||||
label="Group by function"
|
||||
data-testid="cloud-monitoring-aggregation"
|
||||
htmlFor={`${props.refId}-group-by-function`}
|
||||
>
|
||||
<EditorField label="Group by function" data-testid="cloud-monitoring-aggregation">
|
||||
<Select
|
||||
width={16}
|
||||
width="auto"
|
||||
onChange={({ value }) => props.onChange(value!)}
|
||||
value={selected}
|
||||
options={[
|
||||
@ -46,7 +39,7 @@ export const Aggregation: FC<Props> = (props) => {
|
||||
placeholder="Select Reducer"
|
||||
inputId={`${props.refId}-group-by-function`}
|
||||
/>
|
||||
</QueryEditorField>
|
||||
</EditorField>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,11 +1,7 @@
|
||||
import { debounce } from 'lodash';
|
||||
import React, { FunctionComponent, useState } from 'react';
|
||||
|
||||
import { Input } from '@grafana/ui';
|
||||
|
||||
import { INPUT_WIDTH } from '../constants';
|
||||
|
||||
import { QueryEditorRow } from '.';
|
||||
import { EditorField, Input } from '@grafana/ui';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
@ -24,8 +20,8 @@ export const AliasBy: FunctionComponent<Props> = ({ refId, value = '', onChange
|
||||
};
|
||||
|
||||
return (
|
||||
<QueryEditorRow label="Alias by" htmlFor={`${refId}-alias-by`}>
|
||||
<Input id={`${refId}-alias-by`} width={INPUT_WIDTH} value={alias} onChange={onChange} />
|
||||
</QueryEditorRow>
|
||||
<EditorField label="Alias by">
|
||||
<Input id={`${refId}-alias-by`} value={alias} onChange={onChange} />
|
||||
</EditorField>
|
||||
);
|
||||
};
|
||||
|
@ -5,9 +5,9 @@ import { openMenu } from 'react-select-event';
|
||||
|
||||
import { TemplateSrvMock } from 'app/features/templating/template_srv.mock';
|
||||
|
||||
import { createMockDatasource } from '../../__mocks__/cloudMonitoringDatasource';
|
||||
import { createMockMetricQuery } from '../../__mocks__/cloudMonitoringQuery';
|
||||
import { MetricKind, ValueTypes } from '../../types';
|
||||
import { createMockDatasource } from '../__mocks__/cloudMonitoringDatasource';
|
||||
import { createMockMetricQuery } from '../__mocks__/cloudMonitoringQuery';
|
||||
import { MetricKind, ValueTypes } from '../types';
|
||||
|
||||
import { Alignment } from './Alignment';
|
||||
|
@ -1,12 +1,15 @@
|
||||
import React, { FC } from 'react';
|
||||
import React, { FC, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorFieldGroup } from '@grafana/ui';
|
||||
|
||||
import { ALIGNMENT_PERIODS, SELECT_WIDTH } from '../constants';
|
||||
import { ALIGNMENT_PERIODS } from '../constants';
|
||||
import CloudMonitoringDatasource from '../datasource';
|
||||
import { alignmentPeriodLabel } from '../functions';
|
||||
import { CustomMetaData, MetricQuery, SLOQuery } from '../types';
|
||||
|
||||
import { AlignmentFunction, PeriodSelect, AlignmentPeriodLabel, QueryEditorField, QueryEditorRow } from '.';
|
||||
import { AlignmentFunction } from './AlignmentFunction';
|
||||
import { PeriodSelect } from './PeriodSelect';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
@ -25,29 +28,29 @@ export const Alignment: FC<Props> = ({
|
||||
customMetaData,
|
||||
datasource,
|
||||
}) => {
|
||||
const alignmentLabel = useMemo(() => alignmentPeriodLabel(customMetaData, datasource), [customMetaData, datasource]);
|
||||
return (
|
||||
<QueryEditorRow
|
||||
label="Alignment function"
|
||||
tooltip="The process of alignment consists of collecting all data points received in a fixed length of time, applying a function to combine those data points, and assigning a timestamp to the result."
|
||||
fillComponent={<AlignmentPeriodLabel datasource={datasource} customMetaData={customMetaData} />}
|
||||
htmlFor={`${refId}-alignment-function`}
|
||||
>
|
||||
<AlignmentFunction
|
||||
inputId={`${refId}-alignment-function`}
|
||||
templateVariableOptions={templateVariableOptions}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<QueryEditorField label="Alignment period" htmlFor={`${refId}-alignment-period`}>
|
||||
<EditorFieldGroup>
|
||||
<EditorField
|
||||
label="Alignment function"
|
||||
tooltip="The process of alignment consists of collecting all data points received in a fixed length of time, applying a function to combine those data points, and assigning a timestamp to the result."
|
||||
>
|
||||
<AlignmentFunction
|
||||
inputId={`${refId}-alignment-function`}
|
||||
templateVariableOptions={templateVariableOptions}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</EditorField>
|
||||
<EditorField label="Alignment period" tooltip={alignmentLabel}>
|
||||
<PeriodSelect
|
||||
inputId={`${refId}-alignment-period`}
|
||||
selectWidth={SELECT_WIDTH}
|
||||
templateVariableOptions={templateVariableOptions}
|
||||
current={query.alignmentPeriod}
|
||||
onChange={(period) => onChange({ ...query, alignmentPeriod: period })}
|
||||
aligmentPeriods={ALIGNMENT_PERIODS}
|
||||
/>
|
||||
</QueryEditorField>
|
||||
</QueryEditorRow>
|
||||
</EditorField>
|
||||
</EditorFieldGroup>
|
||||
);
|
||||
};
|
||||
|
@ -3,7 +3,6 @@ import React, { FC, useMemo } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select } from '@grafana/ui';
|
||||
|
||||
import { SELECT_WIDTH } from '../constants';
|
||||
import { getAlignmentPickerData } from '../functions';
|
||||
import { MetricQuery } from '../types';
|
||||
|
||||
@ -23,7 +22,6 @@ export const AlignmentFunction: FC<Props> = ({ inputId, query, templateVariableO
|
||||
|
||||
return (
|
||||
<Select
|
||||
width={SELECT_WIDTH}
|
||||
onChange={({ value }) => onChange({ ...query, perSeriesAligner: value! })}
|
||||
value={[...alignOptions, ...templateVariableOptions].find((s) => s.value === perSeriesAligner)}
|
||||
options={[
|
||||
@ -39,6 +37,6 @@ export const AlignmentFunction: FC<Props> = ({ inputId, query, templateVariableO
|
||||
]}
|
||||
placeholder="Select Alignment"
|
||||
inputId={inputId}
|
||||
></Select>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -2,10 +2,8 @@ import React, { useState } from 'react';
|
||||
import { useDebounce } from 'react-use';
|
||||
|
||||
import { QueryEditorProps, toOption } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { EditorField, EditorRows, Input } from '@grafana/ui';
|
||||
|
||||
import { INPUT_WIDTH } from '../constants';
|
||||
import CloudMonitoringDatasource from '../datasource';
|
||||
import {
|
||||
EditorMode,
|
||||
@ -16,10 +14,9 @@ import {
|
||||
AlignmentTypes,
|
||||
} from '../types';
|
||||
|
||||
import { MetricQueryEditor as ExperimentalMetricQueryEditor } from './Experimental/MetricQueryEditor';
|
||||
import { MetricQueryEditor } from './MetricQueryEditor';
|
||||
|
||||
import { AnnotationsHelp, QueryEditorRow } from './';
|
||||
import { AnnotationsHelp } from './';
|
||||
|
||||
export type Props = QueryEditorProps<CloudMonitoringDatasource, CloudMonitoringQuery, CloudMonitoringOptions>;
|
||||
|
||||
@ -80,44 +77,23 @@ export const AnnotationQueryEditor = (props: Props) => {
|
||||
|
||||
return (
|
||||
<EditorRows>
|
||||
{config.featureToggles.cloudMonitoringExperimentalUI ? (
|
||||
<>
|
||||
<ExperimentalMetricQueryEditor
|
||||
refId={query.refId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
customMetaData={customMetaData}
|
||||
onChange={handleQueryChange}
|
||||
onRunQuery={onRunQuery}
|
||||
datasource={datasource}
|
||||
query={metricQuery}
|
||||
/>
|
||||
<EditorField label="Title" htmlFor="annotation-query-title">
|
||||
<Input id="annotation-query-title" value={title} onChange={handleTitleChange} />
|
||||
</EditorField>
|
||||
<EditorField label="Text" htmlFor="annotation-query-text">
|
||||
<Input id="annotation-query-text" value={text} onChange={handleTextChange} />
|
||||
</EditorField>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MetricQueryEditor
|
||||
refId={query.refId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
customMetaData={customMetaData}
|
||||
onChange={handleQueryChange}
|
||||
onRunQuery={onRunQuery}
|
||||
datasource={datasource}
|
||||
query={metricQuery}
|
||||
/>
|
||||
<QueryEditorRow label="Title" htmlFor="annotation-query-title">
|
||||
<Input id="annotation-query-title" value={title} width={INPUT_WIDTH} onChange={handleTitleChange} />
|
||||
</QueryEditorRow>
|
||||
|
||||
<QueryEditorRow label="Text" htmlFor="annotation-query-text">
|
||||
<Input id="annotation-query-text" value={text} width={INPUT_WIDTH} onChange={handleTextChange} />
|
||||
</QueryEditorRow>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<MetricQueryEditor
|
||||
refId={query.refId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
customMetaData={customMetaData}
|
||||
onChange={handleQueryChange}
|
||||
onRunQuery={onRunQuery}
|
||||
datasource={datasource}
|
||||
query={metricQuery}
|
||||
/>
|
||||
<EditorField label="Title" htmlFor="annotation-query-title">
|
||||
<Input id="annotation-query-title" value={title} onChange={handleTitleChange} />
|
||||
</EditorField>
|
||||
<EditorField label="Text" htmlFor="annotation-query-text">
|
||||
<Input id="annotation-query-text" value={text} onChange={handleTextChange} />
|
||||
</EditorField>
|
||||
</>
|
||||
<AnnotationsHelp />
|
||||
</EditorRows>
|
||||
);
|
||||
|
@ -1,65 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { openMenu } from 'react-select-event';
|
||||
import { TemplateSrvStub } from 'test/specs/helpers';
|
||||
|
||||
import { ValueTypes, MetricKind } from '../../types';
|
||||
|
||||
import { Aggregation, Props } from './Aggregation';
|
||||
|
||||
const props: Props = {
|
||||
onChange: () => {},
|
||||
// @ts-ignore
|
||||
templateSrv: new TemplateSrvStub(),
|
||||
metricDescriptor: {
|
||||
valueType: '',
|
||||
metricKind: '',
|
||||
} as any,
|
||||
crossSeriesReducer: '',
|
||||
groupBys: [],
|
||||
templateVariableOptions: [],
|
||||
};
|
||||
|
||||
describe('Aggregation', () => {
|
||||
it('renders correctly', () => {
|
||||
render(<Aggregation {...props} />);
|
||||
expect(screen.getByTestId('cloud-monitoring-aggregation')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('options', () => {
|
||||
describe('when DOUBLE and GAUGE is passed as props', () => {
|
||||
const nextProps = {
|
||||
...props,
|
||||
metricDescriptor: {
|
||||
valueType: ValueTypes.DOUBLE,
|
||||
metricKind: MetricKind.GAUGE,
|
||||
} as any,
|
||||
};
|
||||
|
||||
it('should not have the reduce values', () => {
|
||||
render(<Aggregation {...nextProps} />);
|
||||
const label = screen.getByLabelText('Group by function');
|
||||
openMenu(label);
|
||||
expect(screen.queryByText('count true')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('count false')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when MONEY and CUMULATIVE is passed as props', () => {
|
||||
const nextProps = {
|
||||
...props,
|
||||
metricDescriptor: {
|
||||
valueType: ValueTypes.MONEY,
|
||||
metricKind: MetricKind.CUMULATIVE,
|
||||
} as any,
|
||||
};
|
||||
|
||||
it('should have the reduce values', () => {
|
||||
render(<Aggregation {...nextProps} />);
|
||||
const label = screen.getByLabelText('Group by function');
|
||||
openMenu(label);
|
||||
expect(screen.getByText('none')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,67 +0,0 @@
|
||||
import React, { FC, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, Select } from '@grafana/ui';
|
||||
|
||||
import { getAggregationOptionsByMetric } from '../../functions';
|
||||
import { MetricDescriptor, MetricKind, ValueTypes } from '../../types';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
onChange: (metricDescriptor: string) => void;
|
||||
metricDescriptor?: MetricDescriptor;
|
||||
crossSeriesReducer: string;
|
||||
groupBys: string[];
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
}
|
||||
|
||||
export const Aggregation: FC<Props> = (props) => {
|
||||
const aggOptions = useAggregationOptionsByMetric(props);
|
||||
const selected = useSelectedFromOptions(aggOptions, props);
|
||||
|
||||
return (
|
||||
<EditorField label="Group by function" data-testid="cloud-monitoring-aggregation">
|
||||
<Select
|
||||
width="auto"
|
||||
onChange={({ value }) => props.onChange(value!)}
|
||||
value={selected}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: props.templateVariableOptions,
|
||||
},
|
||||
{
|
||||
label: 'Aggregations',
|
||||
expanded: true,
|
||||
options: aggOptions,
|
||||
},
|
||||
]}
|
||||
placeholder="Select Reducer"
|
||||
inputId={`${props.refId}-group-by-function`}
|
||||
/>
|
||||
</EditorField>
|
||||
);
|
||||
};
|
||||
|
||||
const useAggregationOptionsByMetric = ({ metricDescriptor }: Props): Array<SelectableValue<string>> => {
|
||||
const valueType = metricDescriptor?.valueType;
|
||||
const metricKind = metricDescriptor?.metricKind;
|
||||
|
||||
return useMemo(() => {
|
||||
if (!valueType || !metricKind) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return getAggregationOptionsByMetric(valueType as ValueTypes, metricKind as MetricKind).map((a) => ({
|
||||
...a,
|
||||
label: a.text,
|
||||
}));
|
||||
}, [valueType, metricKind]);
|
||||
};
|
||||
|
||||
const useSelectedFromOptions = (aggOptions: Array<SelectableValue<string>>, props: Props) => {
|
||||
return useMemo(() => {
|
||||
const allOptions = [...aggOptions, ...props.templateVariableOptions];
|
||||
return allOptions.find((s) => s.value === props.crossSeriesReducer);
|
||||
}, [aggOptions, props.crossSeriesReducer, props.templateVariableOptions]);
|
||||
};
|
@ -1,29 +0,0 @@
|
||||
import { debounce } from 'lodash';
|
||||
import React, { FunctionComponent, useState } from 'react';
|
||||
|
||||
import { EditorField, Input } from '@grafana/ui';
|
||||
|
||||
import { SELECT_WIDTH } from '../../constants';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
onChange: (alias: any) => void;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export const AliasBy: FunctionComponent<Props> = ({ refId, value = '', onChange }) => {
|
||||
const [alias, setAlias] = useState(value ?? '');
|
||||
|
||||
const propagateOnChange = debounce(onChange, 1000);
|
||||
|
||||
onChange = (e: any) => {
|
||||
setAlias(e.target.value);
|
||||
propagateOnChange(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<EditorField label="Alias by">
|
||||
<Input id={`${refId}-alias-by`} width={SELECT_WIDTH} value={alias} onChange={onChange} />
|
||||
</EditorField>
|
||||
);
|
||||
};
|
@ -1,56 +0,0 @@
|
||||
import React, { FC, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorFieldGroup } from '@grafana/ui';
|
||||
|
||||
import { ALIGNMENT_PERIODS } from '../../constants';
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { alignmentPeriodLabel } from '../../functions';
|
||||
import { CustomMetaData, MetricQuery, SLOQuery } from '../../types';
|
||||
|
||||
import { AlignmentFunction } from './AlignmentFunction';
|
||||
import { PeriodSelect } from './PeriodSelect';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
onChange: (query: MetricQuery | SLOQuery) => void;
|
||||
query: MetricQuery;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
customMetaData: CustomMetaData;
|
||||
datasource: CloudMonitoringDatasource;
|
||||
}
|
||||
|
||||
export const Alignment: FC<Props> = ({
|
||||
refId,
|
||||
templateVariableOptions,
|
||||
onChange,
|
||||
query,
|
||||
customMetaData,
|
||||
datasource,
|
||||
}) => {
|
||||
const alignmentLabel = useMemo(() => alignmentPeriodLabel(customMetaData, datasource), [customMetaData, datasource]);
|
||||
return (
|
||||
<EditorFieldGroup>
|
||||
<EditorField
|
||||
label="Alignment function"
|
||||
tooltip="The process of alignment consists of collecting all data points received in a fixed length of time, applying a function to combine those data points, and assigning a timestamp to the result."
|
||||
>
|
||||
<AlignmentFunction
|
||||
inputId={`${refId}-alignment-function`}
|
||||
templateVariableOptions={templateVariableOptions}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</EditorField>
|
||||
<EditorField label="Alignment period" tooltip={alignmentLabel}>
|
||||
<PeriodSelect
|
||||
inputId={`${refId}-alignment-period`}
|
||||
templateVariableOptions={templateVariableOptions}
|
||||
current={query.alignmentPeriod}
|
||||
onChange={(period) => onChange({ ...query, alignmentPeriod: period })}
|
||||
aligmentPeriods={ALIGNMENT_PERIODS}
|
||||
/>
|
||||
</EditorField>
|
||||
</EditorFieldGroup>
|
||||
);
|
||||
};
|
@ -1,42 +0,0 @@
|
||||
import React, { FC, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select } from '@grafana/ui';
|
||||
|
||||
import { getAlignmentPickerData } from '../../functions';
|
||||
import { MetricQuery } from '../../types';
|
||||
|
||||
export interface Props {
|
||||
inputId: string;
|
||||
onChange: (query: MetricQuery) => void;
|
||||
query: MetricQuery;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
}
|
||||
|
||||
export const AlignmentFunction: FC<Props> = ({ inputId, query, templateVariableOptions, onChange }) => {
|
||||
const { valueType, metricKind, perSeriesAligner: psa, preprocessor } = query;
|
||||
const { perSeriesAligner, alignOptions } = useMemo(
|
||||
() => getAlignmentPickerData(valueType, metricKind, psa, preprocessor),
|
||||
[valueType, metricKind, psa, preprocessor]
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
onChange={({ value }) => onChange({ ...query, perSeriesAligner: 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"
|
||||
inputId={inputId}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,39 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { select } from 'react-select-event';
|
||||
|
||||
import { GraphPeriod, Props } from './GraphPeriod';
|
||||
|
||||
const props: Props = {
|
||||
onChange: jest.fn(),
|
||||
refId: 'A',
|
||||
variableOptionGroup: { options: [] },
|
||||
};
|
||||
|
||||
describe('Graph Period', () => {
|
||||
it('should enable graph_period by default', () => {
|
||||
render(<GraphPeriod {...props} />);
|
||||
expect(screen.getByLabelText('Graph period')).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('should disable graph_period when toggled', async () => {
|
||||
const onChange = jest.fn();
|
||||
render(<GraphPeriod {...props} onChange={onChange} />);
|
||||
const s = screen.getByTestId('A-switch-graph-period');
|
||||
await userEvent.click(s);
|
||||
expect(onChange).toHaveBeenCalledWith('disabled');
|
||||
});
|
||||
|
||||
it('should set a different value when selected', async () => {
|
||||
const onChange = jest.fn();
|
||||
render(<GraphPeriod {...props} onChange={onChange} />);
|
||||
const selectEl = screen.getByLabelText('Graph period');
|
||||
expect(selectEl).toBeInTheDocument();
|
||||
|
||||
await select(selectEl, '1m', {
|
||||
container: document.body,
|
||||
});
|
||||
expect(onChange).toHaveBeenCalledWith('1m');
|
||||
});
|
||||
});
|
@ -1,48 +0,0 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorRow, HorizontalGroup, Switch } from '@grafana/ui';
|
||||
|
||||
import { GRAPH_PERIODS, SELECT_WIDTH } from '../../constants';
|
||||
import { PeriodSelect } from '../index';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
onChange: (period: string) => void;
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
graphPeriod?: string;
|
||||
}
|
||||
|
||||
export const GraphPeriod: FunctionComponent<Props> = ({ refId, onChange, graphPeriod, variableOptionGroup }) => {
|
||||
return (
|
||||
<EditorRow>
|
||||
<EditorField
|
||||
label="Graph period"
|
||||
htmlFor={`${refId}-graph-period`}
|
||||
tooltip={
|
||||
<>
|
||||
Set <code>graph_period</code> which forces a preferred period between points. Automatically set to the
|
||||
current interval if left blank.
|
||||
</>
|
||||
}
|
||||
>
|
||||
<HorizontalGroup>
|
||||
<Switch
|
||||
data-testid={`${refId}-switch-graph-period`}
|
||||
value={graphPeriod !== 'disabled'}
|
||||
onChange={(e) => onChange(e.currentTarget.checked ? '' : 'disabled')}
|
||||
/>
|
||||
<PeriodSelect
|
||||
inputId={`${refId}-graph-period`}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
current={graphPeriod}
|
||||
onChange={onChange}
|
||||
selectWidth={SELECT_WIDTH}
|
||||
disabled={graphPeriod === 'disabled'}
|
||||
aligmentPeriods={GRAPH_PERIODS}
|
||||
/>
|
||||
</HorizontalGroup>
|
||||
</EditorField>
|
||||
</EditorRow>
|
||||
);
|
||||
};
|
@ -1,61 +0,0 @@
|
||||
import React, { FunctionComponent, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorFieldGroup, MultiSelect } from '@grafana/ui';
|
||||
|
||||
import { SYSTEM_LABELS } from '../../constants';
|
||||
import { labelsToGroupedOptions } from '../../functions';
|
||||
import { MetricDescriptor, MetricQuery } from '../../types';
|
||||
|
||||
import { Aggregation } from './Aggregation';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
labels: string[];
|
||||
metricDescriptor?: MetricDescriptor;
|
||||
onChange: (query: MetricQuery) => void;
|
||||
query: MetricQuery;
|
||||
}
|
||||
|
||||
export const GroupBy: FunctionComponent<Props> = ({
|
||||
refId,
|
||||
labels: groupBys = [],
|
||||
query,
|
||||
onChange,
|
||||
variableOptionGroup,
|
||||
metricDescriptor,
|
||||
}) => {
|
||||
const options = useMemo(
|
||||
() => [variableOptionGroup, ...labelsToGroupedOptions([...groupBys, ...SYSTEM_LABELS])],
|
||||
[groupBys, variableOptionGroup]
|
||||
);
|
||||
|
||||
return (
|
||||
<EditorFieldGroup>
|
||||
<EditorField
|
||||
label="Group by"
|
||||
tooltip="You can reduce the amount of data returned for a metric by combining different time series. To combine multiple time series, you can specify a grouping and a function. Grouping is done on the basis of labels. The grouping function is used to combine the time series in the group into a single time series."
|
||||
>
|
||||
<MultiSelect
|
||||
inputId={`${refId}-group-by`}
|
||||
width="auto"
|
||||
placeholder="Choose label"
|
||||
options={options}
|
||||
value={query.groupBys ?? []}
|
||||
onChange={(options) => {
|
||||
onChange({ ...query, groupBys: options.map((o) => o.value!) });
|
||||
}}
|
||||
/>
|
||||
</EditorField>
|
||||
<Aggregation
|
||||
metricDescriptor={metricDescriptor}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
crossSeriesReducer={query.crossSeriesReducer}
|
||||
groupBys={query.groupBys ?? []}
|
||||
onChange={(crossSeriesReducer) => onChange({ ...query, crossSeriesReducer })}
|
||||
refId={refId}
|
||||
/>
|
||||
</EditorFieldGroup>
|
||||
);
|
||||
};
|
@ -1,118 +0,0 @@
|
||||
import React, { FunctionComponent, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue, toOption } from '@grafana/data';
|
||||
import { AccessoryButton, EditorField, EditorList, EditorRow, HorizontalGroup, Select } from '@grafana/ui';
|
||||
|
||||
import { labelsToGroupedOptions, stringArrayToFilters } from '../../functions';
|
||||
|
||||
export interface Props {
|
||||
labels: { [key: string]: string[] };
|
||||
filters: string[];
|
||||
onChange: (filters: string[]) => void;
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
}
|
||||
|
||||
interface Filter {
|
||||
key: string;
|
||||
operator: string;
|
||||
value: string;
|
||||
condition: string;
|
||||
}
|
||||
|
||||
const DEFAULT_OPERATOR = '=';
|
||||
const DEFAULT_CONDITION = 'AND';
|
||||
|
||||
const filtersToStringArray = (filters: Filter[]) =>
|
||||
filters.flatMap(({ key, operator, value, condition }) => [key, operator, value, condition]).slice(0, -1);
|
||||
|
||||
const operators = ['=', '!=', '=~', '!=~'].map(toOption);
|
||||
|
||||
export const LabelFilter: FunctionComponent<Props> = ({
|
||||
labels = {},
|
||||
filters: filterArray,
|
||||
onChange: _onChange,
|
||||
variableOptionGroup,
|
||||
}) => {
|
||||
const filters: Filter[] = useMemo(() => stringArrayToFilters(filterArray), [filterArray]);
|
||||
const options = useMemo(
|
||||
() => [variableOptionGroup, ...labelsToGroupedOptions(Object.keys(labels))],
|
||||
[labels, variableOptionGroup]
|
||||
);
|
||||
|
||||
const getOptions = ({ key = '', value = '' }: Partial<Filter>) => {
|
||||
// Add the current key and value as options if they are manually entered
|
||||
const keyPresent = options.some((op) => {
|
||||
if (op.options) {
|
||||
return options.some((opp) => opp.label === key);
|
||||
}
|
||||
return op.label === key;
|
||||
});
|
||||
if (!keyPresent) {
|
||||
options.push({ label: key, value: key });
|
||||
}
|
||||
|
||||
const valueOptions = labels.hasOwnProperty(key)
|
||||
? [variableOptionGroup, ...labels[key].map(toOption)]
|
||||
: [variableOptionGroup];
|
||||
const valuePresent = valueOptions.some((op) => op.label === value);
|
||||
if (!valuePresent) {
|
||||
valueOptions.push({ label: value, value });
|
||||
}
|
||||
|
||||
return { options, valueOptions };
|
||||
};
|
||||
|
||||
const onChange = (items: Array<Partial<Filter>>) => {
|
||||
const filters = items.map(({ key, operator, value, condition }) => ({
|
||||
key: key || '',
|
||||
operator: operator || DEFAULT_OPERATOR,
|
||||
value: value || '',
|
||||
condition: condition || DEFAULT_CONDITION,
|
||||
}));
|
||||
_onChange(filtersToStringArray(filters));
|
||||
};
|
||||
|
||||
const renderItem = (item: Partial<Filter>, onChangeItem: (item: Filter) => void, onDeleteItem: () => void) => {
|
||||
const { key = '', operator = DEFAULT_OPERATOR, value = '', condition = DEFAULT_CONDITION } = item;
|
||||
const { options, valueOptions } = getOptions(item);
|
||||
|
||||
return (
|
||||
<HorizontalGroup spacing="xs" width="auto">
|
||||
<Select
|
||||
aria-label="Filter label key"
|
||||
formatCreateLabel={(v) => `Use label key: ${v}`}
|
||||
allowCustomValue
|
||||
value={key}
|
||||
options={options}
|
||||
onChange={({ value: key = '' }) => onChangeItem({ key, operator, value, condition })}
|
||||
/>
|
||||
<Select
|
||||
value={operator}
|
||||
options={operators}
|
||||
onChange={({ value: operator = DEFAULT_OPERATOR }) => onChangeItem({ key, operator, value, condition })}
|
||||
/>
|
||||
<Select
|
||||
aria-label="Filter label value"
|
||||
placeholder="add filter value"
|
||||
formatCreateLabel={(v) => `Use label value: ${v}`}
|
||||
allowCustomValue
|
||||
value={value}
|
||||
options={valueOptions}
|
||||
onChange={({ value = '' }) => onChangeItem({ key, operator, value, condition })}
|
||||
/>
|
||||
<AccessoryButton aria-label="Remove" icon="times" variant="secondary" onClick={onDeleteItem} type="button" />
|
||||
</HorizontalGroup>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<EditorRow>
|
||||
<EditorField
|
||||
label="Filter"
|
||||
tooltip="To reduce the amount of data charted, apply a filter. A filter has three components: a label, a comparison, and a value. The comparison can be an equality, inequality, or regular expression."
|
||||
>
|
||||
<EditorList items={filters} renderItem={renderItem} onChange={onChange} />
|
||||
</EditorField>
|
||||
</EditorRow>
|
||||
);
|
||||
};
|
@ -1,40 +0,0 @@
|
||||
import { render, screen, act } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { TemplateSrvMock } from 'app/features/templating/template_srv.mock';
|
||||
|
||||
import { createMockDatasource } from '../../__mocks__/cloudMonitoringDatasource';
|
||||
import { createMockMetricQuery } from '../../__mocks__/cloudMonitoringQuery';
|
||||
|
||||
import { MetricQueryEditor, Props } from './MetricQueryEditor';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getTemplateSrv: () => new TemplateSrvMock({}),
|
||||
}));
|
||||
|
||||
const props: Props = {
|
||||
onChange: jest.fn(),
|
||||
refId: 'refId',
|
||||
customMetaData: {},
|
||||
onRunQuery: jest.fn(),
|
||||
datasource: createMockDatasource(),
|
||||
variableOptionGroup: { options: [] },
|
||||
query: createMockMetricQuery(),
|
||||
};
|
||||
|
||||
describe('Cloud monitoring: Metric Query Editor', () => {
|
||||
it('shoud render Project selector', async () => {
|
||||
await act(async () => {
|
||||
const originalValue = config.featureToggles.cloudMonitoringExperimentalUI;
|
||||
config.featureToggles.cloudMonitoringExperimentalUI = true;
|
||||
|
||||
render(<MetricQueryEditor {...props} />);
|
||||
|
||||
expect(screen.getByLabelText('Project')).toBeInTheDocument();
|
||||
|
||||
config.featureToggles.cloudMonitoringExperimentalUI = originalValue;
|
||||
});
|
||||
});
|
||||
});
|
@ -1,140 +0,0 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorRows } from '@grafana/ui';
|
||||
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { getAlignmentPickerData } from '../../functions';
|
||||
import {
|
||||
AlignmentTypes,
|
||||
CustomMetaData,
|
||||
EditorMode,
|
||||
MetricDescriptor,
|
||||
MetricKind,
|
||||
MetricQuery,
|
||||
PreprocessorType,
|
||||
SLOQuery,
|
||||
ValueTypes,
|
||||
} from '../../types';
|
||||
|
||||
import { MQLQueryEditor } from './../MQLQueryEditor';
|
||||
import { GraphPeriod } from './GraphPeriod';
|
||||
import { VisualMetricQueryEditor } from './VisualMetricQueryEditor';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
customMetaData: CustomMetaData;
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
onChange: (query: MetricQuery) => void;
|
||||
onRunQuery: () => void;
|
||||
query: MetricQuery;
|
||||
datasource: CloudMonitoringDatasource;
|
||||
}
|
||||
|
||||
interface State {
|
||||
labels: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const defaultState: State = {
|
||||
labels: {},
|
||||
};
|
||||
|
||||
export const defaultQuery: (dataSource: CloudMonitoringDatasource) => MetricQuery = (dataSource) => ({
|
||||
editorMode: EditorMode.Visual,
|
||||
projectName: dataSource.getDefaultProject(),
|
||||
metricType: '',
|
||||
metricKind: MetricKind.GAUGE,
|
||||
valueType: '',
|
||||
crossSeriesReducer: 'REDUCE_MEAN',
|
||||
alignmentPeriod: 'cloud-monitoring-auto',
|
||||
perSeriesAligner: AlignmentTypes.ALIGN_MEAN,
|
||||
groupBys: [],
|
||||
filters: [],
|
||||
aliasBy: '',
|
||||
query: '',
|
||||
preprocessor: PreprocessorType.None,
|
||||
});
|
||||
|
||||
function Editor({
|
||||
refId,
|
||||
query,
|
||||
datasource,
|
||||
onChange: onQueryChange,
|
||||
onRunQuery,
|
||||
customMetaData,
|
||||
variableOptionGroup,
|
||||
}: React.PropsWithChildren<Props>) {
|
||||
const [state, setState] = useState<State>(defaultState);
|
||||
const { projectName, metricType, groupBys, editorMode, crossSeriesReducer } = query;
|
||||
|
||||
useEffect(() => {
|
||||
if (projectName && metricType) {
|
||||
datasource
|
||||
.getLabels(metricType, refId, projectName)
|
||||
.then((labels) => setState((prevState) => ({ ...prevState, labels })));
|
||||
}
|
||||
}, [datasource, groupBys, metricType, projectName, refId, crossSeriesReducer]);
|
||||
|
||||
const onChange = useCallback(
|
||||
(metricQuery: MetricQuery | SLOQuery) => {
|
||||
onQueryChange({ ...query, ...metricQuery });
|
||||
onRunQuery();
|
||||
},
|
||||
[onQueryChange, onRunQuery, query]
|
||||
);
|
||||
|
||||
const onMetricTypeChange = useCallback(
|
||||
({ valueType, metricKind, type }: MetricDescriptor) => {
|
||||
const preprocessor =
|
||||
metricKind === MetricKind.GAUGE || valueType === ValueTypes.DISTRIBUTION
|
||||
? PreprocessorType.None
|
||||
: PreprocessorType.Rate;
|
||||
const { perSeriesAligner } = getAlignmentPickerData(valueType, metricKind, state.perSeriesAligner, preprocessor);
|
||||
onChange({
|
||||
...query,
|
||||
perSeriesAligner,
|
||||
metricType: type,
|
||||
valueType,
|
||||
metricKind,
|
||||
preprocessor,
|
||||
});
|
||||
},
|
||||
[onChange, query, state]
|
||||
);
|
||||
|
||||
return (
|
||||
<EditorRows>
|
||||
{editorMode === EditorMode.Visual && (
|
||||
<VisualMetricQueryEditor
|
||||
refId={refId}
|
||||
labels={state.labels}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
customMetaData={customMetaData}
|
||||
onMetricTypeChange={onMetricTypeChange}
|
||||
onChange={onChange}
|
||||
datasource={datasource}
|
||||
query={query}
|
||||
/>
|
||||
)}
|
||||
|
||||
{editorMode === EditorMode.MQL && (
|
||||
<>
|
||||
<MQLQueryEditor
|
||||
onChange={(q: string) => onQueryChange({ ...query, query: q })}
|
||||
onRunQuery={onRunQuery}
|
||||
query={query.query}
|
||||
></MQLQueryEditor>
|
||||
<GraphPeriod
|
||||
onChange={(graphPeriod: string) => onQueryChange({ ...query, graphPeriod })}
|
||||
graphPeriod={query.graphPeriod}
|
||||
refId={refId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</EditorRows>
|
||||
);
|
||||
}
|
||||
|
||||
export const MetricQueryEditor = React.memo(Editor);
|
@ -1,193 +0,0 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { startCase, uniqBy } from 'lodash';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorFieldGroup, EditorRow, getSelectStyles, Select, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { MetricDescriptor, MetricQuery } from '../../types';
|
||||
|
||||
import { Project } from './Project';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
onChange: (metricDescriptor: MetricDescriptor) => void;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
datasource: CloudMonitoringDatasource;
|
||||
projectName: string;
|
||||
metricType: string;
|
||||
query: MetricQuery;
|
||||
children: (metricDescriptor?: MetricDescriptor) => JSX.Element;
|
||||
onProjectChange: (query: MetricQuery) => void;
|
||||
}
|
||||
|
||||
export function Metrics(props: Props) {
|
||||
const [metricDescriptors, setMetricDescriptors] = useState<MetricDescriptor[]>([]);
|
||||
const [metricDescriptor, setMetricDescriptor] = useState<MetricDescriptor>();
|
||||
const [metrics, setMetrics] = useState<Array<SelectableValue<string>>>([]);
|
||||
const [services, setServices] = useState<Array<SelectableValue<string>>>([]);
|
||||
const [service, setService] = useState<string>('');
|
||||
|
||||
const theme = useTheme2();
|
||||
const selectStyles = getSelectStyles(theme);
|
||||
|
||||
const customStyle = useStyles2(getStyles);
|
||||
|
||||
const {
|
||||
onProjectChange,
|
||||
query,
|
||||
refId,
|
||||
metricType,
|
||||
templateVariableOptions,
|
||||
projectName,
|
||||
datasource,
|
||||
onChange,
|
||||
children,
|
||||
} = props;
|
||||
const { templateSrv } = datasource;
|
||||
|
||||
const getSelectedMetricDescriptor = useCallback(
|
||||
(metricDescriptors: MetricDescriptor[], metricType: string) => {
|
||||
return metricDescriptors.find((md) => md.type === templateSrv.replace(metricType))!;
|
||||
},
|
||||
[templateSrv]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const getMetricsList = (metricDescriptors: MetricDescriptor[]) => {
|
||||
const selectedMetricDescriptor = getSelectedMetricDescriptor(metricDescriptors, metricType);
|
||||
if (!selectedMetricDescriptor) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const metricsByService = metricDescriptors
|
||||
.filter((m) => m.service === selectedMetricDescriptor.service)
|
||||
.map((m) => ({
|
||||
service: m.service,
|
||||
value: m.type,
|
||||
label: m.displayName,
|
||||
component: function optionComponent() {
|
||||
return (
|
||||
<div>
|
||||
<div className={customStyle}>{m.type}</div>
|
||||
<div className={selectStyles.optionDescription}>{m.description}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
return metricsByService;
|
||||
};
|
||||
|
||||
const loadMetricDescriptors = async () => {
|
||||
if (projectName) {
|
||||
const metricDescriptors = await datasource.getMetricTypes(projectName);
|
||||
const services = getServicesList(metricDescriptors);
|
||||
const metrics = getMetricsList(metricDescriptors);
|
||||
const service = metrics.length > 0 ? metrics[0].service : '';
|
||||
const metricDescriptor = getSelectedMetricDescriptor(metricDescriptors, metricType);
|
||||
setMetricDescriptors(metricDescriptors);
|
||||
setServices(services);
|
||||
setMetrics(metrics);
|
||||
setService(service);
|
||||
setMetricDescriptor(metricDescriptor);
|
||||
}
|
||||
};
|
||||
loadMetricDescriptors();
|
||||
}, [datasource, getSelectedMetricDescriptor, metricType, projectName, customStyle, selectStyles.optionDescription]);
|
||||
|
||||
const onServiceChange = ({ value: service }: any) => {
|
||||
const metrics = metricDescriptors
|
||||
.filter((m: MetricDescriptor) => m.service === templateSrv.replace(service))
|
||||
.map((m: MetricDescriptor) => ({
|
||||
service: m.service,
|
||||
value: m.type,
|
||||
label: m.displayName,
|
||||
description: m.description,
|
||||
}));
|
||||
|
||||
if (metrics.length > 0 && !metrics.some((m) => m.value === templateSrv.replace(metricType))) {
|
||||
onMetricTypeChange(metrics[0]);
|
||||
setService(service);
|
||||
setMetrics(metrics);
|
||||
} else {
|
||||
setService(service);
|
||||
setMetrics(metrics);
|
||||
}
|
||||
};
|
||||
|
||||
const onMetricTypeChange = ({ value }: SelectableValue<string>) => {
|
||||
const metricDescriptor = getSelectedMetricDescriptor(metricDescriptors, value!);
|
||||
setMetricDescriptor(metricDescriptor);
|
||||
onChange({ ...metricDescriptor, type: value! });
|
||||
};
|
||||
|
||||
const getServicesList = (metricDescriptors: MetricDescriptor[]) => {
|
||||
const services = metricDescriptors.map((m) => ({
|
||||
value: m.service,
|
||||
label: startCase(m.serviceShortName),
|
||||
}));
|
||||
|
||||
return services.length > 0 ? uniqBy(services, (s) => s.value) : [];
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EditorRow>
|
||||
<EditorFieldGroup>
|
||||
<Project
|
||||
refId={refId}
|
||||
templateVariableOptions={templateVariableOptions}
|
||||
projectName={projectName}
|
||||
datasource={datasource}
|
||||
onChange={(projectName) => {
|
||||
onProjectChange({ ...query, projectName });
|
||||
}}
|
||||
/>
|
||||
|
||||
<EditorField label="Service" width="auto">
|
||||
<Select
|
||||
width="auto"
|
||||
onChange={onServiceChange}
|
||||
value={[...services, ...templateVariableOptions].find((s) => s.value === service)}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
...services,
|
||||
]}
|
||||
placeholder="Select Services"
|
||||
inputId={`${props.refId}-service`}
|
||||
/>
|
||||
</EditorField>
|
||||
<EditorField label="Metric name" width="auto">
|
||||
<Select
|
||||
width="auto"
|
||||
onChange={onMetricTypeChange}
|
||||
value={[...metrics, ...templateVariableOptions].find((s) => s.value === metricType)}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
...metrics,
|
||||
]}
|
||||
placeholder="Select Metric"
|
||||
inputId={`${props.refId}-select-metric`}
|
||||
/>
|
||||
</EditorField>
|
||||
</EditorFieldGroup>
|
||||
</EditorRow>
|
||||
|
||||
{children(metricDescriptor)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => css`
|
||||
label: grafana-select-option-description;
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
color: ${theme.colors.text.secondary};
|
||||
`;
|
@ -1,59 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select } from '@grafana/ui';
|
||||
|
||||
import { periodOption } from '../../constants';
|
||||
|
||||
export interface Props {
|
||||
inputId: string;
|
||||
onChange: (period: string) => void;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
aligmentPeriods: periodOption[];
|
||||
selectWidth?: number;
|
||||
category?: string;
|
||||
disabled?: boolean;
|
||||
current?: string;
|
||||
}
|
||||
|
||||
export function PeriodSelect({
|
||||
inputId,
|
||||
templateVariableOptions,
|
||||
onChange,
|
||||
current,
|
||||
disabled,
|
||||
aligmentPeriods,
|
||||
}: Props) {
|
||||
const options = useMemo(
|
||||
() =>
|
||||
aligmentPeriods.map((ap) => ({
|
||||
...ap,
|
||||
label: ap.text,
|
||||
})),
|
||||
[aligmentPeriods]
|
||||
);
|
||||
const visibleOptions = useMemo(() => options.filter((ap) => !ap.hidden), [options]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
width="auto"
|
||||
onChange={({ value }) => onChange(value!)}
|
||||
value={[...options, ...templateVariableOptions].find((s) => s.value === current)}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
{
|
||||
label: 'Aggregations',
|
||||
expanded: true,
|
||||
options: visibleOptions,
|
||||
},
|
||||
]}
|
||||
placeholder="Select Period"
|
||||
inputId={inputId}
|
||||
disabled={disabled}
|
||||
allowCustomValue
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
import React, { FunctionComponent, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, RadioButtonGroup } from '@grafana/ui';
|
||||
|
||||
import { getAlignmentPickerData } from '../../functions';
|
||||
import { MetricDescriptor, MetricKind, MetricQuery, PreprocessorType, ValueTypes } from '../../types';
|
||||
|
||||
const NONE_OPTION = { label: 'None', value: PreprocessorType.None };
|
||||
|
||||
export interface Props {
|
||||
metricDescriptor?: MetricDescriptor;
|
||||
onChange: (query: MetricQuery) => void;
|
||||
query: MetricQuery;
|
||||
}
|
||||
|
||||
export const Preprocessor: FunctionComponent<Props> = ({ query, metricDescriptor, onChange }) => {
|
||||
const options = useOptions(metricDescriptor);
|
||||
return (
|
||||
<EditorField
|
||||
label="Pre-processing"
|
||||
tooltip="Preprocessing options are displayed when the selected metric has a metric kind of delta or cumulative. The specific options available are determined by the metic's value type. If you select 'Rate', data points are aligned and converted to a rate per time series. If you select 'Delta', data points are aligned by their delta (difference) per time series"
|
||||
>
|
||||
<RadioButtonGroup
|
||||
onChange={(value: PreprocessorType) => {
|
||||
const { valueType, metricKind, perSeriesAligner: psa } = query;
|
||||
const { perSeriesAligner } = getAlignmentPickerData(valueType, metricKind, psa, value);
|
||||
onChange({ ...query, preprocessor: value, perSeriesAligner });
|
||||
}}
|
||||
value={query.preprocessor ?? PreprocessorType.None}
|
||||
options={options}
|
||||
/>
|
||||
</EditorField>
|
||||
);
|
||||
};
|
||||
|
||||
const useOptions = (metricDescriptor?: MetricDescriptor): Array<SelectableValue<PreprocessorType>> => {
|
||||
const metricKind = metricDescriptor?.metricKind;
|
||||
const valueType = metricDescriptor?.valueType;
|
||||
|
||||
return useMemo(() => {
|
||||
if (!metricKind || metricKind === MetricKind.GAUGE || valueType === ValueTypes.DISTRIBUTION) {
|
||||
return [NONE_OPTION];
|
||||
}
|
||||
|
||||
const options = [
|
||||
NONE_OPTION,
|
||||
{
|
||||
label: 'Rate',
|
||||
value: PreprocessorType.Rate,
|
||||
description: 'Data points are aligned and converted to a rate per time series',
|
||||
},
|
||||
];
|
||||
|
||||
return metricKind === MetricKind.CUMULATIVE
|
||||
? [
|
||||
...options,
|
||||
{
|
||||
label: 'Delta',
|
||||
value: PreprocessorType.Delta,
|
||||
description: 'Data points are aligned by their delta (difference) per time series',
|
||||
},
|
||||
]
|
||||
: options;
|
||||
}, [metricKind, valueType]);
|
||||
};
|
@ -1,48 +0,0 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, Select } from '@grafana/ui';
|
||||
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
datasource: CloudMonitoringDatasource;
|
||||
onChange: (projectName: string) => void;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
projectName: string;
|
||||
}
|
||||
|
||||
export function Project({ refId, projectName, datasource, onChange, templateVariableOptions }: Props) {
|
||||
const [projects, setProjects] = useState<Array<SelectableValue<string>>>([]);
|
||||
useEffect(() => {
|
||||
datasource.getProjects().then((projects) => setProjects(projects));
|
||||
}, [datasource]);
|
||||
|
||||
const projectsWithTemplateVariables = useMemo(
|
||||
() => [
|
||||
projects,
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
...projects,
|
||||
],
|
||||
[projects, templateVariableOptions]
|
||||
);
|
||||
|
||||
return (
|
||||
<EditorField label="Project">
|
||||
<Select
|
||||
width="auto"
|
||||
allowCustomValue
|
||||
formatCreateLabel={(v) => `Use project: ${v}`}
|
||||
onChange={({ value }) => onChange(value!)}
|
||||
options={projectsWithTemplateVariables}
|
||||
value={{ value: projectName, label: projectName }}
|
||||
placeholder="Select Project"
|
||||
inputId={`${refId}-project`}
|
||||
/>
|
||||
</EditorField>
|
||||
);
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import { render, screen, act } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { TemplateSrvMock } from 'app/features/templating/template_srv.mock';
|
||||
|
||||
import { createMockDatasource } from '../../__mocks__/cloudMonitoringDatasource';
|
||||
import { createMockSLOQuery } from '../../__mocks__/cloudMonitoringQuery';
|
||||
|
||||
import { SLOQueryEditor, Props } from './SLOQueryEditor';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getTemplateSrv: () => new TemplateSrvMock({}),
|
||||
}));
|
||||
|
||||
const props: Props = {
|
||||
onChange: jest.fn(),
|
||||
refId: 'refId',
|
||||
customMetaData: {},
|
||||
onRunQuery: jest.fn(),
|
||||
datasource: createMockDatasource(),
|
||||
variableOptionGroup: { options: [] },
|
||||
query: createMockSLOQuery(),
|
||||
};
|
||||
|
||||
describe('Cloud monitoring: SLO Query Editor', () => {
|
||||
it('shoud render Service selector', async () => {
|
||||
await act(async () => {
|
||||
const originalValue = config.featureToggles.cloudMonitoringExperimentalUI;
|
||||
config.featureToggles.cloudMonitoringExperimentalUI = true;
|
||||
|
||||
render(<SLOQueryEditor {...props} />);
|
||||
|
||||
expect(screen.getByLabelText('Service')).toBeInTheDocument();
|
||||
|
||||
config.featureToggles.cloudMonitoringExperimentalUI = originalValue;
|
||||
});
|
||||
});
|
||||
});
|
@ -1,88 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorRow } from '@grafana/ui';
|
||||
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { CustomMetaData, MetricDescriptor, MetricQuery, SLOQuery } from '../../types';
|
||||
|
||||
import { AliasBy } from './AliasBy';
|
||||
import { Alignment } from './Alignment';
|
||||
import { GroupBy } from './GroupBy';
|
||||
import { LabelFilter } from './LabelFilter';
|
||||
import { Metrics } from './Metrics';
|
||||
import { Preprocessor } from './Preprocessor';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
customMetaData: CustomMetaData;
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
onMetricTypeChange: (query: MetricDescriptor) => void;
|
||||
onChange: (query: MetricQuery | SLOQuery) => void;
|
||||
query: MetricQuery;
|
||||
datasource: CloudMonitoringDatasource;
|
||||
labels: any;
|
||||
}
|
||||
|
||||
function Editor({
|
||||
refId,
|
||||
query,
|
||||
labels,
|
||||
datasource,
|
||||
onChange,
|
||||
onMetricTypeChange,
|
||||
customMetaData,
|
||||
variableOptionGroup,
|
||||
}: React.PropsWithChildren<Props>) {
|
||||
return (
|
||||
<Metrics
|
||||
refId={refId}
|
||||
projectName={query.projectName}
|
||||
metricType={query.metricType}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
datasource={datasource}
|
||||
onChange={onMetricTypeChange}
|
||||
onProjectChange={onChange}
|
||||
query={query}
|
||||
>
|
||||
{(metric) => (
|
||||
<>
|
||||
<LabelFilter
|
||||
labels={labels}
|
||||
filters={query.filters!}
|
||||
onChange={(filters: string[]) => onChange({ ...query, filters })}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
/>
|
||||
<EditorRow>
|
||||
<Preprocessor metricDescriptor={metric} query={query} onChange={onChange} />
|
||||
<GroupBy
|
||||
refId={refId}
|
||||
labels={Object.keys(labels)}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
metricDescriptor={metric}
|
||||
/>
|
||||
<Alignment
|
||||
refId={refId}
|
||||
datasource={datasource}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
query={query}
|
||||
customMetaData={customMetaData}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<AliasBy
|
||||
refId={refId}
|
||||
value={query.aliasBy}
|
||||
onChange={(aliasBy) => {
|
||||
onChange({ ...query, aliasBy });
|
||||
}}
|
||||
/>
|
||||
</EditorRow>
|
||||
</>
|
||||
)}
|
||||
</Metrics>
|
||||
);
|
||||
}
|
||||
|
||||
export const VisualMetricQueryEditor = React.memo(Editor);
|
@ -1,11 +1,11 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Switch } from '@grafana/ui';
|
||||
import { EditorField, EditorRow, HorizontalGroup, Switch } from '@grafana/ui';
|
||||
|
||||
import { GRAPH_PERIODS, SELECT_WIDTH } from '../constants';
|
||||
import { GRAPH_PERIODS } from '../constants';
|
||||
|
||||
import { PeriodSelect, QueryEditorRow } from '.';
|
||||
import { PeriodSelect } from './index';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
@ -16,8 +16,8 @@ export interface Props {
|
||||
|
||||
export const GraphPeriod: FunctionComponent<Props> = ({ refId, onChange, graphPeriod, variableOptionGroup }) => {
|
||||
return (
|
||||
<>
|
||||
<QueryEditorRow
|
||||
<EditorRow>
|
||||
<EditorField
|
||||
label="Graph period"
|
||||
htmlFor={`${refId}-graph-period`}
|
||||
tooltip={
|
||||
@ -27,21 +27,22 @@ export const GraphPeriod: FunctionComponent<Props> = ({ refId, onChange, graphPe
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Switch
|
||||
data-testid={`${refId}-switch-graph-period`}
|
||||
value={graphPeriod !== 'disabled'}
|
||||
onChange={(e) => onChange(e.currentTarget.checked ? '' : 'disabled')}
|
||||
/>
|
||||
<PeriodSelect
|
||||
inputId={`${refId}-graph-period`}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
current={graphPeriod}
|
||||
onChange={onChange}
|
||||
selectWidth={SELECT_WIDTH}
|
||||
disabled={graphPeriod === 'disabled'}
|
||||
aligmentPeriods={GRAPH_PERIODS}
|
||||
/>
|
||||
</QueryEditorRow>
|
||||
</>
|
||||
<HorizontalGroup>
|
||||
<Switch
|
||||
data-testid={`${refId}-switch-graph-period`}
|
||||
value={graphPeriod !== 'disabled'}
|
||||
onChange={(e) => onChange(e.currentTarget.checked ? '' : 'disabled')}
|
||||
/>
|
||||
<PeriodSelect
|
||||
inputId={`${refId}-graph-period`}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
current={graphPeriod}
|
||||
onChange={onChange}
|
||||
disabled={graphPeriod === 'disabled'}
|
||||
aligmentPeriods={GRAPH_PERIODS}
|
||||
/>
|
||||
</HorizontalGroup>
|
||||
</EditorField>
|
||||
</EditorRow>
|
||||
);
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { openMenu, select } from 'react-select-event';
|
||||
|
||||
import { createMockMetricQuery } from '../../__mocks__/cloudMonitoringQuery';
|
||||
import { createMockMetricQuery } from '../__mocks__/cloudMonitoringQuery';
|
||||
|
||||
import { GroupBy, Props } from './GroupBy';
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React, { FunctionComponent, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { MultiSelect } from '@grafana/ui';
|
||||
import { EditorField, EditorFieldGroup, MultiSelect } from '@grafana/ui';
|
||||
|
||||
import { INPUT_WIDTH, SYSTEM_LABELS } from '../constants';
|
||||
import { SYSTEM_LABELS } from '../constants';
|
||||
import { labelsToGroupedOptions } from '../functions';
|
||||
import { MetricDescriptor, MetricQuery } from '../types';
|
||||
|
||||
import { Aggregation, QueryEditorRow } from '.';
|
||||
import { Aggregation } from './Aggregation';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
@ -32,21 +32,22 @@ export const GroupBy: FunctionComponent<Props> = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<QueryEditorRow
|
||||
label="Group by"
|
||||
tooltip="You can reduce the amount of data returned for a metric by combining different time series. To combine multiple time series, you can specify a grouping and a function. Grouping is done on the basis of labels. The grouping function is used to combine the time series in the group into a single time series."
|
||||
htmlFor={`${refId}-group-by`}
|
||||
>
|
||||
<MultiSelect
|
||||
inputId={`${refId}-group-by`}
|
||||
width={INPUT_WIDTH}
|
||||
placeholder="Choose label"
|
||||
options={options}
|
||||
value={query.groupBys ?? []}
|
||||
onChange={(options) => {
|
||||
onChange({ ...query, groupBys: options.map((o) => o.value!) });
|
||||
}}
|
||||
></MultiSelect>
|
||||
<EditorFieldGroup>
|
||||
<EditorField
|
||||
label="Group by"
|
||||
tooltip="You can reduce the amount of data returned for a metric by combining different time series. To combine multiple time series, you can specify a grouping and a function. Grouping is done on the basis of labels. The grouping function is used to combine the time series in the group into a single time series."
|
||||
>
|
||||
<MultiSelect
|
||||
inputId={`${refId}-group-by`}
|
||||
width="auto"
|
||||
placeholder="Choose label"
|
||||
options={options}
|
||||
value={query.groupBys ?? []}
|
||||
onChange={(options) => {
|
||||
onChange({ ...query, groupBys: options.map((o) => o.value!) });
|
||||
}}
|
||||
/>
|
||||
</EditorField>
|
||||
<Aggregation
|
||||
metricDescriptor={metricDescriptor}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
@ -54,7 +55,7 @@ export const GroupBy: FunctionComponent<Props> = ({
|
||||
groupBys={query.groupBys ?? []}
|
||||
onChange={(crossSeriesReducer) => onChange({ ...query, crossSeriesReducer })}
|
||||
refId={refId}
|
||||
></Aggregation>
|
||||
</QueryEditorRow>
|
||||
/>
|
||||
</EditorFieldGroup>
|
||||
);
|
||||
};
|
||||
|
@ -1,15 +1,9 @@
|
||||
import { flatten } from 'lodash';
|
||||
import React, { FunctionComponent, useCallback, useMemo } from 'react';
|
||||
import React, { FunctionComponent, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue, toOption } from '@grafana/data';
|
||||
import { Button, HorizontalGroup, Select, VerticalGroup } from '@grafana/ui';
|
||||
import { CustomControlProps } from '@grafana/ui/src/components/Select/types';
|
||||
import { AccessoryButton, EditorField, EditorList, EditorRow, HorizontalGroup, Select } from '@grafana/ui';
|
||||
|
||||
import { SELECT_WIDTH } from '../constants';
|
||||
import { labelsToGroupedOptions, stringArrayToFilters } from '../functions';
|
||||
import { Filter } from '../types';
|
||||
|
||||
import { QueryEditorRow } from '.';
|
||||
|
||||
export interface Props {
|
||||
labels: { [key: string]: string[] };
|
||||
@ -18,137 +12,107 @@ export interface Props {
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
}
|
||||
|
||||
const operators = ['=', '!=', '=~', '!=~'];
|
||||
interface Filter {
|
||||
key: string;
|
||||
operator: string;
|
||||
value: string;
|
||||
condition: string;
|
||||
}
|
||||
|
||||
const FilterButton = React.forwardRef<HTMLButtonElement, CustomControlProps<string>>(
|
||||
({ value, isOpen, invalid, ...rest }, ref) => {
|
||||
return <Button {...rest} ref={ref} variant="secondary" icon="plus" aria-label="Add filter"></Button>;
|
||||
}
|
||||
);
|
||||
FilterButton.displayName = 'FilterButton';
|
||||
const DEFAULT_OPERATOR = '=';
|
||||
const DEFAULT_CONDITION = 'AND';
|
||||
|
||||
const OperatorButton = React.forwardRef<HTMLButtonElement, CustomControlProps<string>>(({ value, ...rest }, ref) => {
|
||||
return (
|
||||
<Button {...rest} ref={ref} variant="secondary">
|
||||
<span className="query-segment-operator">{value?.label}</span>
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
OperatorButton.displayName = 'OperatorButton';
|
||||
const filtersToStringArray = (filters: Filter[]) =>
|
||||
filters.flatMap(({ key, operator, value, condition }) => [key, operator, value, condition]).slice(0, -1);
|
||||
|
||||
const operators = ['=', '!=', '=~', '!=~'].map(toOption);
|
||||
|
||||
export const LabelFilter: FunctionComponent<Props> = ({
|
||||
labels = {},
|
||||
filters: filterArray,
|
||||
onChange,
|
||||
onChange: _onChange,
|
||||
variableOptionGroup,
|
||||
}) => {
|
||||
const filters = useMemo(() => stringArrayToFilters(filterArray), [filterArray]);
|
||||
const filters: Filter[] = useMemo(() => stringArrayToFilters(filterArray), [filterArray]);
|
||||
const options = useMemo(
|
||||
() => [variableOptionGroup, ...labelsToGroupedOptions(Object.keys(labels))],
|
||||
[labels, variableOptionGroup]
|
||||
);
|
||||
|
||||
const filtersToStringArray = useCallback((filters: Filter[]) => {
|
||||
const strArr = flatten(filters.map(({ key, operator, value, condition }) => [key, operator, value, condition!]));
|
||||
return strArr.slice(0, strArr.length - 1);
|
||||
}, []);
|
||||
const getOptions = ({ key = '', value = '' }: Partial<Filter>) => {
|
||||
// Add the current key and value as options if they are manually entered
|
||||
const keyPresent = options.some((op) => {
|
||||
if (op.options) {
|
||||
return options.some((opp) => opp.label === key);
|
||||
}
|
||||
return op.label === key;
|
||||
});
|
||||
if (!keyPresent) {
|
||||
options.push({ label: key, value: key });
|
||||
}
|
||||
|
||||
const valueOptions = labels.hasOwnProperty(key)
|
||||
? [variableOptionGroup, ...labels[key].map(toOption)]
|
||||
: [variableOptionGroup];
|
||||
const valuePresent = valueOptions.some((op) => op.label === value);
|
||||
if (!valuePresent) {
|
||||
valueOptions.push({ label: value, value });
|
||||
}
|
||||
|
||||
return { options, valueOptions };
|
||||
};
|
||||
|
||||
const onChange = (items: Array<Partial<Filter>>) => {
|
||||
const filters = items.map(({ key, operator, value, condition }) => ({
|
||||
key: key || '',
|
||||
operator: operator || DEFAULT_OPERATOR,
|
||||
value: value || '',
|
||||
condition: condition || DEFAULT_CONDITION,
|
||||
}));
|
||||
_onChange(filtersToStringArray(filters));
|
||||
};
|
||||
|
||||
const renderItem = (item: Partial<Filter>, onChangeItem: (item: Filter) => void, onDeleteItem: () => void) => {
|
||||
const { key = '', operator = DEFAULT_OPERATOR, value = '', condition = DEFAULT_CONDITION } = item;
|
||||
const { options, valueOptions } = getOptions(item);
|
||||
|
||||
const AddFilter = () => {
|
||||
return (
|
||||
<Select
|
||||
allowCustomValue
|
||||
options={[variableOptionGroup, ...labelsToGroupedOptions(Object.keys(labels))]}
|
||||
onChange={({ value: key = '' }) =>
|
||||
onChange(filtersToStringArray([...filters, { key, operator: '=', condition: 'AND', value: '' }]))
|
||||
}
|
||||
menuPlacement="bottom"
|
||||
renderControl={FilterButton}
|
||||
/>
|
||||
<HorizontalGroup spacing="xs" width="auto">
|
||||
<Select
|
||||
aria-label="Filter label key"
|
||||
formatCreateLabel={(v) => `Use label key: ${v}`}
|
||||
allowCustomValue
|
||||
value={key}
|
||||
options={options}
|
||||
onChange={({ value: key = '' }) => onChangeItem({ key, operator, value, condition })}
|
||||
/>
|
||||
<Select
|
||||
value={operator}
|
||||
options={operators}
|
||||
onChange={({ value: operator = DEFAULT_OPERATOR }) => onChangeItem({ key, operator, value, condition })}
|
||||
/>
|
||||
<Select
|
||||
aria-label="Filter label value"
|
||||
placeholder="add filter value"
|
||||
formatCreateLabel={(v) => `Use label value: ${v}`}
|
||||
allowCustomValue
|
||||
value={value}
|
||||
options={valueOptions}
|
||||
onChange={({ value = '' }) => onChangeItem({ key, operator, value, condition })}
|
||||
/>
|
||||
<AccessoryButton aria-label="Remove" icon="times" variant="secondary" onClick={onDeleteItem} type="button" />
|
||||
</HorizontalGroup>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<QueryEditorRow
|
||||
label="Filter"
|
||||
tooltip={
|
||||
'To reduce the amount of data charted, apply a filter. A filter has three components: a label, a comparison, and a value. The comparison can be an equality, inequality, or regular expression.'
|
||||
}
|
||||
noFillEnd={filters.length > 1}
|
||||
>
|
||||
<VerticalGroup spacing="xs" width="auto">
|
||||
{filters.map(({ key, operator, value, condition }, index) => {
|
||||
// Add the current key and value as options if they are manually entered
|
||||
const keyPresent = options.some((op) => {
|
||||
if (op.options) {
|
||||
return options.some((opp) => opp.label === key);
|
||||
}
|
||||
return op.label === key;
|
||||
});
|
||||
if (!keyPresent) {
|
||||
options.push({ label: key, value: key });
|
||||
}
|
||||
|
||||
const valueOptions = labels.hasOwnProperty(key)
|
||||
? [variableOptionGroup, ...labels[key].map(toOption)]
|
||||
: [variableOptionGroup];
|
||||
const valuePresent = valueOptions.some((op) => {
|
||||
return op.label === value;
|
||||
});
|
||||
if (!valuePresent) {
|
||||
valueOptions.push({ label: value, value });
|
||||
}
|
||||
|
||||
return (
|
||||
<HorizontalGroup key={index} spacing="xs" width="auto">
|
||||
<Select
|
||||
aria-label="Filter label key"
|
||||
width={SELECT_WIDTH}
|
||||
allowCustomValue
|
||||
formatCreateLabel={(v) => `Use label key: ${v}`}
|
||||
value={key}
|
||||
options={options}
|
||||
onChange={({ value: key = '' }) => {
|
||||
onChange(
|
||||
filtersToStringArray(
|
||||
filters.map((f, i) => (i === index ? { key, operator, condition, value: '' } : f))
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
value={operator}
|
||||
options={operators.map(toOption)}
|
||||
onChange={({ value: operator = '=' }) =>
|
||||
onChange(filtersToStringArray(filters.map((f, i) => (i === index ? { ...f, operator } : f))))
|
||||
}
|
||||
menuPlacement="bottom"
|
||||
renderControl={OperatorButton}
|
||||
/>
|
||||
<Select
|
||||
aria-label="Filter label value"
|
||||
width={SELECT_WIDTH}
|
||||
formatCreateLabel={(v) => `Use label value: ${v}`}
|
||||
allowCustomValue
|
||||
value={value}
|
||||
placeholder="add filter value"
|
||||
options={valueOptions}
|
||||
onChange={({ value = '' }) =>
|
||||
onChange(filtersToStringArray(filters.map((f, i) => (i === index ? { ...f, value } : f))))
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="md"
|
||||
icon="trash-alt"
|
||||
aria-label="Remove"
|
||||
onClick={() => onChange(filtersToStringArray(filters.filter((_, i) => i !== index)))}
|
||||
></Button>
|
||||
{index + 1 === filters.length && Object.values(filters).every(({ value }) => value) && <AddFilter />}
|
||||
</HorizontalGroup>
|
||||
);
|
||||
})}
|
||||
{!filters.length && <AddFilter />}
|
||||
</VerticalGroup>
|
||||
</QueryEditorRow>
|
||||
<EditorRow>
|
||||
<EditorField
|
||||
label="Filter"
|
||||
tooltip="To reduce the amount of data charted, apply a filter. A filter has three components: a label, a comparison, and a value. The comparison can be an equality, inequality, or regular expression."
|
||||
>
|
||||
<EditorList items={filters} renderItem={renderItem} onChange={onChange} />
|
||||
</EditorField>
|
||||
</EditorRow>
|
||||
);
|
||||
};
|
||||
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, Select } from '@grafana/ui';
|
||||
|
||||
import { LOOKBACK_PERIODS } from '../../constants';
|
||||
import { LOOKBACK_PERIODS } from '../constants';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorRows } from '@grafana/ui';
|
||||
|
||||
import CloudMonitoringDatasource from '../datasource';
|
||||
import { getAlignmentPickerData } from '../functions';
|
||||
@ -18,8 +19,7 @@ import {
|
||||
|
||||
import { GraphPeriod } from './GraphPeriod';
|
||||
import { MQLQueryEditor } from './MQLQueryEditor';
|
||||
|
||||
import { AliasBy, Project, VisualMetricQueryEditor } from '.';
|
||||
import { VisualMetricQueryEditor } from './VisualMetricQueryEditor';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
@ -104,17 +104,7 @@ function Editor({
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Project
|
||||
refId={refId}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
projectName={projectName}
|
||||
datasource={datasource}
|
||||
onChange={(projectName) => {
|
||||
onChange({ ...query, projectName });
|
||||
}}
|
||||
/>
|
||||
|
||||
<EditorRows>
|
||||
{editorMode === EditorMode.Visual && (
|
||||
<VisualMetricQueryEditor
|
||||
refId={refId}
|
||||
@ -143,15 +133,7 @@ function Editor({
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<AliasBy
|
||||
refId={refId}
|
||||
value={query.aliasBy}
|
||||
onChange={(aliasBy) => {
|
||||
onChange({ ...query, aliasBy });
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
</EditorRows>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,9 @@ import { render, screen, within } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { openMenu, select } from 'react-select-event';
|
||||
|
||||
import { createMockDatasource } from '../../__mocks__/cloudMonitoringDatasource';
|
||||
import { createMockMetricDescriptor } from '../../__mocks__/cloudMonitoringMetricDescriptor';
|
||||
import { createMockMetricQuery } from '../../__mocks__/cloudMonitoringQuery';
|
||||
import { createMockDatasource } from '../__mocks__/cloudMonitoringDatasource';
|
||||
import { createMockMetricDescriptor } from '../__mocks__/cloudMonitoringMetricDescriptor';
|
||||
import { createMockMetricQuery } from '../__mocks__/cloudMonitoringQuery';
|
||||
|
||||
import { Metrics } from './Metrics';
|
||||
|
@ -3,53 +3,49 @@ import { startCase, uniqBy } from 'lodash';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import { TemplateSrv } from '@grafana/runtime';
|
||||
import { getSelectStyles, Select, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
import { EditorField, EditorFieldGroup, EditorRow, getSelectStyles, Select, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
|
||||
import { INNER_LABEL_WIDTH, LABEL_WIDTH, SELECT_WIDTH } from '../constants';
|
||||
import CloudMonitoringDatasource from '../datasource';
|
||||
import { MetricDescriptor } from '../types';
|
||||
import { MetricDescriptor, MetricQuery } from '../types';
|
||||
|
||||
import { QueryEditorField, QueryEditorRow } from '.';
|
||||
import { Project } from './Project';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
onChange: (metricDescriptor: MetricDescriptor) => void;
|
||||
templateSrv: TemplateSrv;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
datasource: CloudMonitoringDatasource;
|
||||
projectName: string;
|
||||
metricType: string;
|
||||
query: MetricQuery;
|
||||
children: (metricDescriptor?: MetricDescriptor) => JSX.Element;
|
||||
}
|
||||
|
||||
interface State {
|
||||
metricDescriptors: MetricDescriptor[];
|
||||
metrics: any[];
|
||||
services: any[];
|
||||
service: string;
|
||||
metric: string;
|
||||
metricDescriptor?: MetricDescriptor;
|
||||
projectName: string | null;
|
||||
onProjectChange: (query: MetricQuery) => void;
|
||||
}
|
||||
|
||||
export function Metrics(props: Props) {
|
||||
const [state, setState] = useState<State>({
|
||||
metricDescriptors: [],
|
||||
metrics: [],
|
||||
services: [],
|
||||
service: '',
|
||||
metric: '',
|
||||
projectName: null,
|
||||
});
|
||||
const [metricDescriptors, setMetricDescriptors] = useState<MetricDescriptor[]>([]);
|
||||
const [metricDescriptor, setMetricDescriptor] = useState<MetricDescriptor>();
|
||||
const [metrics, setMetrics] = useState<Array<SelectableValue<string>>>([]);
|
||||
const [services, setServices] = useState<Array<SelectableValue<string>>>([]);
|
||||
const [service, setService] = useState<string>('');
|
||||
|
||||
const theme = useTheme2();
|
||||
const selectStyles = getSelectStyles(theme);
|
||||
|
||||
const customStyle = useStyles2(getStyles);
|
||||
|
||||
const { services, service, metrics, metricDescriptors } = state;
|
||||
const { metricType, templateVariableOptions, projectName, templateSrv, datasource, onChange, children } = props;
|
||||
const {
|
||||
onProjectChange,
|
||||
query,
|
||||
refId,
|
||||
metricType,
|
||||
templateVariableOptions,
|
||||
projectName,
|
||||
datasource,
|
||||
onChange,
|
||||
children,
|
||||
} = props;
|
||||
const { templateSrv } = datasource;
|
||||
|
||||
const getSelectedMetricDescriptor = useCallback(
|
||||
(metricDescriptors: MetricDescriptor[], metricType: string) => {
|
||||
@ -63,11 +59,8 @@ export function Metrics(props: Props) {
|
||||
if (projectName) {
|
||||
const metricDescriptors = await datasource.getMetricTypes(projectName);
|
||||
const services = getServicesList(metricDescriptors);
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
metricDescriptors,
|
||||
services,
|
||||
}));
|
||||
setMetricDescriptors(metricDescriptors);
|
||||
setServices(services);
|
||||
}
|
||||
};
|
||||
loadMetricDescriptors();
|
||||
@ -97,15 +90,13 @@ export function Metrics(props: Props) {
|
||||
}));
|
||||
return metricsByService;
|
||||
};
|
||||
|
||||
const metrics = getMetricsList(metricDescriptors);
|
||||
const service = metrics.length > 0 ? metrics[0].service : '';
|
||||
const metricDescriptor = getSelectedMetricDescriptor(metricDescriptors, metricType);
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
metricDescriptor,
|
||||
metrics,
|
||||
service: service,
|
||||
}));
|
||||
setMetricDescriptor(metricDescriptor);
|
||||
setMetrics(metrics);
|
||||
setService(service);
|
||||
}, [metricDescriptors, getSelectedMetricDescriptor, metricType, customStyle, selectStyles.optionDescription]);
|
||||
|
||||
const onServiceChange = ({ value: service }: any) => {
|
||||
@ -119,15 +110,18 @@ export function Metrics(props: Props) {
|
||||
}));
|
||||
|
||||
if (metrics.length > 0 && !metrics.some((m) => m.value === templateSrv.replace(metricType))) {
|
||||
onMetricTypeChange(metrics[0], { service, metrics });
|
||||
onMetricTypeChange(metrics[0]);
|
||||
setService(service);
|
||||
setMetrics(metrics);
|
||||
} else {
|
||||
setState({ ...state, service, metrics });
|
||||
setService(service);
|
||||
setMetrics(metrics);
|
||||
}
|
||||
};
|
||||
|
||||
const onMetricTypeChange = ({ value }: SelectableValue<string>, extra: any = {}) => {
|
||||
const metricDescriptor = getSelectedMetricDescriptor(state.metricDescriptors, value!);
|
||||
setState({ ...state, metricDescriptor, ...extra });
|
||||
const onMetricTypeChange = ({ value }: SelectableValue<string>) => {
|
||||
const metricDescriptor = getSelectedMetricDescriptor(metricDescriptors, value!);
|
||||
setMetricDescriptor(metricDescriptor);
|
||||
onChange({ ...metricDescriptor, type: value! });
|
||||
};
|
||||
|
||||
@ -142,42 +136,54 @@ export function Metrics(props: Props) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<QueryEditorRow>
|
||||
<QueryEditorField labelWidth={LABEL_WIDTH} label="Service" htmlFor={`${props.refId}-service`}>
|
||||
<Select
|
||||
width={SELECT_WIDTH}
|
||||
onChange={onServiceChange}
|
||||
value={[...services, ...templateVariableOptions].find((s) => s.value === service)}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
...services,
|
||||
]}
|
||||
placeholder="Select Services"
|
||||
inputId={`${props.refId}-service`}
|
||||
></Select>
|
||||
</QueryEditorField>
|
||||
<QueryEditorField label="Metric name" labelWidth={INNER_LABEL_WIDTH} htmlFor={`${props.refId}-select-metric`}>
|
||||
<Select
|
||||
width={SELECT_WIDTH}
|
||||
onChange={onMetricTypeChange}
|
||||
value={[...metrics, ...templateVariableOptions].find((s) => s.value === metricType)}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
...metrics,
|
||||
]}
|
||||
placeholder="Select Metric"
|
||||
inputId={`${props.refId}-select-metric`}
|
||||
></Select>
|
||||
</QueryEditorField>
|
||||
</QueryEditorRow>
|
||||
<EditorRow>
|
||||
<EditorFieldGroup>
|
||||
<Project
|
||||
refId={refId}
|
||||
templateVariableOptions={templateVariableOptions}
|
||||
projectName={projectName}
|
||||
datasource={datasource}
|
||||
onChange={(projectName) => {
|
||||
onProjectChange({ ...query, projectName });
|
||||
}}
|
||||
/>
|
||||
|
||||
{children(state.metricDescriptor)}
|
||||
<EditorField label="Service" width="auto">
|
||||
<Select
|
||||
width="auto"
|
||||
onChange={onServiceChange}
|
||||
value={[...services, ...templateVariableOptions].find((s) => s.value === service)}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
...services,
|
||||
]}
|
||||
placeholder="Select Services"
|
||||
inputId={`${props.refId}-service`}
|
||||
/>
|
||||
</EditorField>
|
||||
<EditorField label="Metric name" width="auto">
|
||||
<Select
|
||||
width="auto"
|
||||
onChange={onMetricTypeChange}
|
||||
value={[...metrics, ...templateVariableOptions].find((s) => s.value === metricType)}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
...metrics,
|
||||
]}
|
||||
placeholder="Select Metric"
|
||||
inputId={`${props.refId}-select-metric`}
|
||||
/>
|
||||
</EditorField>
|
||||
</EditorFieldGroup>
|
||||
</EditorRow>
|
||||
|
||||
{children(metricDescriptor)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ export function PeriodSelect({
|
||||
templateVariableOptions,
|
||||
onChange,
|
||||
current,
|
||||
selectWidth,
|
||||
disabled,
|
||||
aligmentPeriods,
|
||||
}: Props) {
|
||||
@ -37,7 +36,7 @@ export function PeriodSelect({
|
||||
|
||||
return (
|
||||
<Select
|
||||
width={selectWidth}
|
||||
width="auto"
|
||||
onChange={({ value }) => onChange(value!)}
|
||||
value={[...options, ...templateVariableOptions].find((s) => s.value === current)}
|
||||
options={[
|
||||
@ -55,6 +54,6 @@ export function PeriodSelect({
|
||||
inputId={inputId}
|
||||
disabled={disabled}
|
||||
allowCustomValue
|
||||
></Select>
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import React from 'react';
|
||||
|
||||
import { TemplateSrvMock } from 'app/features/templating/template_srv.mock';
|
||||
|
||||
import { createMockMetricDescriptor } from '../../__mocks__/cloudMonitoringMetricDescriptor';
|
||||
import { createMockMetricQuery } from '../../__mocks__/cloudMonitoringQuery';
|
||||
import { MetricKind, ValueTypes } from '../../types';
|
||||
import { createMockMetricDescriptor } from '../__mocks__/cloudMonitoringMetricDescriptor';
|
||||
import { createMockMetricQuery } from '../__mocks__/cloudMonitoringQuery';
|
||||
import { MetricKind, ValueTypes } from '../types';
|
||||
|
||||
import { Preprocessor } from './Preprocessor';
|
||||
|
@ -1,13 +1,11 @@
|
||||
import React, { FunctionComponent, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { RadioButtonGroup } from '@grafana/ui';
|
||||
import { EditorField, RadioButtonGroup } from '@grafana/ui';
|
||||
|
||||
import { getAlignmentPickerData } from '../functions';
|
||||
import { MetricDescriptor, MetricKind, MetricQuery, PreprocessorType, ValueTypes } from '../types';
|
||||
|
||||
import { QueryEditorRow } from '.';
|
||||
|
||||
const NONE_OPTION = { label: 'None', value: PreprocessorType.None };
|
||||
|
||||
export interface Props {
|
||||
@ -19,7 +17,7 @@ export interface Props {
|
||||
export const Preprocessor: FunctionComponent<Props> = ({ query, metricDescriptor, onChange }) => {
|
||||
const options = useOptions(metricDescriptor);
|
||||
return (
|
||||
<QueryEditorRow
|
||||
<EditorField
|
||||
label="Pre-processing"
|
||||
tooltip="Preprocessing options are displayed when the selected metric has a metric kind of delta or cumulative. The specific options available are determined by the metic's value type. If you select 'Rate', data points are aligned and converted to a rate per time series. If you select 'Delta', data points are aligned by their delta (difference) per time series"
|
||||
>
|
||||
@ -31,8 +29,8 @@ export const Preprocessor: FunctionComponent<Props> = ({ query, metricDescriptor
|
||||
}}
|
||||
value={query.preprocessor ?? PreprocessorType.None}
|
||||
options={options}
|
||||
></RadioButtonGroup>
|
||||
</QueryEditorRow>
|
||||
/>
|
||||
</EditorField>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,13 +1,10 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select } from '@grafana/ui';
|
||||
import { EditorField, Select } from '@grafana/ui';
|
||||
|
||||
import { SELECT_WIDTH } from '../constants';
|
||||
import CloudMonitoringDatasource from '../datasource';
|
||||
|
||||
import { QueryEditorRow } from '.';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
datasource: CloudMonitoringDatasource;
|
||||
@ -35,9 +32,9 @@ export function Project({ refId, projectName, datasource, onChange, templateVari
|
||||
);
|
||||
|
||||
return (
|
||||
<QueryEditorRow label="Project" htmlFor={`${refId}-project`}>
|
||||
<EditorField label="Project">
|
||||
<Select
|
||||
width={SELECT_WIDTH}
|
||||
width="auto"
|
||||
allowCustomValue
|
||||
formatCreateLabel={(v) => `Use project: ${v}`}
|
||||
onChange={({ value }) => onChange(value!)}
|
||||
@ -46,6 +43,6 @@ export function Project({ refId, projectName, datasource, onChange, templateVari
|
||||
placeholder="Select Project"
|
||||
inputId={`${refId}-project`}
|
||||
/>
|
||||
</QueryEditorRow>
|
||||
</EditorField>
|
||||
);
|
||||
}
|
||||
|
@ -1,21 +1,16 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { QueryEditorProps, toOption } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Button, EditorRows, Select } from '@grafana/ui';
|
||||
import { EditorRows } from '@grafana/ui';
|
||||
|
||||
import { QUERY_TYPES, SELECT_WIDTH } from '../constants';
|
||||
import CloudMonitoringDatasource from '../datasource';
|
||||
import { CloudMonitoringQuery, EditorMode, MetricQuery, QueryType, SLOQuery, CloudMonitoringOptions } from '../types';
|
||||
import { CloudMonitoringQuery, MetricQuery, QueryType, SLOQuery, CloudMonitoringOptions } from '../types';
|
||||
|
||||
import { MetricQueryEditor as ExperimentalMetricQueryEditor } from './Experimental/MetricQueryEditor';
|
||||
import { QueryHeader } from './Experimental/QueryHeader';
|
||||
import { SLOQueryEditor as ExperimentalSLOQueryEditor } from './Experimental/SLOQueryEditor';
|
||||
import { defaultQuery } from './MetricQueryEditor';
|
||||
import { defaultQuery as defaultSLOQuery } from './SLO/SLOQueryEditor';
|
||||
import { QueryHeader } from './QueryHeader';
|
||||
import { defaultQuery as defaultSLOQuery } from './SLOQueryEditor';
|
||||
|
||||
import { MetricQueryEditor, QueryEditorRow, SLOQueryEditor } from './';
|
||||
import { MetricQueryEditor, SLOQueryEditor } from './';
|
||||
|
||||
export type Props = QueryEditorProps<CloudMonitoringDatasource, CloudMonitoringQuery, CloudMonitoringOptions>;
|
||||
|
||||
@ -30,7 +25,7 @@ export class QueryEditor extends PureComponent<Props> {
|
||||
this.props.query.metricQuery = metricQuery;
|
||||
}
|
||||
|
||||
if (!this.props.query.hasOwnProperty('queryType')) {
|
||||
if (![QueryType.METRICS, QueryType.SLO].includes(this.props.query.queryType)) {
|
||||
this.props.query.queryType = QueryType.METRICS;
|
||||
}
|
||||
|
||||
@ -58,7 +53,7 @@ export class QueryEditor extends PureComponent<Props> {
|
||||
options: datasource.getVariables().map(toOption),
|
||||
};
|
||||
|
||||
return config.featureToggles.cloudMonitoringExperimentalUI ? (
|
||||
return (
|
||||
<EditorRows>
|
||||
<QueryHeader
|
||||
query={query}
|
||||
@ -67,69 +62,6 @@ export class QueryEditor extends PureComponent<Props> {
|
||||
onChange={onChange}
|
||||
onRunQuery={onRunQuery}
|
||||
/>
|
||||
{queryType === QueryType.METRICS && (
|
||||
<ExperimentalMetricQueryEditor
|
||||
refId={query.refId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
customMetaData={customMetaData}
|
||||
onChange={(metricQuery: MetricQuery) => {
|
||||
this.props.onChange({ ...this.props.query, metricQuery });
|
||||
}}
|
||||
onRunQuery={onRunQuery}
|
||||
datasource={datasource}
|
||||
query={metricQuery}
|
||||
/>
|
||||
)}
|
||||
|
||||
{queryType === QueryType.SLO && (
|
||||
<ExperimentalSLOQueryEditor
|
||||
refId={query.refId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
customMetaData={customMetaData}
|
||||
onChange={(query: SLOQuery) => this.onQueryChange('sloQuery', query)}
|
||||
onRunQuery={onRunQuery}
|
||||
datasource={datasource}
|
||||
query={sloQuery}
|
||||
/>
|
||||
)}
|
||||
</EditorRows>
|
||||
) : (
|
||||
<EditorRows>
|
||||
<QueryEditorRow
|
||||
label="Query type"
|
||||
fillComponent={
|
||||
query.queryType !== QueryType.SLO && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
className={css`
|
||||
margin-left: auto;
|
||||
`}
|
||||
icon="edit"
|
||||
onClick={() =>
|
||||
this.onQueryChange('metricQuery', {
|
||||
...metricQuery,
|
||||
editorMode: metricQuery.editorMode === EditorMode.MQL ? EditorMode.Visual : EditorMode.MQL,
|
||||
})
|
||||
}
|
||||
>
|
||||
{metricQuery.editorMode === EditorMode.MQL ? 'Switch to builder' : 'Edit MQL'}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
htmlFor={`${query.refId}-query-type`}
|
||||
>
|
||||
<Select
|
||||
width={SELECT_WIDTH}
|
||||
value={queryType}
|
||||
options={QUERY_TYPES}
|
||||
onChange={({ value }) => {
|
||||
onChange({ ...query, sloQuery, queryType: value! });
|
||||
onRunQuery();
|
||||
}}
|
||||
inputId={`${query.refId}-query-type`}
|
||||
/>
|
||||
</QueryEditorRow>
|
||||
|
||||
{queryType === QueryType.METRICS && (
|
||||
<MetricQueryEditor
|
||||
refId={query.refId}
|
||||
|
@ -3,8 +3,8 @@ import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { openMenu, select } from 'react-select-event';
|
||||
|
||||
import { createMockQuery, createMockSLOQuery } from '../../__mocks__/cloudMonitoringQuery';
|
||||
import { EditorMode, QueryType } from '../../types';
|
||||
import { createMockQuery, createMockSLOQuery } from '../__mocks__/cloudMonitoringQuery';
|
||||
import { EditorMode, QueryType } from '../types';
|
||||
|
||||
import { QueryHeader } from './QueryHeader';
|
||||
|
@ -2,8 +2,8 @@ import React from 'react';
|
||||
|
||||
import { EditorHeader, FlexItem, InlineSelect, RadioButtonGroup } from '@grafana/ui';
|
||||
|
||||
import { QUERY_TYPES } from '../../constants';
|
||||
import { EditorMode, CloudMonitoringQuery, QueryType, SLOQuery, MetricQuery } from '../../types';
|
||||
import { QUERY_TYPES } from '../constants';
|
||||
import { EditorMode, CloudMonitoringQuery, QueryType, SLOQuery, MetricQuery } from '../types';
|
||||
|
||||
export interface QueryEditorHeaderProps {
|
||||
query: CloudMonitoringQuery;
|
@ -3,8 +3,8 @@ import React, { useEffect, useState } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select, EditorField } from '@grafana/ui';
|
||||
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { SLOQuery } from '../../types';
|
||||
import CloudMonitoringDatasource from '../datasource';
|
||||
import { SLOQuery } from '../types';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
@ -1,53 +0,0 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select } from '@grafana/ui';
|
||||
|
||||
import { QueryEditorRow } from '..';
|
||||
import { SELECT_WIDTH, LOOKBACK_PERIODS } from '../../constants';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
onChange: (lookbackPeriod: string) => void;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
current?: string;
|
||||
}
|
||||
|
||||
export const LookbackPeriodSelect: FunctionComponent<Props> = ({
|
||||
refId,
|
||||
current,
|
||||
templateVariableOptions,
|
||||
onChange,
|
||||
}) => {
|
||||
const options = LOOKBACK_PERIODS.map((lp) => ({
|
||||
...lp,
|
||||
label: lp.text,
|
||||
}));
|
||||
if (current && !options.find((op) => op.value === current)) {
|
||||
options.push({ label: current, text: current, value: current, hidden: false });
|
||||
}
|
||||
const visibleOptions = options.filter((lp) => !lp.hidden);
|
||||
|
||||
return (
|
||||
<QueryEditorRow label="Lookback period" htmlFor={`${refId}-lookback-period`}>
|
||||
<Select
|
||||
inputId={`${refId}-lookback-period`}
|
||||
width={SELECT_WIDTH}
|
||||
allowCustomValue
|
||||
value={[...options, ...templateVariableOptions].find((s) => s.value === current)}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
{
|
||||
label: 'Predefined periods',
|
||||
expanded: true,
|
||||
options: visibleOptions,
|
||||
},
|
||||
]}
|
||||
onChange={({ value }) => onChange(value!)}
|
||||
/>
|
||||
</QueryEditorRow>
|
||||
);
|
||||
};
|
@ -1,56 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select } from '@grafana/ui';
|
||||
|
||||
import { QueryEditorRow } from '..';
|
||||
import { SELECT_WIDTH } from '../../constants';
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { SLOQuery } from '../../types';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
onChange: (query: SLOQuery) => void;
|
||||
query: SLOQuery;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
datasource: CloudMonitoringDatasource;
|
||||
}
|
||||
|
||||
export const SLO = ({ refId, query, templateVariableOptions, onChange, datasource }: Props) => {
|
||||
const [slos, setSLOs] = useState<Array<SelectableValue<string>>>([]);
|
||||
const { projectName, serviceId } = query;
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectName || !serviceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
datasource.getServiceLevelObjectives(projectName, serviceId).then((sloIds: Array<SelectableValue<string>>) => {
|
||||
setSLOs([
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
...sloIds,
|
||||
]);
|
||||
});
|
||||
}, [datasource, projectName, serviceId, templateVariableOptions]);
|
||||
|
||||
return (
|
||||
<QueryEditorRow label="SLO" htmlFor={`${refId}-slo`}>
|
||||
<Select
|
||||
inputId={`${refId}-slo`}
|
||||
width={SELECT_WIDTH}
|
||||
allowCustomValue
|
||||
value={query?.sloId && { value: query?.sloId, label: query?.sloName || query?.sloId }}
|
||||
placeholder="Select SLO"
|
||||
options={slos}
|
||||
onChange={async ({ value: sloId = '', label: sloName = '' }) => {
|
||||
const slos = await datasource.getServiceLevelObjectives(projectName, serviceId);
|
||||
const slo = slos.find(({ value }) => value === datasource.templateSrv.replace(sloId));
|
||||
onChange({ ...query, sloId, sloName, goal: slo?.goal });
|
||||
}}
|
||||
/>
|
||||
</QueryEditorRow>
|
||||
);
|
||||
};
|
@ -1,100 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
import { AliasBy, PeriodSelect, AlignmentPeriodLabel, Project, QueryEditorRow } from '..';
|
||||
import { ALIGNMENT_PERIODS, SELECT_WIDTH, SLO_BURN_RATE_SELECTOR_NAME } from '../../constants';
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { AlignmentTypes, CustomMetaData, SLOQuery } from '../../types';
|
||||
|
||||
import { LookbackPeriodSelect } from './LookbackPeriodSelect';
|
||||
|
||||
import { Selector, Service, SLO } from '.';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
customMetaData: CustomMetaData;
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
onChange: (query: SLOQuery) => void;
|
||||
onRunQuery: () => void;
|
||||
query: SLOQuery;
|
||||
datasource: CloudMonitoringDatasource;
|
||||
}
|
||||
|
||||
export const defaultQuery: (dataSource: CloudMonitoringDatasource) => SLOQuery = (dataSource) => ({
|
||||
projectName: dataSource.getDefaultProject(),
|
||||
alignmentPeriod: 'cloud-monitoring-auto',
|
||||
perSeriesAligner: AlignmentTypes.ALIGN_MEAN,
|
||||
aliasBy: '',
|
||||
selectorName: 'select_slo_health',
|
||||
serviceId: '',
|
||||
serviceName: '',
|
||||
sloId: '',
|
||||
sloName: '',
|
||||
lookbackPeriod: '',
|
||||
});
|
||||
|
||||
export function SLOQueryEditor({
|
||||
refId,
|
||||
query,
|
||||
datasource,
|
||||
onChange,
|
||||
variableOptionGroup,
|
||||
customMetaData,
|
||||
}: React.PropsWithChildren<Props>) {
|
||||
return (
|
||||
<>
|
||||
<Project
|
||||
refId={refId}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
projectName={query.projectName}
|
||||
datasource={datasource}
|
||||
onChange={(projectName) => onChange({ ...query, projectName })}
|
||||
/>
|
||||
<Service
|
||||
refId={refId}
|
||||
datasource={datasource}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
></Service>
|
||||
<SLO
|
||||
refId={refId}
|
||||
datasource={datasource}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
></SLO>
|
||||
<Selector
|
||||
refId={refId}
|
||||
datasource={datasource}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
></Selector>
|
||||
|
||||
{query.selectorName === SLO_BURN_RATE_SELECTOR_NAME && (
|
||||
<LookbackPeriodSelect
|
||||
refId={refId}
|
||||
onChange={(lookbackPeriod) => onChange({ ...query, lookbackPeriod: lookbackPeriod })}
|
||||
current={query.lookbackPeriod}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
/>
|
||||
)}
|
||||
|
||||
<QueryEditorRow label="Alignment period" htmlFor={`${refId}-alignment-period`}>
|
||||
<PeriodSelect
|
||||
inputId={`${refId}-alignment-period`}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
selectWidth={SELECT_WIDTH}
|
||||
current={query.alignmentPeriod}
|
||||
onChange={(period) => onChange({ ...query, alignmentPeriod: period })}
|
||||
aligmentPeriods={ALIGNMENT_PERIODS}
|
||||
/>
|
||||
<AlignmentPeriodLabel datasource={datasource} customMetaData={customMetaData} />
|
||||
</QueryEditorRow>
|
||||
|
||||
<AliasBy refId={refId} value={query.aliasBy} onChange={(aliasBy) => onChange({ ...query, aliasBy })} />
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select } from '@grafana/ui';
|
||||
|
||||
import { QueryEditorRow } from '..';
|
||||
import { SELECT_WIDTH, SELECTORS } from '../../constants';
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { SLOQuery } from '../../types';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
onChange: (query: SLOQuery) => void;
|
||||
query: SLOQuery;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
datasource: CloudMonitoringDatasource;
|
||||
}
|
||||
|
||||
export const Selector = ({ refId, query, templateVariableOptions, onChange, datasource }: Props) => {
|
||||
return (
|
||||
<QueryEditorRow label="Selector" htmlFor={`${refId}-slo-selector`}>
|
||||
<Select
|
||||
inputId={`${refId}-slo-selector`}
|
||||
width={SELECT_WIDTH}
|
||||
allowCustomValue
|
||||
value={[...SELECTORS, ...templateVariableOptions].find((s) => s.value === query?.selectorName ?? '')}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
...SELECTORS,
|
||||
]}
|
||||
onChange={({ value: selectorName }) => onChange({ ...query, selectorName: selectorName ?? '' })}
|
||||
/>
|
||||
</QueryEditorRow>
|
||||
);
|
||||
};
|
@ -1,54 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select } from '@grafana/ui';
|
||||
|
||||
import { QueryEditorRow } from '..';
|
||||
import { SELECT_WIDTH } from '../../constants';
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { SLOQuery } from '../../types';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
onChange: (query: SLOQuery) => void;
|
||||
query: SLOQuery;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
datasource: CloudMonitoringDatasource;
|
||||
}
|
||||
|
||||
export const Service = ({ refId, query, templateVariableOptions, onChange, datasource }: Props) => {
|
||||
const [services, setServices] = useState<Array<SelectableValue<string>>>([]);
|
||||
const { projectName } = query;
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectName) {
|
||||
return;
|
||||
}
|
||||
|
||||
datasource.getSLOServices(projectName).then((services: Array<SelectableValue<string>>) => {
|
||||
setServices([
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
...services,
|
||||
]);
|
||||
});
|
||||
}, [datasource, projectName, templateVariableOptions]);
|
||||
|
||||
return (
|
||||
<QueryEditorRow label="Service" htmlFor={`${refId}-slo-service`}>
|
||||
<Select
|
||||
inputId={`${refId}-slo-service`}
|
||||
width={SELECT_WIDTH}
|
||||
allowCustomValue
|
||||
value={query?.serviceId && { value: query?.serviceId, label: query?.serviceName || query?.serviceId }}
|
||||
placeholder="Select service"
|
||||
options={services}
|
||||
onChange={({ value: serviceId = '', label: serviceName = '' }) =>
|
||||
onChange({ ...query, serviceId, serviceName, sloId: '' })
|
||||
}
|
||||
/>
|
||||
</QueryEditorRow>
|
||||
);
|
||||
};
|
@ -1,3 +0,0 @@
|
||||
export { Service } from './Service';
|
||||
export { SLO } from './SLO';
|
||||
export { Selector } from './Selector';
|
@ -3,10 +3,10 @@ import React, { useMemo } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorFieldGroup, EditorRow } from '@grafana/ui';
|
||||
|
||||
import { ALIGNMENT_PERIODS, SLO_BURN_RATE_SELECTOR_NAME } from '../../constants';
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { alignmentPeriodLabel } from '../../functions';
|
||||
import { AlignmentTypes, CustomMetaData, SLOQuery } from '../../types';
|
||||
import { ALIGNMENT_PERIODS, SLO_BURN_RATE_SELECTOR_NAME } from '../constants';
|
||||
import CloudMonitoringDatasource from '../datasource';
|
||||
import { alignmentPeriodLabel } from '../functions';
|
||||
import { AlignmentTypes, CustomMetaData, SLOQuery } from '../types';
|
||||
|
||||
import { AliasBy } from './AliasBy';
|
||||
import { LookbackPeriodSelect } from './LookbackPeriodSelect';
|
@ -3,9 +3,9 @@ import React from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, Select } from '@grafana/ui';
|
||||
|
||||
import { SELECTORS } from '../../constants';
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { SLOQuery } from '../../types';
|
||||
import { SELECTORS } from '../constants';
|
||||
import CloudMonitoringDatasource from '../datasource';
|
||||
import { SLOQuery } from '../types';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
@ -3,8 +3,8 @@ import React, { useEffect, useState } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, Select } from '@grafana/ui';
|
||||
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { SLOQuery } from '../../types';
|
||||
import CloudMonitoringDatasource from '../datasource';
|
||||
import { SLOQuery } from '../types';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
@ -1,11 +1,17 @@
|
||||
import React from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorRow } from '@grafana/ui';
|
||||
|
||||
import CloudMonitoringDatasource from '../datasource';
|
||||
import { CustomMetaData, MetricDescriptor, MetricQuery, SLOQuery } from '../types';
|
||||
|
||||
import { Alignment, GroupBy, LabelFilter, Metrics, Preprocessor } from '.';
|
||||
import { AliasBy } from './AliasBy';
|
||||
import { Alignment } from './Alignment';
|
||||
import { GroupBy } from './GroupBy';
|
||||
import { LabelFilter } from './LabelFilter';
|
||||
import { Metrics } from './Metrics';
|
||||
import { Preprocessor } from './Preprocessor';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
@ -31,12 +37,13 @@ function Editor({
|
||||
return (
|
||||
<Metrics
|
||||
refId={refId}
|
||||
templateSrv={datasource.templateSrv}
|
||||
projectName={query.projectName}
|
||||
metricType={query.metricType}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
datasource={datasource}
|
||||
onChange={onMetricTypeChange}
|
||||
onProjectChange={onChange}
|
||||
query={query}
|
||||
>
|
||||
{(metric) => (
|
||||
<>
|
||||
@ -46,23 +53,32 @@ function Editor({
|
||||
onChange={(filters: string[]) => onChange({ ...query, filters })}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
/>
|
||||
<Preprocessor metricDescriptor={metric} query={query} onChange={onChange} />
|
||||
<GroupBy
|
||||
refId={refId}
|
||||
labels={Object.keys(labels)}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
metricDescriptor={metric}
|
||||
/>
|
||||
<Alignment
|
||||
refId={refId}
|
||||
datasource={datasource}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
query={query}
|
||||
customMetaData={customMetaData}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<EditorRow>
|
||||
<Preprocessor metricDescriptor={metric} query={query} onChange={onChange} />
|
||||
<GroupBy
|
||||
refId={refId}
|
||||
labels={Object.keys(labels)}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
metricDescriptor={metric}
|
||||
/>
|
||||
<Alignment
|
||||
refId={refId}
|
||||
datasource={datasource}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
query={query}
|
||||
customMetaData={customMetaData}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<AliasBy
|
||||
refId={refId}
|
||||
value={query.aliasBy}
|
||||
onChange={(aliasBy) => {
|
||||
onChange({ ...query, aliasBy });
|
||||
}}
|
||||
/>
|
||||
</EditorRow>
|
||||
</>
|
||||
)}
|
||||
</Metrics>
|
||||
|
@ -9,7 +9,7 @@ export { AlignmentPeriodLabel } from './AlignmentPeriodLabel';
|
||||
export { AliasBy } from './AliasBy';
|
||||
export { Aggregation } from './Aggregation';
|
||||
export { MetricQueryEditor } from './MetricQueryEditor';
|
||||
export { SLOQueryEditor } from './SLO/SLOQueryEditor';
|
||||
export { SLOQueryEditor } from './SLOQueryEditor';
|
||||
export { MQLQueryEditor } from './MQLQueryEditor';
|
||||
export { VariableQueryField, QueryEditorRow, QueryEditorField } from './Fields';
|
||||
export { VisualMetricQueryEditor } from './VisualMetricQueryEditor';
|
||||
|
Loading…
Reference in New Issue
Block a user