[v10.4.x] CloudMonitoring: Fix query type selection issue (#88023)

CloudMonitoring: Fix query type selection issue (#87990)

* Use deepEqual to ensure migratedQuery is only returned when query is unchanged

* Add selectors for query editors

* Add more tests for query editor component

* Clarifying comments

* Fix how state is set

* Simplify query editor loading and migration

(cherry picked from commit 58d382e5dd)

Co-authored-by: Andreas Christou <andreas.christou@grafana.com>
pull/88094/head
grafana-delivery-bot[bot] 1 year ago committed by GitHub
parent b980721bbc
commit 8791093f6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      public/app/plugins/datasource/cloud-monitoring/components/MQLQueryEditor.tsx
  2. 5
      public/app/plugins/datasource/cloud-monitoring/components/PromQLEditor.tsx
  3. 97
      public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.test.tsx
  4. 138
      public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx
  5. 5
      public/app/plugins/datasource/cloud-monitoring/components/SLOQueryEditor.tsx
  6. 5
      public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx
  7. 30
      public/app/plugins/datasource/cloud-monitoring/e2e/selectors.ts

@ -2,6 +2,8 @@ import React from 'react';
import { TextArea } from '@grafana/ui';
import { selectors } from '../e2e/selectors';
export interface Props {
onChange: (query: string) => void;
onRunQuery: () => void;
@ -17,7 +19,7 @@ export function MQLQueryEditor({ query, onChange, onRunQuery }: React.PropsWithC
};
return (
<>
<span data-testid={selectors.components.queryEditor.mqlMetricsQueryEditor.container.input}>
<TextArea
name="Query"
className="slate-query-field"
@ -28,6 +30,6 @@ export function MQLQueryEditor({ query, onChange, onRunQuery }: React.PropsWithC
onChange={(e) => onChange(e.currentTarget.value)}
onKeyDown={onKeyDown}
/>
</>
</span>
);
}

@ -5,6 +5,7 @@ import { EditorField, EditorRow } from '@grafana/experimental';
import { TextArea, Input } from '@grafana/ui';
import CloudMonitoringDatasource from '../datasource';
import { selectors } from '../e2e/selectors';
import { PromQLQuery } from '../types/query';
import { Project } from './Project';
@ -41,7 +42,7 @@ export function PromQLQueryEditor({
}
return (
<>
<span data-testid={selectors.components.queryEditor.promQlQueryEditor.container.input}>
<EditorRow>
<Project
refId={refId}
@ -75,6 +76,6 @@ export function PromQLQueryEditor({
/>
</EditorField>
</EditorRow>
</>
</span>
);
}

@ -1,8 +1,10 @@
import { render, waitFor } from '@testing-library/react';
import { render, waitFor, screen } from '@testing-library/react';
import React from 'react';
import { select } from 'react-select-event';
import { createMockDatasource } from '../__mocks__/cloudMonitoringDatasource';
import { createMockQuery } from '../__mocks__/cloudMonitoringQuery';
import { selectors } from '../e2e/selectors';
import { QueryType } from '../types/query';
import { QueryEditor } from './QueryEditor';
@ -30,9 +32,9 @@ describe('QueryEditor', () => {
const onChange = jest.fn();
datasource.migrateQuery = jest.fn().mockReturnValue(defaultProps.query);
render(<QueryEditor {...defaultProps} datasource={datasource} onChange={onChange} />);
render(<QueryEditor {...defaultProps} query={{ refId: 'A' }} datasource={datasource} onChange={onChange} />);
await waitFor(() => expect(datasource.migrateQuery).toHaveBeenCalledTimes(1));
await waitFor(() => expect(onChange).toHaveBeenCalledTimes(1));
await waitFor(() => expect(onChange).toHaveBeenCalled());
await waitFor(() => expect(onChange).toHaveBeenCalledWith(defaultProps.query));
});
@ -46,4 +48,93 @@ describe('QueryEditor', () => {
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ queryType: QueryType.TIME_SERIES_LIST }))
);
});
it('renders the visual metrics query editor when the query type is timeSeriesList', async () => {
const mockDatasource = createMockDatasource();
const mockQuery = {
...createMockQuery(),
queryType: QueryType.TIME_SERIES_LIST,
};
render(<QueryEditor query={mockQuery} datasource={mockDatasource} onChange={() => {}} onRunQuery={() => {}} />);
await waitFor(() =>
expect(
screen.getByTestId(selectors.components.queryEditor.visualMetricsQueryEditor.container.input)
).toBeInTheDocument()
);
});
it('renders the visual metrics query editor when the query type is timeSeriesList', async () => {
const mockDatasource = createMockDatasource();
const mockQuery = {
...createMockQuery(),
queryType: QueryType.TIME_SERIES_LIST,
};
render(<QueryEditor query={mockQuery} datasource={mockDatasource} onChange={() => {}} onRunQuery={() => {}} />);
await waitFor(() =>
expect(
screen.getByTestId(selectors.components.queryEditor.visualMetricsQueryEditor.container.input)
).toBeInTheDocument()
);
});
it('renders the mql metrics query editor when the query type is timeSeriesQuery', async () => {
const mockDatasource = createMockDatasource();
const mockQuery = {
...createMockQuery(),
queryType: QueryType.TIME_SERIES_QUERY,
};
render(<QueryEditor query={mockQuery} datasource={mockDatasource} onChange={() => {}} onRunQuery={() => {}} />);
await waitFor(() =>
expect(
screen.getByTestId(selectors.components.queryEditor.mqlMetricsQueryEditor.container.input)
).toBeInTheDocument()
);
});
it('renders the SLO query editor when the query type is SLO', async () => {
const mockDatasource = createMockDatasource();
const mockQuery = {
...createMockQuery(),
queryType: QueryType.SLO,
};
render(<QueryEditor query={mockQuery} datasource={mockDatasource} onChange={() => {}} onRunQuery={() => {}} />);
await waitFor(() =>
expect(screen.getByTestId(selectors.components.queryEditor.sloQueryEditor.container.input)).toBeInTheDocument()
);
});
it('renders the PromQL query editor when the query type is PromQL', async () => {
const mockDatasource = createMockDatasource();
const mockQuery = {
...createMockQuery(),
queryType: QueryType.PROMQL,
};
render(<QueryEditor query={mockQuery} datasource={mockDatasource} onChange={() => {}} onRunQuery={() => {}} />);
await waitFor(() =>
expect(screen.getByTestId(selectors.components.queryEditor.promQlQueryEditor.container.input)).toBeInTheDocument()
);
});
it('changes the query type when selected', async () => {
const mockDatasource = createMockDatasource();
const mockQuery = createMockQuery();
const onChange = jest.fn();
render(<QueryEditor query={mockQuery} datasource={mockDatasource} onChange={onChange} onRunQuery={() => {}} />);
await waitFor(() => expect(screen.getByTestId(selectors.components.queryEditor.container)).toBeInTheDocument());
const queryType = await screen.findByLabelText(/Query type/);
await waitFor(() => select(queryType, 'PromQL', { container: document.body }));
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
refId: mockQuery.refId,
datasource: mockQuery.datasource,
queryType: QueryType.PROMQL,
})
);
});
});

