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:
Adam Simpson 2022-07-28 13:47:01 -04:00 committed by GitHub
parent 921d32d70b
commit 165efcd9cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 264 additions and 280 deletions

View File

@ -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>
);
};

View File

@ -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)'));
});
});

View File

@ -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>
);
};

View File

@ -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={[

View File

@ -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>;
};

View File

@ -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>
);
};

View File

@ -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>
);
}

View File

@ -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);

View File

@ -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"

View File

@ -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={[

View File

@ -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>
);
};

View File

@ -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>
);
}

View File

@ -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>
);
};

View File

@ -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>
</>
);
}

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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>

View File

@ -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)');
});
});
});

View File

@ -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 ?? ''})`;
};