AzureMonitor: Convert Logs to React (#32315)

* Convert Logs to React

* copy changes

* fix effect deps

* tests for logs

* remove any from test

* Update QueryEditor.tsx
This commit is contained in:
Josh Hunt 2021-04-13 14:29:32 +01:00 committed by GitHub
parent 7ea58f9cf5
commit d2bdb4ed41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 257 additions and 13 deletions

View File

@ -17,6 +17,8 @@ export default function createMockDatasource() {
getSubscriptions: jest.fn().mockResolvedValueOnce([]),
},
getAzureLogAnalyticsWorkspaces: jest.fn().mockResolvedValueOnce([]),
getResourceGroups: jest.fn().mockResolvedValueOnce([]),
getMetricDefinitions: jest.fn().mockResolvedValueOnce([]),
getResourceNames: jest.fn().mockResolvedValueOnce([]),

View File

@ -0,0 +1,47 @@
import { SelectableValue } from '@grafana/data';
import { Select } from '@grafana/ui';
import React, { useCallback, useMemo } from 'react';
import { AzureMonitorOption, AzureQueryEditorFieldProps, AzureResultFormat } from '../../types';
import { findOption } from '../../utils/common';
import { Field } from '../Field';
const FORMAT_OPTIONS: Array<AzureMonitorOption<AzureResultFormat>> = [
{ label: 'Time series', value: 'time_series' },
{ label: 'Table', value: 'table' },
];
const FormatAsField: React.FC<AzureQueryEditorFieldProps> = ({ query, variableOptionGroup, onQueryChange }) => {
const options = useMemo(() => [...FORMAT_OPTIONS, variableOptionGroup], [variableOptionGroup]);
const handleChange = useCallback(
(change: SelectableValue<AzureResultFormat>) => {
const { value } = change;
if (!value) {
return;
}
onQueryChange({
...query,
azureLogAnalytics: {
...query.azureLogAnalytics,
resultFormat: value,
},
});
},
[onQueryChange, query]
);
return (
<Field label="Format as">
<Select
inputId="azure-monitor-logs-workspaces-field"
value={findOption(FORMAT_OPTIONS, query.azureLogAnalytics.resultFormat)}
onChange={handleChange}
options={options}
width={38}
/>
</Field>
);
};
export default FormatAsField;

View File

@ -0,0 +1,69 @@
import React from 'react';
import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery } from '../../types';
import Datasource from '../../datasource';
import { InlineFieldRow } from '@grafana/ui';
import SubscriptionField from '../SubscriptionField';
import WorkspaceField from './WorkspaceField';
import QueryField from './QueryField';
import FormatAsField from './FormatAsField';
interface LogsQueryEditorProps {
query: AzureMonitorQuery;
datasource: Datasource;
subscriptionId: string;
onChange: (newQuery: AzureMonitorQuery) => void;
variableOptionGroup: { label: string; options: AzureMonitorOption[] };
setError: (source: string, error: AzureMonitorErrorish | undefined) => void;
}
const LogsQueryEditor: React.FC<LogsQueryEditorProps> = ({
query,
datasource,
subscriptionId,
variableOptionGroup,
onChange,
setError,
}) => {
return (
<div data-testid="azure-monitor-logs-query-editor">
<InlineFieldRow>
<SubscriptionField
query={query}
datasource={datasource}
subscriptionId={subscriptionId}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
/>
<WorkspaceField
query={query}
datasource={datasource}
subscriptionId={subscriptionId}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
/>
</InlineFieldRow>
<QueryField
query={query}
datasource={datasource}
subscriptionId={subscriptionId}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
/>
<FormatAsField
query={query}
datasource={datasource}
subscriptionId={subscriptionId}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
/>
</div>
);
};
export default LogsQueryEditor;

View File