@ -1,11 +1,13 @@
import deepEqual from 'fast-deep-equal';
import { isEqual } from 'lodash';
import React, { useEffect, useMemo, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { QueryEditorProps, getDefaultTimeRange, toOption } from '@grafana/data';
import { EditorRows } from '@grafana/experimental';
import { ConfirmModal } from '@grafana/ui';
import CloudMonitoringDatasource from '../datasource';
import { selectors } from '../e2e/selectors';
import { CloudMonitoringQuery, PromQLQuery, QueryType, SLOQuery } from '../types/query';
import { CloudMonitoringOptions } from '../types/types';
@ -19,24 +21,16 @@ import { MetricQueryEditor, SLOQueryEditor } from './';
export type Props = QueryEditorProps<CloudMonitoringDatasource, CloudMonitoringQuery, CloudMonitoringOptions>;
export const QueryEditor = (props: Props) => {
const { datasource, query: oldQ, onRunQuery, onChange, range } = props;
const { datasource, query, onRunQuery, onChange, range } = props;
const [modalIsOpen, setModalIsOpen] = useState<boolean>(false);
const [migratedQuery, setMigratedQuery] = useState<CloudMonitoringQuery | undefined>();
const query = useMemo(() => {
if (!migratedQuery) {
const migratedQuery = datasource.migrateQuery(oldQ);
setMigratedQuery(migratedQuery);
// Update the query once the migrations have been completed.
onChange({ ...migratedQuery });
return migratedQuery;
}
if (migratedQuery) {
return migratedQuery;
useEffect(() => {
const migrated = datasource.migrateQuery(query);
if (!deepEqual(migrated, query)) {
onChange({ ...migrated });
}
}, [query, datasource, onChange]);
return oldQ;
}, [oldQ, datasource, onChange, migratedQuery]);
const [currentQuery, setCurrentQuery] = useState<CloudMonitoringQuery>(query);
const [queryHasBeenEdited, setQueryHasBeenEdited] = useState<boolean>(false);
@ -95,62 +89,64 @@ export const QueryEditor = (props: Props) => {
};
return (
<EditorRows>
<ConfirmModal
data-testid="switch-query-type-modal"
title="Warning"
body="By switching your query type, your current query will be lost."
isOpen={modalIsOpen}
onConfirm={() => {
setModalIsOpen(false);
onChange(currentQuery);
setQueryHasBeenEdited(false);
}}
confirmText="Confirm"
onDismiss={() => {
setModalIsOpen(false);
setCurrentQuery(query);
}}
/>
<QueryHeader query={query} onChange={checkForModalDisplay} onRunQuery={onRunQuery} />
{queryType === QueryType.PROMQL && (
<PromQLQueryEditor
refId={query.refId}
variableOptionGroup={variableOptionGroup}
onChange={onPromQLQueryChange}
onRunQuery={onRunQuery}
datasource={datasource}
query={promQLQuery}
/>
)}
{queryType !== QueryType.SLO && (
<MetricQueryEditor
refId={query.refId}
variableOptionGroup={variableOptionGroup}
customMetaData={customMetaData}
onChange={onMetricQueryChange}
onRunQuery={onRunQuery}
datasource={datasource}
query={query}
range={range || getDefaultTimeRange()}
/>
)}
{queryType === QueryType.SLO && (
<SLOQueryEditor
refId={query.refId}
variableOptionGroup={variableOptionGroup}
customMetaData={customMetaData}
onChange={onSLOQueryChange}
onRunQuery={onRunQuery}
datasource={datasource}
query={sloQuery}
aliasBy={query.aliasBy}
onChangeAliasBy={(aliasBy: string) => onChange({ ...query, aliasBy })}
<span data-testid={selectors.components.queryEditor.container}>
<EditorRows>
<ConfirmModal
data-testid="switch-query-type-modal"
title="Warning"
body="By switching your query type, your current query will be lost."
isOpen={modalIsOpen}
onConfirm={() => {
setModalIsOpen(false);
onChange(currentQuery);
setQueryHasBeenEdited(false);
}}
confirmText="Confirm"
onDismiss={() => {
setModalIsOpen(false);
setCurrentQuery(query);
}}
/>
)}
</EditorRows>
<QueryHeader query={query} onChange={checkForModalDisplay} onRunQuery={onRunQuery} />
{queryType === QueryType.PROMQL && (
<PromQLQueryEditor
refId={query.refId}
variableOptionGroup={variableOptionGroup}
onChange={onPromQLQueryChange}
onRunQuery={onRunQuery}
datasource={datasource}
query={promQLQuery}
/>
)}
{queryType !== QueryType.SLO && (
<MetricQueryEditor
refId={query.refId}
variableOptionGroup={variableOptionGroup}
customMetaData={customMetaData}
onChange={onMetricQueryChange}
onRunQuery={onRunQuery}
datasource={datasource}
query={query}
range={range || getDefaultTimeRange()}
/>
)}
{queryType === QueryType.SLO && (
<SLOQueryEditor
refId={query.refId}
variableOptionGroup={variableOptionGroup}
customMetaData={customMetaData}
onChange={onSLOQueryChange}
onRunQuery={onRunQuery}
datasource={datasource}
query={sloQuery}
aliasBy={query.aliasBy}
onChangeAliasBy={(aliasBy: string) => onChange({ ...query, aliasBy })}
/>
)}
</EditorRows>
</span>
);
};

@ -5,6 +5,7 @@ import { EditorField, EditorFieldGroup, EditorRow } from '@grafana/experimental'
import { ALIGNMENT_PERIODS, SLO_BURN_RATE_SELECTOR_NAME } from '../constants';
import CloudMonitoringDatasource from '../datasource';
import { selectors } from '../e2e/selectors';
import { alignmentPeriodLabel } from '../functions';
import { AlignmentTypes, SLOQuery } from '../types/query';
import { CustomMetaData } from '../types/types';
@ -54,7 +55,7 @@ export function SLOQueryEditor({
}: React.PropsWithChildren<Props>) {
const alignmentLabel = useMemo(() => alignmentPeriodLabel(customMetaData, datasource), [customMetaData, datasource]);
return (
<>
<span data-testid={selectors.components.queryEditor.sloQueryEditor.container.input}>
<EditorRow>
<Project
refId={refId}
@ -107,6 +108,6 @@ export function SLOQueryEditor({
<AliasBy refId={refId} value={aliasBy} onChange={onChangeAliasBy} />
</EditorRow>
</>
</span>
);
}

@ -9,6 +9,7 @@ import { reportInteraction } from '@grafana/runtime';
import { getSelectStyles, Select, AsyncSelect, useStyles2, useTheme2 } from '@grafana/ui';
import CloudMonitoringDatasource from '../datasource';
import { selectors } from '../e2e/selectors';
import { getAlignmentPickerData, getMetricType, setMetricType } from '../functions';
import { PreprocessorType, TimeSeriesList, MetricKind, ValueTypes } from '../types/query';
import { CustomMetaData, MetricDescriptor } from '../types/types';
@ -223,7 +224,7 @@ export function Editor({
};
return (
<>
<span data-testid={selectors.components.queryEditor.visualMetricsQueryEditor.container.input}>
<EditorRow>
<EditorFieldGroup>
<Project
@ -306,7 +307,7 @@ export function Editor({
<AliasBy refId={refId} value={aliasBy} onChange={onChangeAliasBy} />
</EditorRow>
</>
</>
</span>
);
}

@ -0,0 +1,30 @@
import { E2ESelectors } from '@grafana/e2e-selectors';
export const components = {
queryEditor: {
container: 'data-testid cloud-monitoring-query-editor',
header: {
select: 'data-testid cloud-monitoring-header',
},
visualMetricsQueryEditor: {
container: { input: 'data-testid cloud-monitoring-visual-metrics-query-editor' },
},
mqlMetricsQueryEditor: {
container: { input: 'data-testid cloud-monitoring-mql-query-editor' },
},
sloQueryEditor: {
container: {
input: 'data-testid cloud-monitoring-slo-query-editor',
},
},
promQlQueryEditor: {
container: {
input: 'data-testid cloud-monitoring-prom-ql-query-editor',
},
},
},
};
export const selectors: { components: E2ESelectors<typeof components> } = {
components: components,
};
Loading…
Cancel
Save