mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Cloudmonitor: Refactor query builder (#61410)
* Reset filters when service is changed * Update to reset any query props * Reset query properties on metric change * Update tests * Refresh labels on panel time update * Review * Refactor VisualMetricsQueryEditor - Move any Metrics functionality to VisualMetricsQueryEditor - Update tests - Expose timeSrv from datasource - Update getLabels to make use of provided timeRange * Review
This commit is contained in:
parent
021eda7aad
commit
f135c6cbf1
@ -5060,9 +5060,6 @@ exports[`better eslint`] = {
|
|||||||
"public/app/plugins/datasource/cloud-monitoring/components/MQLQueryEditor.tsx:5381": [
|
"public/app/plugins/datasource/cloud-monitoring/components/MQLQueryEditor.tsx:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||||
],
|
],
|
||||||
"public/app/plugins/datasource/cloud-monitoring/components/Metrics.tsx:5381": [
|
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
|
||||||
],
|
|
||||||
"public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.test.tsx:5381": [
|
"public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.test.tsx:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
[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.", "1"]
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { TemplateSrvMock } from 'app/features/templating/template_srv.mock';
|
import { TemplateSrvMock } from 'app/features/templating/template_srv.mock';
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ export const createMockDatasource = (overrides?: Partial<Datasource>) => {
|
|||||||
templateSrv,
|
templateSrv,
|
||||||
getSLOServices: jest.fn().mockResolvedValue([]),
|
getSLOServices: jest.fn().mockResolvedValue([]),
|
||||||
migrateQuery: jest.fn().mockImplementation((query) => query),
|
migrateQuery: jest.fn().mockImplementation((query) => query),
|
||||||
|
timeSrv: getTimeSrv(),
|
||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,203 +0,0 @@
|
|||||||
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 { createMockTimeSeriesList } from '../__mocks__/cloudMonitoringQuery';
|
|
||||||
|
|
||||||
import { Metrics } from './Metrics';
|
|
||||||
|
|
||||||
describe('Metrics', () => {
|
|
||||||
it('renders metrics fields', async () => {
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const query = createMockTimeSeriesList();
|
|
||||||
const datasource = createMockDatasource();
|
|
||||||
|
|
||||||
render(
|
|
||||||
<Metrics
|
|
||||||
refId="refId"
|
|
||||||
metricType=""
|
|
||||||
projectName="projectName"
|
|
||||||
templateVariableOptions={[]}
|
|
||||||
datasource={datasource}
|
|
||||||
onChange={onChange}
|
|
||||||
onProjectChange={jest.fn()}
|
|
||||||
query={query}
|
|
||||||
>
|
|
||||||
{() => <div />}
|
|
||||||
</Metrics>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(await screen.findByLabelText('Service')).toBeInTheDocument();
|
|
||||||
expect(await screen.findByLabelText('Metric name')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can select a service', async () => {
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const query = createMockTimeSeriesList();
|
|
||||||
const datasource = createMockDatasource({
|
|
||||||
getMetricTypes: jest.fn().mockResolvedValue([createMockMetricDescriptor()]),
|
|
||||||
});
|
|
||||||
|
|
||||||
render(
|
|
||||||
<Metrics
|
|
||||||
refId="refId"
|
|
||||||
metricType=""
|
|
||||||
projectName="projectName"
|
|
||||||
templateVariableOptions={[]}
|
|
||||||
datasource={datasource}
|
|
||||||
onChange={onChange}
|
|
||||||
onProjectChange={jest.fn()}
|
|
||||||
query={query}
|
|
||||||
>
|
|
||||||
{() => <div />}
|
|
||||||
</Metrics>
|
|
||||||
);
|
|
||||||
|
|
||||||
const service = await screen.findByLabelText('Service');
|
|
||||||
await openMenu(service);
|
|
||||||
await select(service, 'Srv', { container: document.body });
|
|
||||||
expect(onChange).toBeCalledWith(expect.objectContaining({ service: 'service' }));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can select a metric name', async () => {
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const query = createMockTimeSeriesList();
|
|
||||||
const datasource = createMockDatasource({
|
|
||||||
getMetricTypes: jest.fn().mockResolvedValue([createMockMetricDescriptor()]),
|
|
||||||
});
|
|
||||||
|
|
||||||
render(
|
|
||||||
<Metrics
|
|
||||||
refId="refId"
|
|
||||||
metricType="type"
|
|
||||||
projectName="projectName"
|
|
||||||
templateVariableOptions={[]}
|
|
||||||
datasource={datasource}
|
|
||||||
onChange={onChange}
|
|
||||||
onProjectChange={jest.fn()}
|
|
||||||
query={query}
|
|
||||||
>
|
|
||||||
{() => <div />}
|
|
||||||
</Metrics>
|
|
||||||
);
|
|
||||||
|
|
||||||
const metricName = await screen.findByLabelText('Metric name');
|
|
||||||
await openMenu(metricName);
|
|
||||||
await select(metricName, 'metricName', { container: document.body });
|
|
||||||
expect(onChange).toBeCalledWith(expect.objectContaining({ type: 'type' }));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render available metric options according to the selected service', async () => {
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const query = createMockTimeSeriesList();
|
|
||||||
const datasource = createMockDatasource({
|
|
||||||
getMetricTypes: jest.fn().mockResolvedValue([
|
|
||||||
createMockMetricDescriptor({
|
|
||||||
service: 'service_a',
|
|
||||||
serviceShortName: 'srv_a',
|
|
||||||
type: 'metric1',
|
|
||||||
description: 'description_metric1',
|
|
||||||
displayName: 'displayName_metric1',
|
|
||||||
}),
|
|
||||||
createMockMetricDescriptor({
|
|
||||||
service: 'service_b',
|
|
||||||
serviceShortName: 'srv_b',
|
|
||||||
type: 'metric2',
|
|
||||||
description: 'description_metric2',
|
|
||||||
displayName: 'displayName_metric2',
|
|
||||||
}),
|
|
||||||
createMockMetricDescriptor({
|
|
||||||
service: 'service_b',
|
|
||||||
serviceShortName: 'srv_b',
|
|
||||||
type: 'metric3',
|
|
||||||
description: 'description_metric3',
|
|
||||||
displayName: 'displayName_metric3',
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
});
|
|
||||||
|
|
||||||
render(
|
|
||||||
<Metrics
|
|
||||||
refId="refId"
|
|
||||||
metricType="metric1"
|
|
||||||
projectName="projectName"
|
|
||||||
templateVariableOptions={[]}
|
|
||||||
datasource={datasource}
|
|
||||||
onChange={onChange}
|
|
||||||
onProjectChange={jest.fn()}
|
|
||||||
query={query}
|
|
||||||
>
|
|
||||||
{() => <div />}
|
|
||||||
</Metrics>
|
|
||||||
);
|
|
||||||
|
|
||||||
const metricName = await screen.findByLabelText('Metric name');
|
|
||||||
await openMenu(metricName);
|
|
||||||
|
|
||||||
const metricNameOptions = screen.getByLabelText('Select options menu');
|
|
||||||
expect(within(metricNameOptions).getByText('description_metric1')).toBeInTheDocument();
|
|
||||||
expect(within(metricNameOptions).getByText('displayName_metric1')).toBeInTheDocument();
|
|
||||||
expect(within(metricNameOptions).queryByText('displayName_metric2')).not.toBeInTheDocument();
|
|
||||||
expect(within(metricNameOptions).queryByText('description_metric2')).not.toBeInTheDocument();
|
|
||||||
expect(within(metricNameOptions).queryByText('displayName_metric3')).not.toBeInTheDocument();
|
|
||||||
expect(within(metricNameOptions).queryByText('description_metric3')).not.toBeInTheDocument();
|
|
||||||
|
|
||||||
await select(screen.getByLabelText('Service'), 'Srv B', { container: document.body });
|
|
||||||
expect(within(metricNameOptions).queryByText('displayName_metric1')).not.toBeInTheDocument();
|
|
||||||
expect(within(metricNameOptions).queryByText('description_metric1')).not.toBeInTheDocument();
|
|
||||||
expect(within(metricNameOptions).getByText('displayName_metric2')).toBeInTheDocument();
|
|
||||||
expect(within(metricNameOptions).getByText('description_metric2')).toBeInTheDocument();
|
|
||||||
expect(within(metricNameOptions).getByText('displayName_metric3')).toBeInTheDocument();
|
|
||||||
expect(within(metricNameOptions).getByText('description_metric3')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have a distinct list of services', async () => {
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const datasource = createMockDatasource({
|
|
||||||
getMetricTypes: jest.fn().mockResolvedValue([
|
|
||||||
createMockMetricDescriptor({
|
|
||||||
service: 'service_a',
|
|
||||||
serviceShortName: 'srv_a',
|
|
||||||
type: 'metric1',
|
|
||||||
description: 'description_metric1',
|
|
||||||
displayName: 'displayName_metric1',
|
|
||||||
}),
|
|
||||||
createMockMetricDescriptor({
|
|
||||||
service: 'service_b',
|
|
||||||
serviceShortName: 'srv_b',
|
|
||||||
type: 'metric2',
|
|
||||||
description: 'description_metric2',
|
|
||||||
displayName: 'displayName_metric2',
|
|
||||||
}),
|
|
||||||
createMockMetricDescriptor({
|
|
||||||
service: 'service_b',
|
|
||||||
serviceShortName: 'srv_b',
|
|
||||||
type: 'metric3',
|
|
||||||
description: 'description_metric3',
|
|
||||||
displayName: 'displayName_metric3',
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
});
|
|
||||||
const query = createMockTimeSeriesList();
|
|
||||||
|
|
||||||
render(
|
|
||||||
<Metrics
|
|
||||||
refId="refId"
|
|
||||||
metricType="metric1"
|
|
||||||
projectName="projectName"
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,197 +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 } from '@grafana/experimental';
|
|
||||||
import { getSelectStyles, Select, useStyles2, useTheme2 } from '@grafana/ui';
|
|
||||||
|
|
||||||
import CloudMonitoringDatasource from '../datasource';
|
|
||||||
import { MetricDescriptor, TimeSeriesList } 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: TimeSeriesList;
|
|
||||||
children: (metricDescriptor?: MetricDescriptor) => JSX.Element;
|
|
||||||
onProjectChange: (query: TimeSeriesList) => 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 loadMetricDescriptors = async () => {
|
|
||||||
if (projectName) {
|
|
||||||
const metricDescriptors = await datasource.getMetricTypes(projectName);
|
|
||||||
const services = getServicesList(metricDescriptors);
|
|
||||||
setMetricDescriptors(metricDescriptors);
|
|
||||||
setServices(services);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
loadMetricDescriptors();
|
|
||||||
}, [datasource, projectName, customStyle, selectStyles.optionDescription]);
|
|
||||||
|
|
||||||
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 metrics = getMetricsList(metricDescriptors);
|
|
||||||
const service = metrics.length > 0 ? metrics[0].service : '';
|
|
||||||
const metricDescriptor = getSelectedMetricDescriptor(metricDescriptors, metricType);
|
|
||||||
setMetricDescriptor(metricDescriptor);
|
|
||||||
setMetrics(metrics);
|
|
||||||
setService(service);
|
|
||||||
}, [metricDescriptors, getSelectedMetricDescriptor, metricType, 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};
|
|
||||||
`;
|
|
@ -0,0 +1,286 @@
|
|||||||
|
import { act, render, screen, waitFor, within } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { openMenu, select } from 'react-select-event';
|
||||||
|
|
||||||
|
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
|
||||||
|
import { createMockDatasource } from '../__mocks__/cloudMonitoringDatasource';
|
||||||
|
import { createMockMetricDescriptor } from '../__mocks__/cloudMonitoringMetricDescriptor';
|
||||||
|
import { createMockTimeSeriesList } from '../__mocks__/cloudMonitoringQuery';
|
||||||
|
import { MetricKind, PreprocessorType } from '../types';
|
||||||
|
|
||||||
|
import { defaultTimeSeriesList } from './MetricQueryEditor';
|
||||||
|
import { VisualMetricQueryEditor } from './VisualMetricQueryEditor';
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
refId: 'refId',
|
||||||
|
customMetaData: {},
|
||||||
|
variableOptionGroup: { options: [] },
|
||||||
|
aliasBy: '',
|
||||||
|
onChangeAliasBy: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('VisualMetricQueryEditor', () => {
|
||||||
|
it('renders metrics fields', async () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const query = createMockTimeSeriesList();
|
||||||
|
const datasource = createMockDatasource();
|
||||||
|
|
||||||
|
render(<VisualMetricQueryEditor {...defaultProps} onChange={onChange} datasource={datasource} query={query} />);
|
||||||
|
|
||||||
|
expect(await screen.findByLabelText('Service')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByLabelText('Metric name')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can select a service', async () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const query = createMockTimeSeriesList();
|
||||||
|
const mockMetricDescriptor = createMockMetricDescriptor();
|
||||||
|
const datasource = createMockDatasource({
|
||||||
|
getMetricTypes: jest.fn().mockResolvedValue([mockMetricDescriptor]),
|
||||||
|
getLabels: jest.fn().mockResolvedValue([]),
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<VisualMetricQueryEditor {...defaultProps} onChange={onChange} datasource={datasource} query={query} />);
|
||||||
|
|
||||||
|
const service = await screen.findByLabelText('Service');
|
||||||
|
await openMenu(service);
|
||||||
|
await act(async () => {
|
||||||
|
await select(service, 'Srv', { container: document.body });
|
||||||
|
expect(onChange).toBeCalledWith(
|
||||||
|
expect.objectContaining({ filters: ['metric.type', '=', mockMetricDescriptor.type] })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can select a metric name', async () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const query = createMockTimeSeriesList();
|
||||||
|
const mockMetricDescriptor = createMockMetricDescriptor({ displayName: 'metricName_test', type: 'test_type' });
|
||||||
|
const datasource = createMockDatasource({
|
||||||
|
getMetricTypes: jest.fn().mockResolvedValue([createMockMetricDescriptor(), mockMetricDescriptor]),
|
||||||
|
getLabels: jest.fn().mockResolvedValue([]),
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<VisualMetricQueryEditor {...defaultProps} onChange={onChange} datasource={datasource} query={query} />);
|
||||||
|
|
||||||
|
const service = await screen.findByLabelText('Service');
|
||||||
|
await openMenu(service);
|
||||||
|
await act(async () => {
|
||||||
|
await select(service, 'Srv', { container: document.body });
|
||||||
|
});
|
||||||
|
const metricName = await screen.findByLabelText('Metric name');
|
||||||
|
await openMenu(metricName);
|
||||||
|
await waitFor(() => expect(document.body).toHaveTextContent('metricName_test'));
|
||||||
|
await act(async () => {
|
||||||
|
await select(metricName, 'metricName_test', { container: document.body });
|
||||||
|
expect(onChange).toBeCalledWith(
|
||||||
|
expect.objectContaining({ filters: ['metric.type', '=', mockMetricDescriptor.type] })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render available metric options according to the selected service', async () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const query = createMockTimeSeriesList();
|
||||||
|
const datasource = createMockDatasource({
|
||||||
|
getMetricTypes: jest.fn().mockResolvedValue([
|
||||||
|
createMockMetricDescriptor({
|
||||||
|
service: 'service_a',
|
||||||
|
serviceShortName: 'srv_a',
|
||||||
|
type: 'metric1',
|
||||||
|
description: 'description_metric1',
|
||||||
|
displayName: 'displayName_metric1',
|
||||||
|
}),
|
||||||
|
createMockMetricDescriptor({
|
||||||
|
service: 'service_b',
|
||||||
|
serviceShortName: 'srv_b',
|
||||||
|
type: 'metric2',
|
||||||
|
description: 'description_metric2',
|
||||||
|
displayName: 'displayName_metric2',
|
||||||
|
}),
|
||||||
|
createMockMetricDescriptor({
|
||||||
|
service: 'service_b',
|
||||||
|
serviceShortName: 'srv_b',
|
||||||
|
type: 'metric3',
|
||||||
|
description: 'description_metric3',
|
||||||
|
displayName: 'displayName_metric3',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
getLabels: jest.fn().mockResolvedValue([]),
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<VisualMetricQueryEditor {...defaultProps} onChange={onChange} datasource={datasource} query={query} />);
|
||||||
|
|
||||||
|
const service = await screen.findByLabelText('Service');
|
||||||
|
await openMenu(service);
|
||||||
|
await act(async () => {
|
||||||
|
await select(service, 'Srv A', { container: document.body });
|
||||||
|
});
|
||||||
|
const metricName = await screen.findByLabelText('Metric name');
|
||||||
|
await openMenu(metricName);
|
||||||
|
|
||||||
|
const metricNameOptions = screen.getByLabelText('Select options menu');
|
||||||
|
expect(within(metricNameOptions).getByText('description_metric1')).toBeInTheDocument();
|
||||||
|
expect(within(metricNameOptions).getByText('displayName_metric1')).toBeInTheDocument();
|
||||||
|
expect(within(metricNameOptions).queryByText('displayName_metric2')).not.toBeInTheDocument();
|
||||||
|
expect(within(metricNameOptions).queryByText('description_metric2')).not.toBeInTheDocument();
|
||||||
|
expect(within(metricNameOptions).queryByText('displayName_metric3')).not.toBeInTheDocument();
|
||||||
|
expect(within(metricNameOptions).queryByText('description_metric3')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
await openMenu(service);
|
||||||
|
await act(async () => {
|
||||||
|
await select(service, 'Srv B', { container: document.body });
|
||||||
|
});
|
||||||
|
expect(within(metricNameOptions).queryByText('displayName_metric1')).not.toBeInTheDocument();
|
||||||
|
expect(within(metricNameOptions).queryByText('description_metric1')).not.toBeInTheDocument();
|
||||||
|
expect(within(metricNameOptions).getByText('displayName_metric2')).toBeInTheDocument();
|
||||||
|
expect(within(metricNameOptions).getByText('description_metric2')).toBeInTheDocument();
|
||||||
|
expect(within(metricNameOptions).getByText('displayName_metric3')).toBeInTheDocument();
|
||||||
|
expect(within(metricNameOptions).getByText('description_metric3')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a distinct list of services', async () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const datasource = createMockDatasource({
|
||||||
|
getMetricTypes: jest.fn().mockResolvedValue([
|
||||||
|
createMockMetricDescriptor({
|
||||||
|
service: 'service_a',
|
||||||
|
serviceShortName: 'srv_a',
|
||||||
|
type: 'metric1',
|
||||||
|
description: 'description_metric1',
|
||||||
|
displayName: 'displayName_metric1',
|
||||||
|
}),
|
||||||
|
createMockMetricDescriptor({
|
||||||
|
service: 'service_b',
|
||||||
|
serviceShortName: 'srv_b',
|
||||||
|
type: 'metric2',
|
||||||
|
description: 'description_metric2',
|
||||||
|
displayName: 'displayName_metric2',
|
||||||
|
}),
|
||||||
|
createMockMetricDescriptor({
|
||||||
|
service: 'service_b',
|
||||||
|
serviceShortName: 'srv_b',
|
||||||
|
type: 'metric3',
|
||||||
|
description: 'description_metric3',
|
||||||
|
displayName: 'displayName_metric3',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
const query = createMockTimeSeriesList();
|
||||||
|
|
||||||
|
render(<VisualMetricQueryEditor {...defaultProps} onChange={onChange} datasource={datasource} query={query} />);
|
||||||
|
const service = await screen.findByLabelText('Service');
|
||||||
|
await openMenu(service);
|
||||||
|
expect(screen.getAllByLabelText('Select option').length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resets query to default when service changes', async () => {
|
||||||
|
const query = createMockTimeSeriesList({ filters: ['metric.test_label', '=', 'test', 'AND'] });
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const datasource = createMockDatasource({
|
||||||
|
getMetricTypes: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue([
|
||||||
|
createMockMetricDescriptor(),
|
||||||
|
createMockMetricDescriptor({ type: 'type2', service: 'service2', serviceShortName: 'srv2' }),
|
||||||
|
]),
|
||||||
|
getLabels: jest.fn().mockResolvedValue([]),
|
||||||
|
});
|
||||||
|
const defaultQuery = { ...query, ...defaultTimeSeriesList(datasource), filters: ['metric.type', '=', 'type2'] };
|
||||||
|
|
||||||
|
render(<VisualMetricQueryEditor {...defaultProps} onChange={onChange} datasource={datasource} query={query} />);
|
||||||
|
|
||||||
|
expect(screen.getByText('metric.test_label')).toBeInTheDocument();
|
||||||
|
const service = await screen.findByLabelText('Service');
|
||||||
|
openMenu(service);
|
||||||
|
await select(service, 'Srv 2', { container: document.body });
|
||||||
|
expect(onChange).toBeCalledWith(expect.objectContaining({ filters: ['metric.type', '=', 'type2'] }));
|
||||||
|
expect(query).toEqual(defaultQuery);
|
||||||
|
expect(screen.queryByText('metric.test_label')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resets query to defaults (except filters) when metric changes', async () => {
|
||||||
|
const groupBys = ['metric.test_groupby'];
|
||||||
|
const query = createMockTimeSeriesList({
|
||||||
|
filters: ['metric.test_label', '=', 'test', 'AND', 'metric.type', '=', 'type'],
|
||||||
|
groupBys,
|
||||||
|
preprocessor: PreprocessorType.Delta,
|
||||||
|
});
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const datasource = createMockDatasource({
|
||||||
|
getMetricTypes: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue([
|
||||||
|
createMockMetricDescriptor(),
|
||||||
|
createMockMetricDescriptor({ type: 'type2', displayName: 'metricName2', metricKind: MetricKind.GAUGE }),
|
||||||
|
]),
|
||||||
|
getLabels: jest.fn().mockResolvedValue({ 'metric.test_groupby': '' }),
|
||||||
|
templateSrv: new TemplateSrv(),
|
||||||
|
});
|
||||||
|
const defaultQuery = { ...query, ...defaultTimeSeriesList(datasource), filters: query.filters };
|
||||||
|
|
||||||
|
render(<VisualMetricQueryEditor {...defaultProps} onChange={onChange} datasource={datasource} query={query} />);
|
||||||
|
expect(document.body).toHaveTextContent('metric.test_label');
|
||||||
|
expect(await screen.findByText('Delta')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByText('metric.test_groupby')).toBeInTheDocument();
|
||||||
|
const metric = await screen.findByLabelText('Metric name');
|
||||||
|
openMenu(metric);
|
||||||
|
await select(metric, 'metricName2', { container: document.body });
|
||||||
|
expect(onChange).toBeCalledWith(
|
||||||
|
expect.objectContaining({ filters: ['metric.test_label', '=', 'test', 'AND', 'metric.type', '=', 'type2'] })
|
||||||
|
);
|
||||||
|
expect(query).toEqual(defaultQuery);
|
||||||
|
expect(document.body).toHaveTextContent('metric.test_label');
|
||||||
|
expect(await screen.queryByText('Delta')).not.toBeInTheDocument();
|
||||||
|
expect(await screen.queryByText('metric.test_groupby')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates labels on time range change', async () => {
|
||||||
|
const timeSrv = getTimeSrv();
|
||||||
|
const query = createMockTimeSeriesList();
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const datasource = createMockDatasource({
|
||||||
|
getMetricTypes: jest.fn().mockResolvedValue([createMockMetricDescriptor()]),
|
||||||
|
getLabels: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(
|
||||||
|
timeSrv.time.from === 'now-6h' ? { 'metric.test_groupby': '' } : { 'metric.test_groupby_1': '' }
|
||||||
|
),
|
||||||
|
templateSrv: new TemplateSrv(),
|
||||||
|
timeSrv,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { rerender } = render(
|
||||||
|
<VisualMetricQueryEditor {...defaultProps} onChange={onChange} datasource={datasource} query={query} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const service = await screen.findByLabelText('Service');
|
||||||
|
await openMenu(service);
|
||||||
|
await act(async () => {
|
||||||
|
await select(service, 'Srv', { container: document.body });
|
||||||
|
});
|
||||||
|
const metricName = await screen.findByLabelText('Metric name');
|
||||||
|
await openMenu(metricName);
|
||||||
|
await waitFor(() => expect(document.body).toHaveTextContent('metricName'));
|
||||||
|
await act(async () => {
|
||||||
|
await select(metricName, 'metricName', { container: document.body });
|
||||||
|
});
|
||||||
|
const groupBy = await screen.findByLabelText('Group by');
|
||||||
|
await openMenu(groupBy);
|
||||||
|
await waitFor(() => expect(document.body).toHaveTextContent('metric.test_groupby'));
|
||||||
|
await act(async () => {
|
||||||
|
timeSrv.setTime({ from: 'now-12h', to: 'now' });
|
||||||
|
const datasourceUpdated = createMockDatasource({
|
||||||
|
timeSrv,
|
||||||
|
getLabels: jest.fn().mockResolvedValue({ 'metric.test_groupby_1': '' }),
|
||||||
|
});
|
||||||
|
rerender(
|
||||||
|
<VisualMetricQueryEditor {...defaultProps} onChange={onChange} datasource={datasourceUpdated} query={query} />
|
||||||
|
);
|
||||||
|
await openMenu(groupBy);
|
||||||
|
await waitFor(() => expect(document.body).toHaveTextContent('metric.test_groupby_1'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,7 +1,10 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { startCase, uniqBy } from 'lodash';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { GrafanaTheme2, SelectableValue, TimeRange } from '@grafana/data';
|
||||||
import { EditorRow } from '@grafana/experimental';
|
import { EditorField, EditorFieldGroup, EditorRow } from '@grafana/experimental';
|
||||||
|
import { getSelectStyles, Select, useStyles2, useTheme2 } from '@grafana/ui';
|
||||||
|
|
||||||
import CloudMonitoringDatasource from '../datasource';
|
import CloudMonitoringDatasource from '../datasource';
|
||||||
import { getAlignmentPickerData, getMetricType, setMetricType } from '../functions';
|
import { getAlignmentPickerData, getMetricType, setMetricType } from '../functions';
|
||||||
@ -11,106 +14,263 @@ import { AliasBy } from './AliasBy';
|
|||||||
import { Alignment } from './Alignment';
|
import { Alignment } from './Alignment';
|
||||||
import { GroupBy } from './GroupBy';
|
import { GroupBy } from './GroupBy';
|
||||||
import { LabelFilter } from './LabelFilter';
|
import { LabelFilter } from './LabelFilter';
|
||||||
import { Metrics } from './Metrics';
|
import { defaultTimeSeriesList } from './MetricQueryEditor';
|
||||||
import { Preprocessor } from './Preprocessor';
|
import { Preprocessor } from './Preprocessor';
|
||||||
|
import { Project } from './Project';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
refId: string;
|
refId: string;
|
||||||
customMetaData: CustomMetaData;
|
customMetaData: CustomMetaData;
|
||||||
variableOptionGroup: SelectableValue<string>;
|
|
||||||
onChange: (query: TimeSeriesList) => void;
|
onChange: (query: TimeSeriesList) => void;
|
||||||
query: TimeSeriesList;
|
|
||||||
datasource: CloudMonitoringDatasource;
|
datasource: CloudMonitoringDatasource;
|
||||||
|
query: TimeSeriesList;
|
||||||
|
variableOptionGroup: SelectableValue<string>;
|
||||||
aliasBy?: string;
|
aliasBy?: string;
|
||||||
onChangeAliasBy: (aliasBy: string) => void;
|
onChangeAliasBy: (aliasBy: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Editor({
|
export function Editor({
|
||||||
refId,
|
refId,
|
||||||
query,
|
|
||||||
datasource,
|
|
||||||
onChange,
|
onChange,
|
||||||
customMetaData,
|
datasource,
|
||||||
|
query,
|
||||||
variableOptionGroup,
|
variableOptionGroup,
|
||||||
|
customMetaData,
|
||||||
aliasBy,
|
aliasBy,
|
||||||
onChangeAliasBy,
|
onChangeAliasBy,
|
||||||
}: React.PropsWithChildren<Props>) {
|
}: React.PropsWithChildren<Props>) {
|
||||||
const [labels, setLabels] = useState<{ [k: string]: any }>({});
|
const [labels, setLabels] = useState<{ [k: string]: any }>({});
|
||||||
|
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 [timeRange, setTimeRange] = useState<TimeRange>({ ...datasource.timeSrv.timeRange() });
|
||||||
|
|
||||||
|
const useTime = (time: TimeRange) => {
|
||||||
|
if (timeRange !== null && (timeRange.raw.from !== time.raw.from || timeRange.raw.to !== time.raw.to)) {
|
||||||
|
setTimeRange({ ...time });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useTime(datasource.timeSrv.timeRange());
|
||||||
|
|
||||||
|
const theme = useTheme2();
|
||||||
|
const selectStyles = getSelectStyles(theme);
|
||||||
|
|
||||||
|
const customStyle = useStyles2(getStyles);
|
||||||
|
|
||||||
const { projectName, groupBys, crossSeriesReducer } = query;
|
const { projectName, groupBys, crossSeriesReducer } = query;
|
||||||
const metricType = getMetricType(query);
|
const metricType = getMetricType(query);
|
||||||
|
const { templateSrv } = datasource;
|
||||||
|
|
||||||
|
const getSelectedMetricDescriptor = useCallback(
|
||||||
|
(metricDescriptors: MetricDescriptor[], metricType: string) => {
|
||||||
|
return metricDescriptors.find((md) => md.type === templateSrv.replace(metricType))!;
|
||||||
|
},
|
||||||
|
[templateSrv]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (projectName && metricType) {
|
if (projectName && metricType) {
|
||||||
datasource.getLabels(metricType, refId, projectName).then((labels) => setLabels(labels));
|
datasource
|
||||||
|
.getLabels(metricType, refId, projectName, { groupBys, crossSeriesReducer }, timeRange)
|
||||||
|
.then((labels) => setLabels(labels));
|
||||||
}
|
}
|
||||||
}, [datasource, groupBys, metricType, projectName, refId, crossSeriesReducer]);
|
}, [datasource, groupBys, metricType, projectName, refId, crossSeriesReducer, timeRange]);
|
||||||
|
|
||||||
const onMetricTypeChange = useCallback(
|
useEffect(() => {
|
||||||
({ valueType, metricKind, type }: MetricDescriptor) => {
|
const loadMetricDescriptors = async () => {
|
||||||
const preprocessor =
|
if (projectName) {
|
||||||
metricKind === MetricKind.GAUGE || valueType === ValueTypes.DISTRIBUTION
|
const metricDescriptors = await datasource.getMetricTypes(projectName);
|
||||||
? PreprocessorType.None
|
const services = getServicesList(metricDescriptors);
|
||||||
: PreprocessorType.Rate;
|
setMetricDescriptors(metricDescriptors);
|
||||||
const { perSeriesAligner } = getAlignmentPickerData(valueType, metricKind, query.perSeriesAligner, preprocessor);
|
setServices(services);
|
||||||
onChange({
|
}
|
||||||
...setMetricType(
|
};
|
||||||
{
|
loadMetricDescriptors();
|
||||||
...query,
|
}, [datasource, projectName, customStyle, selectStyles.optionDescription]);
|
||||||
perSeriesAligner,
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
type
|
}));
|
||||||
),
|
return metricsByService;
|
||||||
preprocessor,
|
};
|
||||||
});
|
|
||||||
},
|
const metrics = getMetricsList(metricDescriptors);
|
||||||
[onChange, query]
|
const service = metrics.length > 0 ? metrics[0].service : '';
|
||||||
);
|
const metricDescriptor = getSelectedMetricDescriptor(metricDescriptors, metricType);
|
||||||
|
setMetricDescriptor(metricDescriptor);
|
||||||
|
setMetrics(metrics);
|
||||||
|
setService(service);
|
||||||
|
}, [metricDescriptors, getSelectedMetricDescriptor, metricType, customStyle, selectStyles.optionDescription]);
|
||||||
|
|
||||||
|
const onServiceChange = ({ value: service }: SelectableValue<string>) => {
|
||||||
|
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,
|
||||||
|
}));
|
||||||
|
// On service change reset all query values except the project name
|
||||||
|
query.filters = [];
|
||||||
|
|
||||||
|
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 getServicesList = (metricDescriptors: MetricDescriptor[]) => {
|
||||||
|
const services = metricDescriptors.map((m) => ({
|
||||||
|
value: m.service,
|
||||||
|
label: startCase(m.serviceShortName),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return services.length > 0 ? uniqBy(services, (s) => s.value) : [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMetricTypeChange = ({ value }: SelectableValue<string>) => {
|
||||||
|
const metricDescriptor = getSelectedMetricDescriptor(metricDescriptors, value!);
|
||||||
|
setMetricDescriptor(metricDescriptor);
|
||||||
|
const { metricKind, valueType } = metricDescriptor;
|
||||||
|
const preprocessor =
|
||||||
|
metricKind === MetricKind.GAUGE || valueType === ValueTypes.DISTRIBUTION
|
||||||
|
? PreprocessorType.None
|
||||||
|
: PreprocessorType.Rate;
|
||||||
|
const { perSeriesAligner } = getAlignmentPickerData(valueType, metricKind, query.perSeriesAligner, preprocessor);
|
||||||
|
|
||||||
|
// On metric name change reset query to defaults except project name and filters
|
||||||
|
Object.assign(query, {
|
||||||
|
...defaultTimeSeriesList(datasource),
|
||||||
|
projectName: query.projectName,
|
||||||
|
filters: query.filters,
|
||||||
|
});
|
||||||
|
onChange({
|
||||||
|
...setMetricType(
|
||||||
|
{
|
||||||
|
...query,
|
||||||
|
perSeriesAligner,
|
||||||
|
},
|
||||||
|
value!
|
||||||
|
),
|
||||||
|
preprocessor,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Metrics
|
<>
|
||||||
refId={refId}
|
<EditorRow>
|
||||||
projectName={query.projectName}
|
<EditorFieldGroup>
|
||||||
metricType={metricType}
|
<Project
|
||||||
templateVariableOptions={variableOptionGroup.options}
|
refId={refId}
|
||||||
datasource={datasource}
|
templateVariableOptions={variableOptionGroup.options}
|
||||||
onChange={onMetricTypeChange}
|
projectName={projectName}
|
||||||
onProjectChange={onChange}
|
datasource={datasource}
|
||||||
query={query}
|
onChange={(projectName) => {
|
||||||
>
|
onChange({ ...query, projectName });
|
||||||
{(metric) => (
|
}}
|
||||||
<>
|
|
||||||
<LabelFilter
|
|
||||||
labels={labels}
|
|
||||||
filters={query.filters!}
|
|
||||||
onChange={(filters: string[]) => onChange({ ...query, filters })}
|
|
||||||
variableOptionGroup={variableOptionGroup}
|
|
||||||
/>
|
/>
|
||||||
<EditorRow>
|
|
||||||
<Preprocessor metricDescriptor={metric} query={query} onChange={onChange} />
|
<EditorField label="Service" width="auto">
|
||||||
<GroupBy
|
<Select
|
||||||
refId={refId}
|
width="auto"
|
||||||
labels={Object.keys(labels)}
|
onChange={onServiceChange}
|
||||||
query={query}
|
value={[...services, ...variableOptionGroup.options].find((s) => s.value === service)}
|
||||||
onChange={onChange}
|
options={[
|
||||||
variableOptionGroup={variableOptionGroup}
|
{
|
||||||
metricDescriptor={metric}
|
label: 'Template Variables',
|
||||||
|
options: variableOptionGroup.options,
|
||||||
|
},
|
||||||
|
...services,
|
||||||
|
]}
|
||||||
|
placeholder="Select Services"
|
||||||
|
inputId={`${refId}-service`}
|
||||||
/>
|
/>
|
||||||
<Alignment
|
</EditorField>
|
||||||
refId={refId}
|
<EditorField label="Metric name" width="auto">
|
||||||
datasource={datasource}
|
<Select
|
||||||
templateVariableOptions={variableOptionGroup.options}
|
width="auto"
|
||||||
query={query}
|
onChange={onMetricTypeChange}
|
||||||
customMetaData={customMetaData}
|
value={[...metrics, ...variableOptionGroup.options].find((s) => s.value === metricType)}
|
||||||
onChange={onChange}
|
options={[
|
||||||
metricDescriptor={metric}
|
{
|
||||||
preprocessor={query.preprocessor}
|
label: 'Template Variables',
|
||||||
|
options: variableOptionGroup.options,
|
||||||
|
},
|
||||||
|
...metrics,
|
||||||
|
]}
|
||||||
|
placeholder="Select Metric"
|
||||||
|
inputId={`${refId}-select-metric`}
|
||||||
/>
|
/>
|
||||||
<AliasBy refId={refId} value={aliasBy} onChange={onChangeAliasBy} />
|
</EditorField>
|
||||||
</EditorRow>
|
</EditorFieldGroup>
|
||||||
</>
|
</EditorRow>
|
||||||
)}
|
|
||||||
</Metrics>
|
<>
|
||||||
|
<LabelFilter
|
||||||
|
labels={labels}
|
||||||
|
filters={query.filters!}
|
||||||
|
onChange={(filters: string[]) => onChange({ ...query, filters })}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
/>
|
||||||
|
<EditorRow>
|
||||||
|
<Preprocessor metricDescriptor={metricDescriptor} query={query} onChange={onChange} />
|
||||||
|
<GroupBy
|
||||||
|
refId={refId}
|
||||||
|
labels={Object.keys(labels)}
|
||||||
|
query={query}
|
||||||
|
onChange={onChange}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
metricDescriptor={metricDescriptor}
|
||||||
|
/>
|
||||||
|
<Alignment
|
||||||
|
refId={refId}
|
||||||
|
datasource={datasource}
|
||||||
|
templateVariableOptions={variableOptionGroup.options}
|
||||||
|
query={query}
|
||||||
|
customMetaData={customMetaData}
|
||||||
|
onChange={onChange}
|
||||||
|
metricDescriptor={metricDescriptor}
|
||||||
|
preprocessor={query.preprocessor}
|
||||||
|
/>
|
||||||
|
<AliasBy refId={refId} value={aliasBy} onChange={onChangeAliasBy} />
|
||||||
|
</EditorRow>
|
||||||
|
</>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => css`
|
||||||
|
label: grafana-select-option-description;
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: italic;
|
||||||
|
color: ${theme.colors.text.secondary};
|
||||||
|
`;
|
||||||
|
|
||||||
export const VisualMetricQueryEditor = React.memo(Editor);
|
export const VisualMetricQueryEditor = React.memo(Editor);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
export { Project } from './Project';
|
export { Project } from './Project';
|
||||||
export { Metrics } from './Metrics';
|
|
||||||
export { GroupBy } from './GroupBy';
|
export { GroupBy } from './GroupBy';
|
||||||
export { Alignment } from './Alignment';
|
export { Alignment } from './Alignment';
|
||||||
export { LabelFilter } from './LabelFilter';
|
export { LabelFilter } from './LabelFilter';
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
DataSourceInstanceSettings,
|
DataSourceInstanceSettings,
|
||||||
ScopedVars,
|
ScopedVars,
|
||||||
SelectableValue,
|
SelectableValue,
|
||||||
|
TimeRange,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { DataSourceWithBackend, getBackendSrv, toDataQueryResponse, BackendSrv } from '@grafana/runtime';
|
import { DataSourceWithBackend, getBackendSrv, toDataQueryResponse, BackendSrv } from '@grafana/runtime';
|
||||||
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
@ -39,7 +40,7 @@ export default class CloudMonitoringDatasource extends DataSourceWithBackend<
|
|||||||
constructor(
|
constructor(
|
||||||
private instanceSettings: DataSourceInstanceSettings<CloudMonitoringOptions>,
|
private instanceSettings: DataSourceInstanceSettings<CloudMonitoringOptions>,
|
||||||
public templateSrv: TemplateSrv = getTemplateSrv(),
|
public templateSrv: TemplateSrv = getTemplateSrv(),
|
||||||
private readonly timeSrv: TimeSrv = getTimeSrv()
|
readonly timeSrv: TimeSrv = getTimeSrv()
|
||||||
) {
|
) {
|
||||||
super(instanceSettings);
|
super(instanceSettings);
|
||||||
this.authenticationType = instanceSettings.jsonData.authenticationType || 'jwt';
|
this.authenticationType = instanceSettings.jsonData.authenticationType || 'jwt';
|
||||||
@ -89,7 +90,13 @@ export default class CloudMonitoringDatasource extends DataSourceWithBackend<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLabels(metricType: string, refId: string, projectName: string, aggregation?: Aggregation) {
|
async getLabels(
|
||||||
|
metricType: string,
|
||||||
|
refId: string,
|
||||||
|
projectName: string,
|
||||||
|
aggregation?: Aggregation,
|
||||||
|
timeRange?: TimeRange
|
||||||
|
) {
|
||||||
const options = {
|
const options = {
|
||||||
targets: [
|
targets: [
|
||||||
{
|
{
|
||||||
@ -107,7 +114,7 @@ export default class CloudMonitoringDatasource extends DataSourceWithBackend<
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
range: this.timeSrv.timeRange(),
|
range: timeRange ?? this.timeSrv.timeRange(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const queries = options.targets;
|
const queries = options.targets;
|
||||||
|
Loading…
Reference in New Issue
Block a user