@ -0,0 +1,32 @@
import { CodeEditor } from '@grafana/ui';
import React, { useCallback } from 'react';
import { AzureQueryEditorFieldProps } from '../../types';
const QueryField: React.FC<AzureQueryEditorFieldProps> = ({ query, onQueryChange }) => {
const onChange = useCallback(
(newQuery: string) => {
onQueryChange({
...query,
azureLogAnalytics: {
...query.azureLogAnalytics,
query: newQuery,
},
});
},
[onQueryChange, query]
);
return (
<CodeEditor
value={query.azureLogAnalytics.query}
language="kql"
height={200}
width="100%"
showMiniMap={false}
onBlur={onChange}
onSave={onChange}
/>
);
};
export default QueryField;

View File

@ -0,0 +1,64 @@
import { SelectableValue } from '@grafana/data';
import { Select } from '@grafana/ui';
import React, { useEffect, useState, useMemo, useCallback } from 'react';
import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types';
import { findOption, toOption } from '../../utils/common';
import { Field } from '../Field';
const ERROR_SOURCE = 'logs-workspaces';
const WorkspaceField: React.FC<AzureQueryEditorFieldProps> = ({
query,
datasource,
subscriptionId,
variableOptionGroup,
onQueryChange,
setError,
}) => {
const [workspaces, setWorkspaces] = useState<AzureMonitorOption[]>([]);
useEffect(() => {
if (!subscriptionId) {
workspaces.length > 0 && setWorkspaces([]);
}
datasource
.getAzureLogAnalyticsWorkspaces(subscriptionId)
.then((results) => {
setWorkspaces(results.map(toOption));
})
.catch((err) => setError(ERROR_SOURCE, err));
}, [datasource, setError, subscriptionId, workspaces.length]);
const handleChange = useCallback(
(change: SelectableValue<string>) => {
if (!change.value) {
return;
}
onQueryChange({
...query,
azureLogAnalytics: {
...query.azureLogAnalytics,
workspace: change.value,
},
});
},
[onQueryChange, query]
);
const options = useMemo(() => [...workspaces, variableOptionGroup], [workspaces, variableOptionGroup]);
return (
<Field label="Workspace">
<Select
inputId="azure-monitor-logs-workspaces-field"
value={findOption(workspaces, query.azureLogAnalytics.workspace)}
onChange={handleChange}
options={options}
width={38}
/>
</Field>
);
};
export default WorkspaceField;

View File

@ -0,0 +1 @@
export { default } from './LogsQueryEditor';

View File

