mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
CloudMonitor: Consolidate editor rows (#52675)
* consolidate project, metric, and service * CloudMonitor: make AlignmentPeriodLabel a tooltip alignmentPeriodLabel now returns a string instead of component. This is because as a <label> it would cause wrapping issues when the screen size would change. It also isn't essential information _unless_ the user is using a template variable for the Alignment period. * refactor: remove any hard-coded widths for auto * chore: use newly moved experimental comps in UI
This commit is contained in:
parent
921d32d70b
commit
165efcd9cc
@ -1,7 +1,7 @@
|
||||
import { debounce } from 'lodash';
|
||||
import React, { FunctionComponent, useState } from 'react';
|
||||
|
||||
import { EditorField, EditorRow, Input } from '@grafana/ui';
|
||||
import { EditorField, Input } from '@grafana/ui';
|
||||
|
||||
import { SELECT_WIDTH } from '../../constants';
|
||||
|
||||
@ -22,10 +22,8 @@ export const AliasBy: FunctionComponent<Props> = ({ refId, value = '', onChange
|
||||
};
|
||||
|
||||
return (
|
||||
<EditorRow>
|
||||
<EditorField label="Alias by">
|
||||
<Input id={`${refId}-alias-by`} width={SELECT_WIDTH} value={alias} onChange={onChange} />
|
||||
</EditorField>
|
||||
</EditorRow>
|
||||
<EditorField label="Alias by">
|
||||
<Input id={`${refId}-alias-by`} width={SELECT_WIDTH} value={alias} onChange={onChange} />
|
||||
</EditorField>
|
||||
);
|
||||
};
|
||||
|
@ -80,23 +80,4 @@ describe('Alignment', () => {
|
||||
await userEvent.click(screen.getByText('1m'));
|
||||
expect(onChange).toBeCalledWith(expect.objectContaining({ alignmentPeriod: '+60s' }));
|
||||
});
|
||||
|
||||
it('renders period label if alignment period and per series aligner is set', () => {
|
||||
const datasource = createMockDatasource();
|
||||
const query = createMockMetricQuery();
|
||||
const onChange = jest.fn();
|
||||
|
||||
render(
|
||||
<Alignment
|
||||
refId="refId"
|
||||
customMetaData={{ perSeriesAligner: 'ALIGN_DELTA', alignmentPeriod: '10' }}
|
||||
datasource={datasource}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
templateVariableOptions={[]}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('10s interval (delta)'));
|
||||
});
|
||||
});
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React, { FC } from 'react';
|
||||
import React, { FC, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorRow, EditorFieldGroup, EditorField, Stack } from '@grafana/ui';
|
||||
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 } from './AlignmentFunction';
|
||||
import { AlignmentPeriodLabel } from './AlignmentPeriodLabel';
|
||||
import { PeriodSelect } from './PeriodSelect';
|
||||
|
||||
export interface Props {
|
||||
@ -28,34 +28,29 @@ export const Alignment: FC<Props> = ({
|
||||
customMetaData,
|
||||
datasource,
|
||||
}) => {
|
||||
const alignmentLabel = useMemo(() => alignmentPeriodLabel(customMetaData, datasource), [customMetaData, datasource]);
|
||||
return (
|
||||
<EditorRow>
|
||||
<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">
|
||||
<PeriodSelect
|
||||
inputId={`${refId}-alignment-period`}
|
||||
selectWidth={SELECT_WIDTH}
|
||||
templateVariableOptions={templateVariableOptions}
|
||||
current={query.alignmentPeriod}
|
||||
onChange={(period) => onChange({ ...query, alignmentPeriod: period })}
|
||||
aligmentPeriods={ALIGNMENT_PERIODS}
|
||||
/>
|
||||
</EditorField>
|
||||
<Stack alignItems="flex-end">
|
||||
<AlignmentPeriodLabel datasource={datasource} customMetaData={customMetaData} />
|
||||
</Stack>
|
||||
</EditorFieldGroup>
|
||||
</EditorRow>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
@ -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={[
|
||||
|
@ -1,28 +0,0 @@
|
||||
import React, { FC, useMemo } from 'react';
|
||||
|
||||
import { rangeUtil } from '@grafana/data';
|
||||
|
||||
import { ALIGNMENTS } from '../../constants';
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { CustomMetaData } from '../../types';
|
||||
|
||||
export interface Props {
|
||||
customMetaData: CustomMetaData;
|
||||
datasource: CloudMonitoringDatasource;
|
||||
}
|
||||
|
||||
export const AlignmentPeriodLabel: FC<Props> = ({ customMetaData, datasource }) => {
|
||||
const { perSeriesAligner, alignmentPeriod } = customMetaData;
|
||||
const formatAlignmentText = useMemo(() => {
|
||||
if (!alignmentPeriod || !perSeriesAligner) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const alignment = ALIGNMENTS.find((ap) => ap.value === datasource.templateSrv.replace(perSeriesAligner));
|
||||
const seconds = parseInt(alignmentPeriod, 10);
|
||||
const hms = rangeUtil.secondsToHms(seconds);
|
||||
return `${hms} interval (${alignment?.text ?? ''})`;
|
||||
}, [datasource, perSeriesAligner, alignmentPeriod]);
|
||||
|
||||
return <label>{formatAlignmentText}</label>;
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import React, { FunctionComponent, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorFieldGroup, EditorRow, MultiSelect } from '@grafana/ui';
|
||||
import { EditorField, EditorFieldGroup, MultiSelect } from '@grafana/ui';
|
||||
|
||||
import { SYSTEM_LABELS } from '../../constants';
|
||||
import { labelsToGroupedOptions } from '../../functions';
|
||||
@ -32,32 +32,30 @@ export const GroupBy: FunctionComponent<Props> = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<EditorRow>
|
||||
<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>
|
||||
<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!) });
|
||||
}}
|
||||
/>
|
||||
</EditorFieldGroup>
|
||||
</EditorRow>
|
||||
</EditorField>
|
||||
<Aggregation
|
||||
metricDescriptor={metricDescriptor}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
crossSeriesReducer={query.crossSeriesReducer}
|
||||
groupBys={query.groupBys ?? []}
|
||||
onChange={(crossSeriesReducer) => onChange({ ...query, crossSeriesReducer })}
|
||||
refId={refId}
|
||||
/>
|
||||
</EditorFieldGroup>
|
||||
);
|
||||
};
|
||||
|
@ -18,9 +18,7 @@ import {
|
||||
} from '../../types';
|
||||
|
||||
import { MQLQueryEditor } from './../MQLQueryEditor';
|
||||
import { AliasBy } from './AliasBy';
|
||||
import { GraphPeriod } from './GraphPeriod';
|
||||
import { Project } from './Project';
|
||||
import { VisualMetricQueryEditor } from './VisualMetricQueryEditor';
|
||||
|
||||
export interface Props {
|
||||
@ -107,16 +105,6 @@ function Editor({
|
||||
|
||||
return (
|
||||
<EditorRows>
|
||||
<Project
|
||||
refId={refId}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
projectName={projectName}
|
||||
datasource={datasource}
|
||||
onChange={(projectName) => {
|
||||
onChange({ ...query, projectName });
|
||||
}}
|
||||
/>
|
||||
|
||||
{editorMode === EditorMode.Visual && (
|
||||
<VisualMetricQueryEditor
|
||||
refId={refId}
|
||||
@ -145,14 +133,6 @@ function Editor({
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<AliasBy
|
||||
refId={refId}
|
||||
value={query.aliasBy}
|
||||
onChange={(aliasBy) => {
|
||||
onChange({ ...query, aliasBy });
|
||||
}}
|
||||
/>
|
||||
</EditorRows>
|
||||
);
|
||||
}
|
||||
|
@ -4,12 +4,14 @@ import { openMenu, select } from 'react-select-event';
|
||||
|
||||
import { createMockDatasource } from '../../__mocks__/cloudMonitoringDatasource';
|
||||
import { createMockMetricDescriptor } from '../../__mocks__/cloudMonitoringMetricDescriptor';
|
||||
import { createMockMetricQuery } from '../../__mocks__/cloudMonitoringQuery';
|
||||
|
||||
import { Metrics } from './Metrics';
|
||||
|
||||
describe('Metrics', () => {
|
||||
it('renders metrics fields', async () => {
|
||||
const onChange = jest.fn();
|
||||
const query = createMockMetricQuery();
|
||||
const datasource = createMockDatasource();
|
||||
|
||||
render(
|
||||
@ -20,6 +22,8 @@ describe('Metrics', () => {
|
||||
templateVariableOptions={[]}
|
||||
datasource={datasource}
|
||||
onChange={onChange}
|
||||
onProjectChange={jest.fn()}
|
||||
query={query}
|
||||
>
|
||||
{() => <div />}
|
||||
</Metrics>
|
||||
@ -31,6 +35,7 @@ describe('Metrics', () => {
|
||||
|
||||
it('can select a service', async () => {
|
||||
const onChange = jest.fn();
|
||||
const query = createMockMetricQuery();
|
||||
const datasource = createMockDatasource({
|
||||
getMetricTypes: jest.fn().mockResolvedValue([createMockMetricDescriptor()]),
|
||||
});
|
||||
@ -43,6 +48,8 @@ describe('Metrics', () => {
|
||||
templateVariableOptions={[]}
|
||||
datasource={datasource}
|
||||
onChange={onChange}
|
||||
onProjectChange={jest.fn()}
|
||||
query={query}
|
||||
>
|
||||
{() => <div />}
|
||||
</Metrics>
|
||||
@ -56,6 +63,7 @@ describe('Metrics', () => {
|
||||
|
||||
it('can select a metric name', async () => {
|
||||
const onChange = jest.fn();
|
||||
const query = createMockMetricQuery();
|
||||
const datasource = createMockDatasource({
|
||||
getMetricTypes: jest.fn().mockResolvedValue([createMockMetricDescriptor()]),
|
||||
});
|
||||
@ -68,6 +76,8 @@ describe('Metrics', () => {
|
||||
templateVariableOptions={[]}
|
||||
datasource={datasource}
|
||||
onChange={onChange}
|
||||
onProjectChange={jest.fn()}
|
||||
query={query}
|
||||
>
|
||||
{() => <div />}
|
||||
</Metrics>
|
||||
@ -81,6 +91,7 @@ describe('Metrics', () => {
|
||||
|
||||
it('should render available metric options according to the selected service', async () => {
|
||||
const onChange = jest.fn();
|
||||
const query = createMockMetricQuery();
|
||||
const datasource = createMockDatasource({
|
||||
getMetricTypes: jest.fn().mockResolvedValue([
|
||||
createMockMetricDescriptor({
|
||||
@ -115,6 +126,8 @@ describe('Metrics', () => {
|
||||
templateVariableOptions={[]}
|
||||
datasource={datasource}
|
||||
onChange={onChange}
|
||||
onProjectChange={jest.fn()}
|
||||
query={query}
|
||||
>
|
||||
{() => <div />}
|
||||
</Metrics>
|
||||
@ -167,6 +180,7 @@ describe('Metrics', () => {
|
||||
}),
|
||||
]),
|
||||
});
|
||||
const query = createMockMetricQuery();
|
||||
|
||||
render(
|
||||
<Metrics
|
||||
@ -176,11 +190,12 @@ describe('Metrics', () => {
|
||||
templateVariableOptions={[]}
|
||||
datasource={datasource}
|
||||
onChange={onChange}
|
||||
onProjectChange={jest.fn()}
|
||||
query={query}
|
||||
>
|
||||
{() => <div />}
|
||||
</Metrics>
|
||||
);
|
||||
|
||||
const service = await screen.findByLabelText('Service');
|
||||
await openMenu(service);
|
||||
expect(screen.getAllByLabelText('Select option').length).toEqual(2);
|
||||
|
@ -6,7 +6,9 @@ import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorFieldGroup, EditorRow, getSelectStyles, Select, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { MetricDescriptor } from '../../types';
|
||||
import { MetricDescriptor, MetricQuery } from '../../types';
|
||||
|
||||
import { Project } from './Project';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
@ -15,7 +17,9 @@ export interface Props {
|
||||
datasource: CloudMonitoringDatasource;
|
||||
projectName: string;
|
||||
metricType: string;
|
||||
query: MetricQuery;
|
||||
children: (metricDescriptor?: MetricDescriptor) => JSX.Element;
|
||||
onProjectChange: (query: MetricQuery) => void;
|
||||
}
|
||||
|
||||
export function Metrics(props: Props) {
|
||||
@ -30,7 +34,17 @@ export function Metrics(props: Props) {
|
||||
|
||||
const customStyle = useStyles2(getStyles);
|
||||
|
||||
const { metricType, templateVariableOptions, projectName, datasource, onChange, children } = props;
|
||||
const {
|
||||
onProjectChange,
|
||||
query,
|
||||
refId,
|
||||
metricType,
|
||||
templateVariableOptions,
|
||||
projectName,
|
||||
datasource,
|
||||
onChange,
|
||||
children,
|
||||
} = props;
|
||||
const { templateSrv } = datasource;
|
||||
|
||||
const getSelectedMetricDescriptor = useCallback(
|
||||
@ -121,6 +135,16 @@ export function Metrics(props: Props) {
|
||||
<>
|
||||
<EditorRow>
|
||||
<EditorFieldGroup>
|
||||
<Project
|
||||
refId={refId}
|
||||
templateVariableOptions={templateVariableOptions}
|
||||
projectName={projectName}
|
||||
datasource={datasource}
|
||||
onChange={(projectName) => {
|
||||
onProjectChange({ ...query, projectName });
|
||||
}}
|
||||
/>
|
||||
|
||||
<EditorField label="Service" width="auto">
|
||||
<Select
|
||||
width="auto"
|
||||
|
@ -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={[
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { FunctionComponent, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorRow, RadioButtonGroup } from '@grafana/ui';
|
||||
import { EditorField, RadioButtonGroup } from '@grafana/ui';
|
||||
|
||||
import { getAlignmentPickerData } from '../../functions';
|
||||
import { MetricDescriptor, MetricKind, MetricQuery, PreprocessorType, ValueTypes } from '../../types';
|
||||
@ -17,22 +17,20 @@ export interface Props {
|
||||
export const Preprocessor: FunctionComponent<Props> = ({ query, metricDescriptor, onChange }) => {
|
||||
const options = useOptions(metricDescriptor);
|
||||
return (
|
||||
<EditorRow>
|
||||
<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>
|
||||
</EditorRow>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorRow, Select } from '@grafana/ui';
|
||||
import { EditorField, Select } from '@grafana/ui';
|
||||
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
|
||||
@ -32,19 +32,17 @@ export function Project({ refId, projectName, datasource, onChange, templateVari
|
||||
);
|
||||
|
||||
return (
|
||||
<EditorRow>
|
||||
<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>
|
||||
</EditorRow>
|
||||
<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,7 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorRow, Select } from '@grafana/ui';
|
||||
import { Select, EditorField } from '@grafana/ui';
|
||||
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { SLOQuery } from '../../types';
|
||||
@ -35,22 +35,20 @@ export const SLO: React.FC<Props> = ({ refId, query, templateVariableOptions, on
|
||||
}, [datasource, projectName, serviceId, templateVariableOptions]);
|
||||
|
||||
return (
|
||||
<EditorRow>
|
||||
<EditorField label="SLO">
|
||||
<Select
|
||||
inputId={`${refId}-slo`}
|
||||
width="auto"
|
||||
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 });
|
||||
}}
|
||||
/>
|
||||
</EditorField>
|
||||
</EditorRow>
|
||||
<EditorField label="SLO">
|
||||
<Select
|
||||
inputId={`${refId}-slo`}
|
||||
width="auto"
|
||||
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 });
|
||||
}}
|
||||
/>
|
||||
</EditorField>
|
||||
);
|
||||
};
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorRow, EditorFieldGroup, EditorField, Stack } from '@grafana/ui';
|
||||
import { EditorField, EditorFieldGroup, EditorRow } from '@grafana/ui';
|
||||
|
||||
import { ALIGNMENT_PERIODS } from '../../constants';
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { alignmentPeriodLabel } from '../../functions';
|
||||
import { AlignmentTypes, CustomMetaData, SLOQuery } from '../../types';
|
||||
|
||||
import { AliasBy } from './AliasBy';
|
||||
import { AlignmentPeriodLabel } from './AlignmentPeriodLabel';
|
||||
import { PeriodSelect } from './PeriodSelect';
|
||||
import { Project } from './Project';
|
||||
import { SLO } from './SLO';
|
||||
@ -45,40 +45,41 @@ export function SLOQueryEditor({
|
||||
variableOptionGroup,
|
||||
customMetaData,
|
||||
}: React.PropsWithChildren<Props>) {
|
||||
const alignmentLabel = useMemo(() => alignmentPeriodLabel(customMetaData, datasource), [customMetaData, datasource]);
|
||||
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}
|
||||
/>
|
||||
<SLO
|
||||
refId={refId}
|
||||
datasource={datasource}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Selector
|
||||
refId={refId}
|
||||
datasource={datasource}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
||||
<EditorRow>
|
||||
<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}
|
||||
/>
|
||||
<SLO
|
||||
refId={refId}
|
||||
datasource={datasource}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Selector
|
||||
refId={refId}
|
||||
datasource={datasource}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
||||
<EditorFieldGroup>
|
||||
<EditorField label="Alignment period">
|
||||
<EditorField label="Alignment period" tooltip={alignmentLabel}>
|
||||
<PeriodSelect
|
||||
inputId={`${refId}-alignment-period`}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
@ -87,13 +88,10 @@ export function SLOQueryEditor({
|
||||
aligmentPeriods={ALIGNMENT_PERIODS}
|
||||
/>
|
||||
</EditorField>
|
||||
<Stack alignItems="flex-end">
|
||||
<AlignmentPeriodLabel datasource={datasource} customMetaData={customMetaData} />
|
||||
</Stack>
|
||||
</EditorFieldGroup>
|
||||
</EditorRow>
|
||||
|
||||
<AliasBy refId={refId} value={query.aliasBy} onChange={(aliasBy) => onChange({ ...query, aliasBy })} />
|
||||
<AliasBy refId={refId} value={query.aliasBy} onChange={(aliasBy) => onChange({ ...query, aliasBy })} />
|
||||
</EditorRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorRow, Select } from '@grafana/ui';
|
||||
import { EditorField, Select } from '@grafana/ui';
|
||||
|
||||
import { SELECTORS } from '../../constants';
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
@ -17,23 +17,21 @@ export interface Props {
|
||||
|
||||
export const Selector: React.FC<Props> = ({ refId, query, templateVariableOptions, onChange, datasource }) => {
|
||||
return (
|
||||
<EditorRow>
|
||||
<EditorField label="Selector" htmlFor={`${refId}-slo-selector`}>
|
||||
<Select
|
||||
inputId={`${refId}-slo-selector`}
|
||||
width="auto"
|
||||
allowCustomValue
|
||||
value={[...SELECTORS, ...templateVariableOptions].find((s) => s.value === query?.selectorName ?? '')}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
...SELECTORS,
|
||||
]}
|
||||
onChange={({ value: selectorName }) => onChange({ ...query, selectorName: selectorName ?? '' })}
|
||||
/>
|
||||
</EditorField>
|
||||
</EditorRow>
|
||||
<EditorField label="Selector" htmlFor={`${refId}-slo-selector`}>
|
||||
<Select
|
||||
inputId={`${refId}-slo-selector`}
|
||||
width="auto"
|
||||
allowCustomValue
|
||||
value={[...SELECTORS, ...templateVariableOptions].find((s) => s.value === query?.selectorName ?? '')}
|
||||
options={[
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: templateVariableOptions,
|
||||
},
|
||||
...SELECTORS,
|
||||
]}
|
||||
onChange={({ value: selectorName }) => onChange({ ...query, selectorName: selectorName ?? '' })}
|
||||
/>
|
||||
</EditorField>
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorRow, Select } from '@grafana/ui';
|
||||
import { EditorField, Select } from '@grafana/ui';
|
||||
|
||||
import CloudMonitoringDatasource from '../../datasource';
|
||||
import { SLOQuery } from '../../types';
|
||||
@ -35,20 +35,18 @@ export const Service: React.FC<Props> = ({ refId, query, templateVariableOptions
|
||||
}, [datasource, projectName, templateVariableOptions]);
|
||||
|
||||
return (
|
||||
<EditorRow>
|
||||
<EditorField label="Service">
|
||||
<Select
|
||||
inputId={`${refId}-slo-service`}
|
||||
width="auto"
|
||||
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: '' })
|
||||
}
|
||||
/>
|
||||
</EditorField>
|
||||
</EditorRow>
|
||||
<EditorField label="Service">
|
||||
<Select
|
||||
inputId={`${refId}-slo-service`}
|
||||
width="auto"
|
||||
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: '' })
|
||||
}
|
||||
/>
|
||||
</EditorField>
|
||||
);
|
||||
};
|
||||
|
@ -1,10 +1,12 @@
|
||||
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';
|
||||
@ -40,6 +42,8 @@ function Editor({
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
datasource={datasource}
|
||||
onChange={onMetricTypeChange}
|
||||
onProjectChange={onChange}
|
||||
query={query}
|
||||
>
|
||||
{(metric) => (
|
||||
<>
|
||||
@ -49,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,6 +9,7 @@ import {
|
||||
getMetricTypesByService,
|
||||
labelsToGroupedOptions,
|
||||
stringArrayToFilters,
|
||||
alignmentPeriodLabel,
|
||||
} from './functions';
|
||||
import { newMockDatasource } from './specs/testData';
|
||||
import { AlignmentTypes, MetricDescriptor, MetricKind, ValueTypes } from './types';
|
||||
@ -226,4 +227,13 @@ describe('functions', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('alignmentPeriodLabel', () => {
|
||||
it('returns period label if alignment period and per series aligner is set', () => {
|
||||
const datasource = newMockDatasource();
|
||||
|
||||
const label = alignmentPeriodLabel({ perSeriesAligner: 'ALIGN_DELTA', alignmentPeriod: '10' }, datasource);
|
||||
expect(label).toBe('10s interval (delta)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { chunk, initial, startCase, uniqBy } from 'lodash';
|
||||
|
||||
import { rangeUtil } from '@grafana/data';
|
||||
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import { AGGREGATIONS, ALIGNMENTS, SYSTEM_LABELS } from './constants';
|
||||
import CloudMonitoringDatasource from './datasource';
|
||||
import { AlignmentTypes, MetricDescriptor, MetricKind, PreprocessorType, ValueTypes } from './types';
|
||||
import { AlignmentTypes, CustomMetaData, MetricDescriptor, MetricKind, PreprocessorType, ValueTypes } from './types';
|
||||
|
||||
const templateSrv: TemplateSrv = getTemplateSrv();
|
||||
|
||||
@ -113,3 +114,15 @@ export const stringArrayToFilters = (filterArray: string[]) =>
|
||||
value,
|
||||
condition,
|
||||
}));
|
||||
|
||||
export const alignmentPeriodLabel = (customMetaData: CustomMetaData, datasource: CloudMonitoringDatasource) => {
|
||||
const { perSeriesAligner, alignmentPeriod } = customMetaData;
|
||||
if (!alignmentPeriod || !perSeriesAligner) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const alignment = ALIGNMENTS.find((ap) => ap.value === datasource.templateSrv.replace(perSeriesAligner));
|
||||
const seconds = parseInt(alignmentPeriod, 10);
|
||||
const hms = rangeUtil.secondsToHms(seconds);
|
||||
return `${hms} interval (${alignment?.text ?? ''})`;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user