@ -26,7 +26,7 @@ const LegendFormatField: React.FC<AzureQueryEditorFieldProps> = ({ onQueryChange
}, [onQueryChange, query, value]);
return (
<Field label="Legend Format">
<Field label="Legend format">
<Input
id="azure-monitor-metrics-legend-field"
placeholder="Alias patterns"

View File

@ -65,7 +65,7 @@ const MetricNamespaceField: React.FC<AzureQueryEditorFieldProps> = ({
const options = useMemo(() => [...metricNamespaces, variableOptionGroup], [metricNamespaces, variableOptionGroup]);
return (
<Field label="Metric Namespace">
<Field label="Metric namespace">
<Select
inputId="azure-monitor-metrics-metric-namespace-field"
value={findOption(metricNamespaces, query.azureMonitor.metricNamespace)}

View File

@ -59,7 +59,7 @@ const ResourceGroupsField: React.FC<AzureQueryEditorFieldProps> = ({
const options = useMemo(() => [...resourceGroups, variableOptionGroup], [resourceGroups, variableOptionGroup]);
return (
<Field label="Resource Group">
<Field label="Resource group">
<Select
inputId="azure-monitor-metrics-resource-group-field"
value={findOption(resourceGroups, query.azureMonitor.resourceGroup)}

View File

@ -58,7 +58,7 @@ const ResourceNameField: React.FC<AzureQueryEditorFieldProps> = ({
const selectedResourceNameValue = findOption(resourceNames, query.azureMonitor.resourceName);
return (
<Field label="Resource Name">
<Field label="Resource name">
<Select
inputId="azure-monitor-metrics-resource-name-field"
value={selectedResourceNameValue}

View File

@ -55,7 +55,7 @@ const TimeGrainField: React.FC<TimeGrainFieldProps> = ({
}, [timeGrainOptions, variableOptionGroup]);
return (
<Field label="Time Grain">
<Field label="Time grain">
<Select
inputId="azure-monitor-metrics-time-grain-field"
value={findOption(timeGrainOptions, query.azureMonitor.timeGrain)}

View File

@ -8,6 +8,15 @@ import createMockQuery from '../../__mocks__/query';
import createMockDatasource from '../../__mocks__/datasource';
import { AzureQueryType } from '../../types';
import { invalidNamespaceError } from '../../__mocks__/errors';
import * as ui from '@grafana/ui';
// Have to mock CodeEditor because it doesnt seem to work in tests???
jest.mock('@grafana/ui', () => ({
...jest.requireActual<typeof ui>('@grafana/ui'),
CodeEditor: function CodeEditor({ value }: { value: string }) {
return <pre>{value}</pre>;
},
}));
const variableOptionGroup = {
label: 'Template variables',
@ -17,9 +26,14 @@ const variableOptionGroup = {
describe('Azure Monitor QueryEditor', () => {
it('renders the Metrics query editor when the query type is Metrics', async () => {
const mockDatasource = createMockDatasource();
const mockQuery = {
...createMockQuery(),
queryType: AzureQueryType.AzureMonitor,
};
render(
<QueryEditor
query={createMockQuery()}
query={mockQuery}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
onChange={() => {}}
@ -28,22 +42,22 @@ describe('Azure Monitor QueryEditor', () => {
await waitFor(() => expect(screen.getByTestId('azure-monitor-metrics-query-editor')).toBeInTheDocument());
});
it("does not render the Metrics query editor when the query type isn't Metrics", async () => {
it('renders the Metrics query editor when the query type is Metrics', async () => {
const mockDatasource = createMockDatasource();
const mockQuery = createMockQuery();
const logsMockQuery = {
...mockQuery,
const mockQuery = {
...createMockQuery(),
queryType: AzureQueryType.LogAnalytics,
};
render(
<QueryEditor
query={logsMockQuery}
query={mockQuery}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
onChange={() => {}}
/>
);
await waitFor(() => expect(screen.queryByTestId('azure-monitor-metrics-query-editor')).not.toBeInTheDocument());
await waitFor(() => expect(screen.queryByTestId('azure-monitor-logs-query-editor')).toBeInTheDocument());
});
it('changes the query type when selected', async () => {

View File

@ -5,6 +5,7 @@ import { AzureMonitorQuery, AzureQueryType, AzureMonitorOption, AzureMonitorErro
import MetricsQueryEditor from '../MetricsQueryEditor';
import QueryTypeField from './QueryTypeField';
import useLastError from '../../utils/useLastError';
import LogsQueryEditor from '../LogsQueryEditor';
interface BaseQueryEditorProps {
query: AzureMonitorQuery;
@ -70,6 +71,18 @@ const EditorForQueryType: React.FC<EditorForQueryTypeProps> = ({
setError={setError}
/>
);
case AzureQueryType.LogAnalytics:
return (
<LogsQueryEditor
subscriptionId={subscriptionId}
query={query}
datasource={datasource}
onChange={onChange}
variableOptionGroup={variableOptionGroup}
setError={setError}
/>
);
}
return null;

View File

@ -30,7 +30,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
];
// Query types that have been migrated to React
reactQueryEditors = [AzureQueryType.AzureMonitor];
reactQueryEditors = [AzureQueryType.AzureMonitor, AzureQueryType.LogAnalytics];
// target: AzureMonitorQuery;

View File

@ -3,6 +3,8 @@ import Datasource from './datasource';
export type AzureDataSourceSettings = DataSourceSettings<AzureDataSourceJsonData, AzureDataSourceSecureJsonData>;
export type AzureResultFormat = 'time_series' | 'table';
export enum AzureQueryType {
AzureMonitor = 'Azure Monitor',
ApplicationInsights = 'Application Insights',