mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Cloudwatch: Migrate Config editor and Variable editor to new form stying under feature toggle (#77838)
This commit is contained in:
parent
b3bf38ad68
commit
b56d7131bd
@ -5413,9 +5413,6 @@ exports[`better eslint`] = {
|
||||
"public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LegacyLogGroupNamesSelection.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
||||
],
|
||||
"public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LogGroupsField.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
||||
],
|
||||
"public/app/plugins/datasource/cloudwatch/datasource.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
|
@ -242,7 +242,7 @@
|
||||
"@fingerprintjs/fingerprintjs": "^3.4.2",
|
||||
"@glideapps/glide-data-grid": "^5.2.1",
|
||||
"@grafana-plugins/grafana-testdata-datasource": "workspace:*",
|
||||
"@grafana/aws-sdk": "0.2.0",
|
||||
"@grafana/aws-sdk": "0.3.1",
|
||||
"@grafana/data": "workspace:*",
|
||||
"@grafana/e2e-selectors": "workspace:*",
|
||||
"@grafana/experimental": "1.7.4",
|
||||
|
@ -5,6 +5,7 @@ import { Provider } from 'react-redux';
|
||||
|
||||
import { AwsAuthType } from '@grafana/aws-sdk';
|
||||
import { PluginContextProvider, PluginMeta, PluginMetaInfo, PluginType } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
|
||||
import { CloudWatchSettings, setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource';
|
||||
@ -128,178 +129,205 @@ describe('Render', () => {
|
||||
datasource.getVariables = jest.fn().mockReturnValue([]);
|
||||
});
|
||||
|
||||
it('it should disable access key id field when the datasource has been previously configured', async () => {
|
||||
setup({
|
||||
secureJsonFields: {
|
||||
secretKey: true,
|
||||
},
|
||||
});
|
||||
await waitFor(async () => expect(screen.getByPlaceholderText('Configured')).toBeDisabled());
|
||||
});
|
||||
const originalFormFeatureToggleValue = config.featureToggles.awsDatasourcesNewFormStyling;
|
||||
|
||||
it('should show credentials profile name field', async () => {
|
||||
setup({
|
||||
jsonData: {
|
||||
authType: AwsAuthType.Credentials,
|
||||
},
|
||||
});
|
||||
await waitFor(async () => expect(screen.getByLabelText('Credentials Profile Name')).toBeInTheDocument());
|
||||
});
|
||||
const cleanupFeatureToggle = () => {
|
||||
config.featureToggles.awsDatasourcesNewFormStyling = originalFormFeatureToggleValue;
|
||||
};
|
||||
|
||||
it('should show access key and secret access key fields when the datasource has not been configured before', async () => {
|
||||
setup({
|
||||
jsonData: {
|
||||
authType: AwsAuthType.Keys,
|
||||
},
|
||||
});
|
||||
await waitFor(async () => {
|
||||
expect(screen.getByLabelText('Access Key ID')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Secret Access Key')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show arn role field', async () => {
|
||||
setup({
|
||||
jsonData: {
|
||||
authType: AwsAuthType.ARN,
|
||||
},
|
||||
});
|
||||
await waitFor(async () => expect(screen.getByLabelText('Assume Role ARN')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should display log group selector field', async () => {
|
||||
setup();
|
||||
await waitFor(async () => expect(screen.getByText('Select log groups')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should only display the first two default log groups and show all of them when clicking "Show all" button', async () => {
|
||||
setup({
|
||||
version: 2,
|
||||
jsonData: {
|
||||
logGroups: [
|
||||
{ arn: 'arn:aws:logs:us-east-2:123456789012:log-group:logGroup-foo:*', name: 'logGroup-foo' },
|
||||
{ arn: 'arn:aws:logs:us-east-2:123456789012:log-group:logGroup-bar:*', name: 'logGroup-bar' },
|
||||
{ arn: 'arn:aws:logs:us-east-2:123456789012:log-group:logGroup-baz:*', name: 'logGroup-baz' },
|
||||
],
|
||||
},
|
||||
});
|
||||
await waitFor(async () => {
|
||||
expect(await screen.getByText('logGroup-foo')).toBeInTheDocument();
|
||||
expect(await screen.getByText('logGroup-bar')).toBeInTheDocument();
|
||||
expect(await screen.queryByText('logGroup-baz')).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByText('Show all'));
|
||||
|
||||
expect(await screen.getByText('logGroup-baz')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should load the data source if it was saved before', async () => {
|
||||
const SAVED_VERSION = 2;
|
||||
setup({ version: SAVED_VERSION });
|
||||
await waitFor(async () => expect(loadDataSourceMock).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('should not load the data source if it wasnt saved before', async () => {
|
||||
const SAVED_VERSION = undefined;
|
||||
setup({ version: SAVED_VERSION });
|
||||
await waitFor(async () => expect(loadDataSourceMock).not.toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('should show error message if Select log group button is clicked when data source is never saved', async () => {
|
||||
setup({ version: 1 });
|
||||
await waitFor(() => expect(screen.getByText('Select log groups')).toBeInTheDocument());
|
||||
await userEvent.click(screen.getByText('Select log groups'));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText('You need to save the data source before adding log groups.')).toBeInTheDocument()
|
||||
);
|
||||
});
|
||||
|
||||
it('should show error message if Select log group button is clicked when data source is saved before but have unsaved changes', async () => {
|
||||
const SAVED_VERSION = 3;
|
||||
const newProps = {
|
||||
...props,
|
||||
options: {
|
||||
...props.options,
|
||||
version: SAVED_VERSION,
|
||||
},
|
||||
};
|
||||
const meta: PluginMeta = {
|
||||
...newProps.options,
|
||||
id: 'cloudwatch',
|
||||
type: PluginType.datasource,
|
||||
info: {} as PluginMetaInfo,
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
};
|
||||
|
||||
const { rerender } = render(
|
||||
<PluginContextProvider meta={meta}>
|
||||
<ConfigEditor {...newProps} />
|
||||
</PluginContextProvider>
|
||||
);
|
||||
await waitFor(() => expect(screen.getByText('Select log groups')).toBeInTheDocument());
|
||||
const rerenderProps = {
|
||||
...newProps,
|
||||
options: {
|
||||
...newProps.options,
|
||||
jsonData: {
|
||||
...newProps.options.jsonData,
|
||||
authType: AwsAuthType.Default,
|
||||
function run() {
|
||||
it('it should disable access key id field when the datasource has been previously configured', async () => {
|
||||
setup({
|
||||
secureJsonFields: {
|
||||
secretKey: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
rerender(
|
||||
<PluginContextProvider meta={meta}>
|
||||
<ConfigEditor {...rerenderProps} />
|
||||
</PluginContextProvider>
|
||||
);
|
||||
await waitFor(() => expect(screen.getByText('AWS SDK Default')).toBeInTheDocument());
|
||||
await userEvent.click(screen.getByText('Select log groups'));
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByText(
|
||||
'You have unsaved connection detail changes. You need to save the data source before adding log groups.'
|
||||
)
|
||||
).toBeInTheDocument()
|
||||
);
|
||||
});
|
||||
});
|
||||
await waitFor(async () => expect(screen.getByPlaceholderText('Configured')).toBeDisabled());
|
||||
});
|
||||
|
||||
it('should open log group selector if Select log group button is clicked when data source has saved changes', async () => {
|
||||
const newProps = {
|
||||
...props,
|
||||
options: {
|
||||
...props.options,
|
||||
version: 1,
|
||||
},
|
||||
};
|
||||
const meta: PluginMeta = {
|
||||
...newProps.options,
|
||||
id: 'cloudwatch',
|
||||
type: PluginType.datasource,
|
||||
info: {} as PluginMetaInfo,
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
};
|
||||
const { rerender } = render(
|
||||
<PluginContextProvider meta={meta}>
|
||||
<ConfigEditor {...newProps} />
|
||||
</PluginContextProvider>
|
||||
);
|
||||
await waitFor(() => expect(screen.getByText('Select log groups')).toBeInTheDocument());
|
||||
const rerenderProps = {
|
||||
...newProps,
|
||||
options: {
|
||||
...newProps.options,
|
||||
it('should show credentials profile name field', async () => {
|
||||
setup({
|
||||
jsonData: {
|
||||
authType: AwsAuthType.Credentials,
|
||||
},
|
||||
});
|
||||
await waitFor(async () => expect(screen.getByText('Credentials Profile Name')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should show access key and secret access key fields when the datasource has not been configured before', async () => {
|
||||
setup({
|
||||
jsonData: {
|
||||
authType: AwsAuthType.Keys,
|
||||
},
|
||||
});
|
||||
await waitFor(async () => {
|
||||
expect(screen.getByLabelText('Access Key ID')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Secret Access Key')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show arn role field', async () => {
|
||||
setup({
|
||||
jsonData: {
|
||||
authType: AwsAuthType.ARN,
|
||||
},
|
||||
});
|
||||
await waitFor(async () => expect(screen.getByText('Assume Role ARN')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should display log group selector field', async () => {
|
||||
setup();
|
||||
await waitFor(async () => expect(screen.getByText('Select log groups')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should only display the first two default log groups and show all of them when clicking "Show all" button', async () => {
|
||||
setup({
|
||||
version: 2,
|
||||
},
|
||||
};
|
||||
rerender(
|
||||
<PluginContextProvider meta={meta}>
|
||||
<ConfigEditor {...rerenderProps} />
|
||||
</PluginContextProvider>
|
||||
);
|
||||
await userEvent.click(screen.getByText('Select log groups'));
|
||||
await waitFor(() => expect(screen.getByText('Log group name prefix')).toBeInTheDocument());
|
||||
jsonData: {
|
||||
logGroups: [
|
||||
{ arn: 'arn:aws:logs:us-east-2:123456789012:log-group:logGroup-foo:*', name: 'logGroup-foo' },
|
||||
{ arn: 'arn:aws:logs:us-east-2:123456789012:log-group:logGroup-bar:*', name: 'logGroup-bar' },
|
||||
{ arn: 'arn:aws:logs:us-east-2:123456789012:log-group:logGroup-baz:*', name: 'logGroup-baz' },
|
||||
],
|
||||
},
|
||||
});
|
||||
await waitFor(async () => {
|
||||
expect(await screen.getByText('logGroup-foo')).toBeInTheDocument();
|
||||
expect(await screen.getByText('logGroup-bar')).toBeInTheDocument();
|
||||
expect(await screen.queryByText('logGroup-baz')).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByText('Show all'));
|
||||
|
||||
expect(await screen.getByText('logGroup-baz')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should load the data source if it was saved before', async () => {
|
||||
const SAVED_VERSION = 2;
|
||||
setup({ version: SAVED_VERSION });
|
||||
await waitFor(async () => expect(loadDataSourceMock).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('should not load the data source if it wasnt saved before', async () => {
|
||||
const SAVED_VERSION = undefined;
|
||||
setup({ version: SAVED_VERSION });
|
||||
await waitFor(async () => expect(loadDataSourceMock).not.toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('should show error message if Select log group button is clicked when data source is never saved', async () => {
|
||||
setup({ version: 1 });
|
||||
await waitFor(() => expect(screen.getByText('Select log groups')).toBeInTheDocument());
|
||||
await userEvent.click(screen.getByText('Select log groups'));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText('You need to save the data source before adding log groups.')).toBeInTheDocument()
|
||||
);
|
||||
});
|
||||
|
||||
it('should show error message if Select log group button is clicked when data source is saved before but have unsaved changes', async () => {
|
||||
const SAVED_VERSION = 3;
|
||||
const newProps = {
|
||||
...props,
|
||||
options: {
|
||||
...props.options,
|
||||
version: SAVED_VERSION,
|
||||
},
|
||||
};
|
||||
const meta: PluginMeta = {
|
||||
...newProps.options,
|
||||
id: 'cloudwatch',
|
||||
type: PluginType.datasource,
|
||||
info: {} as PluginMetaInfo,
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
};
|
||||
|
||||
const { rerender } = render(
|
||||
<PluginContextProvider meta={meta}>
|
||||
<ConfigEditor {...newProps} />
|
||||
</PluginContextProvider>
|
||||
);
|
||||
await waitFor(() => expect(screen.getByText('Select log groups')).toBeInTheDocument());
|
||||
const rerenderProps = {
|
||||
...newProps,
|
||||
options: {
|
||||
...newProps.options,
|
||||
jsonData: {
|
||||
...newProps.options.jsonData,
|
||||
authType: AwsAuthType.Default,
|
||||
},
|
||||
},
|
||||
};
|
||||
rerender(
|
||||
<PluginContextProvider meta={meta}>
|
||||
<ConfigEditor {...rerenderProps} />
|
||||
</PluginContextProvider>
|
||||
);
|
||||
await waitFor(() => expect(screen.getByText('AWS SDK Default')).toBeInTheDocument());
|
||||
await userEvent.click(screen.getByText('Select log groups'));
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByText(
|
||||
'You have unsaved connection detail changes. You need to save the data source before adding log groups.'
|
||||
)
|
||||
).toBeInTheDocument()
|
||||
);
|
||||
});
|
||||
|
||||
it('should open log group selector if Select log group button is clicked when data source has saved changes', async () => {
|
||||
const newProps = {
|
||||
...props,
|
||||
options: {
|
||||
...props.options,
|
||||
version: 1,
|
||||
},
|
||||
};
|
||||
const meta: PluginMeta = {
|
||||
...newProps.options,
|
||||
id: 'cloudwatch',
|
||||
type: PluginType.datasource,
|
||||
info: {} as PluginMetaInfo,
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
};
|
||||
const { rerender } = render(
|
||||
<PluginContextProvider meta={meta}>
|
||||
<ConfigEditor {...newProps} />
|
||||
</PluginContextProvider>
|
||||
);
|
||||
await waitFor(() => expect(screen.getByText('Select log groups')).toBeInTheDocument());
|
||||
const rerenderProps = {
|
||||
...newProps,
|
||||
options: {
|
||||
...newProps.options,
|
||||
version: 2,
|
||||
},
|
||||
};
|
||||
rerender(
|
||||
<PluginContextProvider meta={meta}>
|
||||
<ConfigEditor {...rerenderProps} />
|
||||
</PluginContextProvider>
|
||||
);
|
||||
await userEvent.click(screen.getByText('Select log groups'));
|
||||
await waitFor(() => expect(screen.getByText('Log group name prefix')).toBeInTheDocument());
|
||||
});
|
||||
}
|
||||
|
||||
describe('QueryEditor with awsDatasourcesNewFormStyling feature toggle enabled', () => {
|
||||
beforeAll(() => {
|
||||
config.featureToggles.awsDatasourcesNewFormStyling = false;
|
||||
});
|
||||
afterAll(() => {
|
||||
cleanupFeatureToggle();
|
||||
});
|
||||
run();
|
||||
describe('QueryEditor with awsDatasourcesNewFormStyling feature toggle enabled', () => {
|
||||
beforeAll(() => {
|
||||
config.featureToggles.awsDatasourcesNewFormStyling = true;
|
||||
});
|
||||
afterAll(() => {
|
||||
cleanupFeatureToggle();
|
||||
});
|
||||
run();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -10,8 +10,9 @@ import {
|
||||
DataSourceTestSucceeded,
|
||||
DataSourceTestFailed,
|
||||
} from '@grafana/data';
|
||||
import { ConfigSection } from '@grafana/experimental';
|
||||
import { getAppEvents, usePluginInteractionReporter } from '@grafana/runtime';
|
||||
import { Input, InlineField, FieldProps, SecureSocksProxySettings } from '@grafana/ui';
|
||||
import { Input, InlineField, FieldProps, SecureSocksProxySettings, Field, Divider } from '@grafana/ui';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { config } from 'app/core/config';
|
||||
import { createWarningNotification } from 'app/core/copy/appNotification';
|
||||
@ -23,6 +24,7 @@ import { SelectableResourceValue } from '../../resources/types';
|
||||
import { CloudWatchJsonData, CloudWatchSecureJsonData } from '../../types';
|
||||
import { LogGroupsFieldWrapper } from '../shared/LogGroups/LogGroupsField';
|
||||
|
||||
import { SecureSocksProxySettingsNewStyling } from './SecureSocksProxySettingsNewStyling';
|
||||
import { XrayLinkConfig } from './XrayLinkConfig';
|
||||
|
||||
export type Props = DataSourcePluginOptionsEditorProps<CloudWatchJsonData, CloudWatchSecureJsonData>;
|
||||
@ -39,6 +41,7 @@ export const ConfigEditor = (props: Props) => {
|
||||
const [logGroupFieldState, setLogGroupFieldState] = useState<LogGroupFieldState>({
|
||||
invalid: false,
|
||||
});
|
||||
const newFormStylingEnabled = config.featureToggles.awsDatasourcesNewFormStyling;
|
||||
useEffect(() => setLogGroupFieldState({ invalid: false }), [props.options]);
|
||||
const report = usePluginInteractionReporter();
|
||||
useEffect(() => {
|
||||
@ -67,7 +70,103 @@ export const ConfigEditor = (props: Props) => {
|
||||
}
|
||||
}, [datasource, externalId]);
|
||||
|
||||
return (
|
||||
return newFormStylingEnabled ? (
|
||||
<div className="width-30">
|
||||
<ConnectionConfig
|
||||
{...props}
|
||||
newFormStylingEnabled={true}
|
||||
loadRegions={
|
||||
datasource &&
|
||||
(async () => {
|
||||
return datasource.resources
|
||||
.getRegions()
|
||||
.then((regions) =>
|
||||
regions.reduce(
|
||||
(acc: string[], curr: SelectableResourceValue) => (curr.value ? [...acc, curr.value] : acc),
|
||||
[]
|
||||
)
|
||||
);
|
||||
})
|
||||
}
|
||||
externalId={externalId}
|
||||
/>
|
||||
{config.secureSocksDSProxyEnabled && (
|
||||
<SecureSocksProxySettingsNewStyling options={options} onOptionsChange={onOptionsChange} />
|
||||
)}
|
||||
<Divider />
|
||||
<ConfigSection title="Cloudwatch Logs">
|
||||
<Field
|
||||
htmlFor="logsTimeout"
|
||||
label="Query Result Timeout"
|
||||
description='Grafana will poll for Cloudwatch Logs results every second until Done status is returned from AWS or timeout is exceeded, in which case Grafana will return an error. Note: For Alerting, the timeout from Grafana config file will take precedence. Must be a valid duration string, such as "30m" (default) "30s" "2000ms" etc.'
|
||||
invalid={Boolean(logsTimeoutError)}
|
||||
>
|
||||
<Input
|
||||
id="logsTimeout"
|
||||
width={60}
|
||||
placeholder="30m"
|
||||
value={options.jsonData.logsTimeout || ''}
|
||||
onChange={onUpdateDatasourceJsonDataOption(props, 'logsTimeout')}
|
||||
title={'The timeout must be a valid duration string, such as "15m" "30s" "2000ms" etc.'}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Default Log Groups"
|
||||
description="Optionally, specify default log groups for CloudWatch Logs queries."
|
||||
{...logGroupFieldState}
|
||||
>
|
||||
{datasource ? (
|
||||
<LogGroupsFieldWrapper
|
||||
newFormStylingEnabled={true}
|
||||
region={defaultRegion ?? ''}
|
||||
datasource={datasource}
|
||||
onBeforeOpen={() => {
|
||||
if (saved) {
|
||||
return;
|
||||
}
|
||||
|
||||
let error = 'You need to save the data source before adding log groups.';
|
||||
if (props.options.version && props.options.version > 1) {
|
||||
error =
|
||||
'You have unsaved connection detail changes. You need to save the data source before adding log groups.';
|
||||
}
|
||||
setLogGroupFieldState({
|
||||
invalid: true,
|
||||
error,
|
||||
});
|
||||
throw new Error(error);
|
||||
}}
|
||||
legacyLogGroupNames={defaultLogGroups}
|
||||
logGroups={logGroups}
|
||||
onChange={(updatedLogGroups) => {
|
||||
onOptionsChange({
|
||||
...props.options,
|
||||
jsonData: {
|
||||
...props.options.jsonData,
|
||||
logGroups: updatedLogGroups,
|
||||
defaultLogGroups: undefined,
|
||||
},
|
||||
});
|
||||
}}
|
||||
maxNoOfVisibleLogGroups={2}
|
||||
//legacy props
|
||||
legacyOnChange={(logGroups) => {
|
||||
updateDatasourcePluginJsonDataOption(props, 'defaultLogGroups', logGroups);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Field>
|
||||
</ConfigSection>
|
||||
<Divider />
|
||||
<XrayLinkConfig
|
||||
newFormStyling={true}
|
||||
onChange={(uid) => updateDatasourcePluginJsonDataOption(props, 'tracingDatasourceUid', uid)}
|
||||
datasourceUid={options.jsonData.tracingDatasourceUid}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<ConnectionConfig
|
||||
{...props}
|
||||
|
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
|
||||
import { DataSourceJsonData, DataSourcePluginOptionsEditorProps } from '@grafana/data';
|
||||
import { ConfigSection } from '@grafana/experimental';
|
||||
import { Field, Switch } from '@grafana/ui';
|
||||
|
||||
export interface Props<T extends DataSourceJsonData>
|
||||
extends Pick<DataSourcePluginOptionsEditorProps<T>, 'options' | 'onOptionsChange'> {}
|
||||
|
||||
export interface SecureSocksProxyConfig extends DataSourceJsonData {
|
||||
enableSecureSocksProxy?: boolean;
|
||||
}
|
||||
|
||||
export function SecureSocksProxySettingsNewStyling<T extends SecureSocksProxyConfig>({
|
||||
options,
|
||||
onOptionsChange,
|
||||
}: Props<T>): JSX.Element {
|
||||
return (
|
||||
<ConfigSection title="Secure Socks Proxy">
|
||||
<Field label="Enabled" description="Connect to this datasource via the secure socks proxy.">
|
||||
<Switch
|
||||
value={options.jsonData.enableSecureSocksProxy ?? false}
|
||||
onChange={(event) =>
|
||||
onOptionsChange({
|
||||
...options,
|
||||
jsonData: { ...options.jsonData, enableSecureSocksProxy: event.currentTarget.checked },
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
</ConfigSection>
|
||||
);
|
||||
}
|
@ -2,7 +2,8 @@ import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2, DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { Alert, InlineField, useStyles2 } from '@grafana/ui';
|
||||
import { ConfigSection } from '@grafana/experimental';
|
||||
import { Alert, Field, InlineField, useStyles2 } from '@grafana/ui';
|
||||
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
|
||||
@ -16,16 +17,39 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
interface Props {
|
||||
datasourceUid?: string;
|
||||
onChange: (uid: string) => void;
|
||||
newFormStyling?: boolean;
|
||||
}
|
||||
|
||||
const xRayDsId = 'grafana-x-ray-datasource';
|
||||
|
||||
export function XrayLinkConfig({ datasourceUid, onChange }: Props) {
|
||||
export function XrayLinkConfig({ newFormStyling, datasourceUid, onChange }: Props) {
|
||||
const hasXrayDatasource = Boolean(getDatasourceSrv().getList({ pluginId: xRayDsId }).length);
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
return newFormStyling ? (
|
||||
<ConfigSection
|
||||
title="X-ray trace link"
|
||||
description="Grafana will automatically create a link to a trace in X-ray data source if logs contain @xrayTraceId field"
|
||||
>
|
||||
{!hasXrayDatasource && (
|
||||
<Alert
|
||||
title={
|
||||
'There is no X-ray datasource to link to. First add an X-ray data source and then link it to Cloud Watch. '
|
||||
}
|
||||
severity="info"
|
||||
/>
|
||||
)}
|
||||
<Field htmlFor="data-source-picker" label="Data source" description="X-ray data source containing traces">
|
||||
<DataSourcePicker
|
||||
pluginId={xRayDsId}
|
||||
onChange={(ds: DataSourceInstanceSettings) => onChange(ds.uid)}
|
||||
current={datasourceUid}
|
||||
noDefault={true}
|
||||
/>
|
||||
</Field>
|
||||
</ConfigSection>
|
||||
) : (
|
||||
<>
|
||||
<h3 className="page-heading">X-ray trace link</h3>
|
||||
|
||||
|
@ -3,6 +3,8 @@ import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { select } from 'react-select-event';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource';
|
||||
import { GetDimensionKeysRequest } from '../../resources/types';
|
||||
import { VariableQueryType } from '../../types';
|
||||
@ -23,6 +25,12 @@ const defaultQuery = {
|
||||
|
||||
const ds = setupMockedDataSource();
|
||||
|
||||
const originalFormFeatureToggleValue = config.featureToggles.awsDatasourcesNewFormStyling;
|
||||
|
||||
const cleanupFeatureToggle = () => {
|
||||
config.featureToggles.awsDatasourcesNewFormStyling = originalFormFeatureToggleValue;
|
||||
};
|
||||
|
||||
ds.datasource.resources.getRegions = jest.fn().mockResolvedValue([
|
||||
{ label: 'a1', value: 'a1' },
|
||||
{ label: 'b1', value: 'b1' },
|
||||
@ -75,195 +83,216 @@ describe('VariableEditor', () => {
|
||||
beforeEach(() => {
|
||||
onChange.mockClear();
|
||||
});
|
||||
describe('and a new variable is created', () => {
|
||||
it('should trigger a query using the first query type in the array', async () => {
|
||||
const props = defaultProps;
|
||||
props.query = defaultQuery;
|
||||
render(<VariableQueryEditor {...props} />);
|
||||
|
||||
await waitFor(() => {
|
||||
const querySelect = screen.queryByRole('combobox', { name: 'Query type' });
|
||||
expect(querySelect).toBeInTheDocument();
|
||||
expect(screen.queryByText('Regions')).toBeInTheDocument();
|
||||
// Should not render any fields besides Query Type
|
||||
const regionSelect = screen.queryByRole('combobox', { name: 'Region' });
|
||||
expect(regionSelect).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('and an existing variable is edited', () => {
|
||||
it('should trigger new query using the saved query type', async () => {
|
||||
const props = defaultProps;
|
||||
props.query = {
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.Metrics,
|
||||
namespace: 'z2',
|
||||
region: 'a1',
|
||||
};
|
||||
render(<VariableQueryEditor {...props} />);
|
||||
|
||||
await waitFor(() => {
|
||||
const querySelect = screen.queryByRole('combobox', { name: 'Query type' });
|
||||
expect(querySelect).toBeInTheDocument();
|
||||
expect(screen.queryByText('Metrics')).toBeInTheDocument();
|
||||
const regionSelect = screen.queryByRole('combobox', { name: 'Region' });
|
||||
expect(regionSelect).toBeInTheDocument();
|
||||
expect(screen.queryByText('a1')).toBeInTheDocument();
|
||||
const namespaceSelect = screen.queryByRole('combobox', { name: 'Namespace' });
|
||||
expect(namespaceSelect).toBeInTheDocument();
|
||||
expect(screen.queryByText('z2')).toBeInTheDocument();
|
||||
// Should only render Query Type, Region, and Namespace selectors
|
||||
const metricSelect = screen.queryByRole('combobox', { name: 'Metric' });
|
||||
expect(metricSelect).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
it('should parse dimensionFilters correctly', async () => {
|
||||
const props = defaultProps;
|
||||
props.query = {
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.DimensionValues,
|
||||
namespace: 'z2',
|
||||
region: 'a1',
|
||||
metricName: 'i3',
|
||||
dimensionKey: 's4',
|
||||
dimensionFilters: { s4: 'foo' },
|
||||
};
|
||||
await act(async () => {
|
||||
function run() {
|
||||
describe('and a new variable is created', () => {
|
||||
it('should trigger a query using the first query type in the array', async () => {
|
||||
const props = defaultProps;
|
||||
props.query = defaultQuery;
|
||||
render(<VariableQueryEditor {...props} />);
|
||||
});
|
||||
const filterItem = screen.getByTestId('cloudwatch-dimensions-filter-item');
|
||||
expect(filterItem).toBeInTheDocument();
|
||||
expect(within(filterItem).getByText('s4')).toBeInTheDocument();
|
||||
expect(within(filterItem).getByText('foo')).toBeInTheDocument();
|
||||
|
||||
// change filter key
|
||||
const keySelect = screen.getByRole('combobox', { name: 'Dimensions filter key' });
|
||||
// confirms getDimensionKeys was called with filter and that the element uses keysForDimensionFilter
|
||||
await select(keySelect, 'v4', {
|
||||
container: document.body,
|
||||
});
|
||||
expect(ds.datasource.resources.getDimensionKeys).toHaveBeenCalledWith({
|
||||
namespace: 'z2',
|
||||
region: 'a1',
|
||||
metricName: 'i3',
|
||||
dimensionFilters: undefined,
|
||||
});
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.DimensionValues,
|
||||
namespace: 'z2',
|
||||
region: 'a1',
|
||||
metricName: 'i3',
|
||||
dimensionKey: 's4',
|
||||
dimensionFilters: { v4: undefined },
|
||||
});
|
||||
|
||||
// set filter value
|
||||
const valueSelect = screen.getByRole('combobox', { name: 'Dimensions filter value' });
|
||||
await select(valueSelect, 'bar', {
|
||||
container: document.body,
|
||||
});
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.DimensionValues,
|
||||
namespace: 'z2',
|
||||
region: 'a1',
|
||||
metricName: 'i3',
|
||||
dimensionKey: 's4',
|
||||
dimensionFilters: { v4: 'bar' },
|
||||
await waitFor(() => {
|
||||
const querySelect = screen.queryByRole('combobox', { name: 'Query type' });
|
||||
expect(querySelect).toBeInTheDocument();
|
||||
expect(screen.queryByText('Regions')).toBeInTheDocument();
|
||||
// Should not render any fields besides Query Type
|
||||
const regionSelect = screen.queryByRole('combobox', { name: 'Region' });
|
||||
expect(regionSelect).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should parse multiFilters correctly', async () => {
|
||||
const props = defaultProps;
|
||||
props.query = {
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.EC2InstanceAttributes,
|
||||
region: 'a1',
|
||||
attributeName: 'Tags.blah',
|
||||
ec2Filters: { s4: ['foo', 'bar'] },
|
||||
};
|
||||
render(<VariableQueryEditor {...props} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByDisplayValue('Tags.blah')).toBeInTheDocument();
|
||||
describe('and an existing variable is edited', () => {
|
||||
it('should trigger new query using the saved query type', async () => {
|
||||
const props = defaultProps;
|
||||
props.query = {
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.Metrics,
|
||||
namespace: 'z2',
|
||||
region: 'a1',
|
||||
};
|
||||
render(<VariableQueryEditor {...props} />);
|
||||
|
||||
await waitFor(() => {
|
||||
const querySelect = screen.queryByRole('combobox', { name: 'Query type' });
|
||||
expect(querySelect).toBeInTheDocument();
|
||||
expect(screen.queryByText('Metrics')).toBeInTheDocument();
|
||||
const regionSelect = screen.queryByRole('combobox', { name: 'Region' });
|
||||
expect(regionSelect).toBeInTheDocument();
|
||||
expect(screen.queryByText('a1')).toBeInTheDocument();
|
||||
const namespaceSelect = screen.queryByRole('combobox', { name: 'Namespace' });
|
||||
expect(namespaceSelect).toBeInTheDocument();
|
||||
expect(screen.queryByText('z2')).toBeInTheDocument();
|
||||
// Should only render Query Type, Region, and Namespace selectors
|
||||
const metricSelect = screen.queryByRole('combobox', { name: 'Metric' });
|
||||
expect(metricSelect).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
it('should parse dimensionFilters correctly', async () => {
|
||||
const props = defaultProps;
|
||||
props.query = {
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.DimensionValues,
|
||||
namespace: 'z2',
|
||||
region: 'a1',
|
||||
metricName: 'i3',
|
||||
dimensionKey: 's4',
|
||||
dimensionFilters: { s4: 'foo' },
|
||||
};
|
||||
await act(async () => {
|
||||
render(<VariableQueryEditor {...props} />);
|
||||
});
|
||||
const filterItem = screen.getByTestId('cloudwatch-dimensions-filter-item');
|
||||
expect(filterItem).toBeInTheDocument();
|
||||
expect(within(filterItem).getByText('s4')).toBeInTheDocument();
|
||||
expect(within(filterItem).getByText('foo')).toBeInTheDocument();
|
||||
|
||||
const filterItem = screen.getByTestId('cloudwatch-multifilter-item');
|
||||
expect(filterItem).toBeInTheDocument();
|
||||
expect(within(filterItem).getByDisplayValue('foo, bar')).toBeInTheDocument();
|
||||
|
||||
// set filter value
|
||||
const valueElement = screen.getByTestId('cloudwatch-multifilter-item-value');
|
||||
expect(valueElement).toBeInTheDocument();
|
||||
await userEvent.type(valueElement!, ',baz');
|
||||
fireEvent.blur(valueElement!);
|
||||
|
||||
expect(screen.getByDisplayValue('foo, bar, baz')).toBeInTheDocument();
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.EC2InstanceAttributes,
|
||||
region: 'a1',
|
||||
attributeName: 'Tags.blah',
|
||||
ec2Filters: { s4: ['foo', 'bar', 'baz'] },
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('and a different region is selected', () => {
|
||||
it('should clear invalid fields', async () => {
|
||||
const props = defaultProps;
|
||||
props.query = {
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.DimensionValues,
|
||||
namespace: 'z2',
|
||||
region: 'a1',
|
||||
metricName: 'i3',
|
||||
dimensionKey: 's4',
|
||||
dimensionFilters: { s4: 'foo' },
|
||||
};
|
||||
render(<VariableQueryEditor {...props} />);
|
||||
|
||||
const querySelect = screen.queryByLabelText('Query type');
|
||||
expect(querySelect).toBeInTheDocument();
|
||||
expect(screen.queryByText('Dimension Values')).toBeInTheDocument();
|
||||
const regionSelect = screen.getByRole('combobox', { name: 'Region' });
|
||||
await waitFor(() =>
|
||||
select(regionSelect, 'b1', {
|
||||
// change filter key
|
||||
const keySelect = screen.getByRole('combobox', { name: 'Dimensions filter key' });
|
||||
// confirms getDimensionKeys was called with filter and that the element uses keysForDimensionFilter
|
||||
await select(keySelect, 'v4', {
|
||||
container: document.body,
|
||||
})
|
||||
);
|
||||
});
|
||||
expect(ds.datasource.resources.getDimensionKeys).toHaveBeenCalledWith({
|
||||
namespace: 'z2',
|
||||
region: 'a1',
|
||||
metricName: 'i3',
|
||||
dimensionFilters: undefined,
|
||||
});
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.DimensionValues,
|
||||
namespace: 'z2',
|
||||
region: 'a1',
|
||||
metricName: 'i3',
|
||||
dimensionKey: 's4',
|
||||
dimensionFilters: { v4: undefined },
|
||||
});
|
||||
|
||||
expect(ds.datasource.resources.getMetrics).toHaveBeenCalledWith({ namespace: 'z2', region: 'b1' });
|
||||
expect(ds.datasource.resources.getDimensionKeys).toHaveBeenCalledWith({ namespace: 'z2', region: 'b1' });
|
||||
expect(props.onChange).toHaveBeenCalledWith({
|
||||
...defaultQuery,
|
||||
refId: 'CloudWatchVariableQueryEditor-VariableQuery',
|
||||
queryType: VariableQueryType.DimensionValues,
|
||||
namespace: 'z2',
|
||||
region: 'b1',
|
||||
// metricName i3 exists in the new region and should not be removed
|
||||
metricName: 'i3',
|
||||
// dimensionKey s4 and valueDimension do not exist in the new region and should be removed
|
||||
dimensionKey: '',
|
||||
dimensionFilters: {},
|
||||
// set filter value
|
||||
const valueSelect = screen.getByRole('combobox', { name: 'Dimensions filter value' });
|
||||
await select(valueSelect, 'bar', {
|
||||
container: document.body,
|
||||
});
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.DimensionValues,
|
||||
namespace: 'z2',
|
||||
region: 'a1',
|
||||
metricName: 'i3',
|
||||
dimensionKey: 's4',
|
||||
dimensionFilters: { v4: 'bar' },
|
||||
});
|
||||
});
|
||||
it('should parse multiFilters correctly', async () => {
|
||||
const props = defaultProps;
|
||||
props.query = {
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.EC2InstanceAttributes,
|
||||
region: 'a1',
|
||||
attributeName: 'Tags.blah',
|
||||
ec2Filters: { s4: ['foo', 'bar'] },
|
||||
};
|
||||
render(<VariableQueryEditor {...props} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByDisplayValue('Tags.blah')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const filterItem = screen.getByTestId('cloudwatch-multifilter-item');
|
||||
expect(filterItem).toBeInTheDocument();
|
||||
expect(within(filterItem).getByDisplayValue('foo, bar')).toBeInTheDocument();
|
||||
|
||||
// set filter value
|
||||
const valueElement = screen.getByTestId('cloudwatch-multifilter-item-value');
|
||||
expect(valueElement).toBeInTheDocument();
|
||||
await userEvent.type(valueElement!, ',baz');
|
||||
fireEvent.blur(valueElement!);
|
||||
|
||||
expect(screen.getByDisplayValue('foo, bar, baz')).toBeInTheDocument();
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.EC2InstanceAttributes,
|
||||
region: 'a1',
|
||||
attributeName: 'Tags.blah',
|
||||
ec2Filters: { s4: ['foo', 'bar', 'baz'] },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('LogGroups queryType is selected', () => {
|
||||
it('should only render region and prefix', async () => {
|
||||
const props = defaultProps;
|
||||
props.query = {
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.LogGroups,
|
||||
};
|
||||
render(<VariableQueryEditor {...props} />);
|
||||
describe('and a different region is selected', () => {
|
||||
it('should clear invalid fields', async () => {
|
||||
const props = defaultProps;
|
||||
props.query = {
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.DimensionValues,
|
||||
namespace: 'z2',
|
||||
region: 'a1',
|
||||
metricName: 'i3',
|
||||
dimensionKey: 's4',
|
||||
dimensionFilters: { s4: 'foo' },
|
||||
};
|
||||
render(<VariableQueryEditor {...props} />);
|
||||
|
||||
await waitFor(() => {
|
||||
screen.getByLabelText('Log group prefix');
|
||||
screen.getByLabelText('Region');
|
||||
const querySelect = screen.queryByLabelText('Query type');
|
||||
expect(querySelect).toBeInTheDocument();
|
||||
expect(screen.queryByText('Dimension Values')).toBeInTheDocument();
|
||||
const regionSelect = screen.getByRole('combobox', { name: 'Region' });
|
||||
await waitFor(() =>
|
||||
select(regionSelect, 'b1', {
|
||||
container: document.body,
|
||||
})
|
||||
);
|
||||
|
||||
expect(ds.datasource.resources.getMetrics).toHaveBeenCalledWith({ namespace: 'z2', region: 'b1' });
|
||||
expect(ds.datasource.resources.getDimensionKeys).toHaveBeenCalledWith({ namespace: 'z2', region: 'b1' });
|
||||
expect(props.onChange).toHaveBeenCalledWith({
|
||||
...defaultQuery,
|
||||
refId: 'CloudWatchVariableQueryEditor-VariableQuery',
|
||||
queryType: VariableQueryType.DimensionValues,
|
||||
namespace: 'z2',
|
||||
region: 'b1',
|
||||
// metricName i3 exists in the new region and should not be removed
|
||||
metricName: 'i3',
|
||||
// dimensionKey s4 and valueDimension do not exist in the new region and should be removed
|
||||
dimensionKey: '',
|
||||
dimensionFilters: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('LogGroups queryType is selected', () => {
|
||||
it('should only render region and prefix', async () => {
|
||||
const props = defaultProps;
|
||||
props.query = {
|
||||
...defaultQuery,
|
||||
queryType: VariableQueryType.LogGroups,
|
||||
};
|
||||
render(<VariableQueryEditor {...props} />);
|
||||
|
||||
expect(screen.queryByLabelText('Namespace')).not.toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
screen.getByLabelText('Log group prefix');
|
||||
screen.getByLabelText('Region');
|
||||
});
|
||||
|
||||
expect(screen.queryByLabelText('Namespace')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('variable editor with awsDatasourcesNewFormStyling feature toggle enabled', () => {
|
||||
beforeAll(() => {
|
||||
config.featureToggles.awsDatasourcesNewFormStyling = false;
|
||||
});
|
||||
afterAll(() => {
|
||||
cleanupFeatureToggle();
|
||||
});
|
||||
run();
|
||||
describe('variable editor with awsDatasourcesNewFormStyling feature toggle enabled', () => {
|
||||
beforeAll(() => {
|
||||
config.featureToggles.awsDatasourcesNewFormStyling = true;
|
||||
});
|
||||
afterAll(() => {
|
||||
cleanupFeatureToggle();
|
||||
});
|
||||
run();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { QueryEditorProps, SelectableValue } from '@grafana/data';
|
||||
import { EditorField } from '@grafana/experimental';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { InlineField } from '@grafana/ui';
|
||||
|
||||
@ -44,6 +45,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const keysForDimensionFilter = useDimensionKeys(datasource, { region, namespace, metricName, dimensionFilters });
|
||||
const accountState = useAccountOptions(datasource.resources, query.region);
|
||||
|
||||
const newFormStylingEnabled = config.featureToggles.awsDatasourcesNewFormStyling;
|
||||
const onRegionChange = async (region: string) => {
|
||||
const validatedQuery = await sanitizeQuery({
|
||||
...parsedQuery,
|
||||
@ -122,6 +124,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
}
|
||||
label="Query type"
|
||||
inputId={`variable-query-type-${query.refId}`}
|
||||
newFormStylingEnabled={newFormStylingEnabled}
|
||||
/>
|
||||
{hasRegionField && (
|
||||
<VariableQueryField
|
||||
@ -131,6 +134,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
label="Region"
|
||||
isLoading={regionIsLoading}
|
||||
inputId={`variable-query-region-${query.refId}`}
|
||||
newFormStylingEnabled={newFormStylingEnabled}
|
||||
/>
|
||||
)}
|
||||
{hasAccountIDField &&
|
||||
@ -143,6 +147,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
onChange={(accountId?: string) => onQueryChange({ ...parsedQuery, accountId })}
|
||||
options={[ALL_ACCOUNTS_OPTION, ...accountState?.value]}
|
||||
allowCustomValue={false}
|
||||
newFormStylingEnabled={newFormStylingEnabled}
|
||||
/>
|
||||
)}
|
||||
{hasNamespaceField && (
|
||||
@ -153,6 +158,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
label="Namespace"
|
||||
inputId={`variable-query-namespace-${query.refId}`}
|
||||
allowCustomValue
|
||||
newFormStylingEnabled={newFormStylingEnabled}
|
||||
/>
|
||||
)}
|
||||
{parsedQuery.queryType === VariableQueryType.DimensionValues && (
|
||||
@ -164,6 +170,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
label="Metric"
|
||||
inputId={`variable-query-metric-${query.refId}`}
|
||||
allowCustomValue
|
||||
newFormStylingEnabled={newFormStylingEnabled}
|
||||
/>
|
||||
<VariableQueryField
|
||||
value={dimensionKey || null}
|
||||
@ -172,18 +179,38 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
label="Dimension key"
|
||||
inputId={`variable-query-dimension-key-${query.refId}`}
|
||||
allowCustomValue
|
||||
newFormStylingEnabled={newFormStylingEnabled}
|
||||
/>
|
||||
<InlineField label="Dimensions" labelWidth={20} shrink tooltip="Dimensions to filter the returned values on">
|
||||
<Dimensions
|
||||
metricStat={{ ...parsedQuery, dimensions: parsedQuery.dimensionFilters }}
|
||||
onChange={(dimensions) => {
|
||||
onChange({ ...parsedQuery, dimensionFilters: dimensions });
|
||||
}}
|
||||
dimensionKeys={keysForDimensionFilter}
|
||||
disableExpressions={true}
|
||||
datasource={datasource}
|
||||
/>
|
||||
</InlineField>
|
||||
{newFormStylingEnabled ? (
|
||||
<EditorField label="Dimensions" className="width-30" tooltip="Dimensions to filter the returned values on">
|
||||
<Dimensions
|
||||
metricStat={{ ...parsedQuery, dimensions: parsedQuery.dimensionFilters }}
|
||||
onChange={(dimensions) => {
|
||||
onChange({ ...parsedQuery, dimensionFilters: dimensions });
|
||||
}}
|
||||
dimensionKeys={keysForDimensionFilter}
|
||||
disableExpressions={true}
|
||||
datasource={datasource}
|
||||
/>
|
||||
</EditorField>
|
||||
) : (
|
||||
<InlineField
|
||||
label="Dimensions"
|
||||
labelWidth={20}
|
||||
shrink
|
||||
tooltip="Dimensions to filter the returned values on"
|
||||
>
|
||||
<Dimensions
|
||||
metricStat={{ ...parsedQuery, dimensions: parsedQuery.dimensionFilters }}
|
||||
onChange={(dimensions) => {
|
||||
onChange({ ...parsedQuery, dimensionFilters: dimensions });
|
||||
}}
|
||||
dimensionKeys={keysForDimensionFilter}
|
||||
disableExpressions={true}
|
||||
datasource={datasource}
|
||||
/>
|
||||
</InlineField>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{parsedQuery.queryType === VariableQueryType.EBSVolumeIDs && (
|
||||
@ -192,6 +219,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
placeholder="i-XXXXXXXXXXXXXXXXX"
|
||||
onBlur={(value: string) => onQueryChange({ ...parsedQuery, instanceID: value })}
|
||||
label="Instance ID"
|
||||
newFormStylingEnabled={newFormStylingEnabled}
|
||||
/>
|
||||
)}
|
||||
{parsedQuery.queryType === VariableQueryType.EC2InstanceAttributes && (
|
||||
@ -201,6 +229,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
onBlur={(value: string) => onQueryChange({ ...parsedQuery, attributeName: value })}
|
||||
label="Attribute name"
|
||||
interactive={true}
|
||||
newFormStylingEnabled={newFormStylingEnabled}
|
||||
tooltip={
|
||||
<>
|
||||
{'Attribute or tag to query on. Tags should be formatted "Tags.<name>". '}
|
||||
@ -214,31 +243,58 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<InlineField
|
||||
label="Filters"
|
||||
labelWidth={20}
|
||||
shrink
|
||||
tooltip={
|
||||
<>
|
||||
<a
|
||||
href="https://grafana.com/docs/grafana/latest/datasources/aws-cloudwatch/template-queries-cloudwatch/#selecting-attributes"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Pre-defined ec2:DescribeInstances filters/tags
|
||||
</a>
|
||||
{' and the values to filter on. Tags should be formatted tag:<name>.'}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<MultiFilter
|
||||
filters={parsedQuery.ec2Filters}
|
||||
onChange={(filters) => {
|
||||
onChange({ ...parsedQuery, ec2Filters: filters });
|
||||
}}
|
||||
keyPlaceholder="filter/tag"
|
||||
/>
|
||||
</InlineField>
|
||||
{newFormStylingEnabled ? (
|
||||
<EditorField
|
||||
label="Filters"
|
||||
tooltipInteractive
|
||||
tooltip={
|
||||
<>
|
||||
<a
|
||||
href="https://grafana.com/docs/grafana/latest/datasources/aws-cloudwatch/template-queries-cloudwatch/#selecting-attributes"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Pre-defined ec2:DescribeInstances filters/tags
|
||||
</a>
|
||||
{' and the values to filter on. Tags should be formatted tag:<name>.'}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<MultiFilter
|
||||
filters={parsedQuery.ec2Filters}
|
||||
onChange={(filters) => {
|
||||
onChange({ ...parsedQuery, ec2Filters: filters });
|
||||
}}
|
||||
keyPlaceholder="filter/tag"
|
||||
/>
|
||||
</EditorField>
|
||||
) : (
|
||||
<InlineField
|
||||
label="Filters"
|
||||
labelWidth={20}
|
||||
shrink
|
||||
tooltip={
|
||||
<>
|
||||
<a
|
||||
href="https://grafana.com/docs/grafana/latest/datasources/aws-cloudwatch/template-queries-cloudwatch/#selecting-attributes"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Pre-defined ec2:DescribeInstances filters/tags
|
||||
</a>
|
||||
{' and the values to filter on. Tags should be formatted tag:<name>.'}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<MultiFilter
|
||||
filters={parsedQuery.ec2Filters}
|
||||
onChange={(filters) => {
|
||||
onChange({ ...parsedQuery, ec2Filters: filters });
|
||||
}}
|
||||
keyPlaceholder="filter/tag"
|
||||
/>
|
||||
</InlineField>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{parsedQuery.queryType === VariableQueryType.ResourceArns && (
|
||||
@ -247,16 +303,29 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
value={parsedQuery.resourceType}
|
||||
onBlur={(value: string) => onQueryChange({ ...parsedQuery, resourceType: value })}
|
||||
label="Resource type"
|
||||
newFormStylingEnabled={newFormStylingEnabled}
|
||||
/>
|
||||
<InlineField label="Tags" shrink labelWidth={20} tooltip="Tags to filter the returned values on.">
|
||||
<MultiFilter
|
||||
filters={parsedQuery.tags}
|
||||
onChange={(filters) => {
|
||||
onChange({ ...parsedQuery, tags: filters });
|
||||
}}
|
||||
keyPlaceholder="tag"
|
||||
/>
|
||||
</InlineField>
|
||||
{newFormStylingEnabled ? (
|
||||
<EditorField label="Tags" tooltip="Tags to filter the returned values on.">
|
||||
<MultiFilter
|
||||
filters={parsedQuery.tags}
|
||||
onChange={(filters) => {
|
||||
onChange({ ...parsedQuery, tags: filters });
|
||||
}}
|
||||
keyPlaceholder="tag"
|
||||
/>
|
||||
</EditorField>
|
||||
) : (
|
||||
<InlineField label="Tags" shrink labelWidth={20} tooltip="Tags to filter the returned values on.">
|
||||
<MultiFilter
|
||||
filters={parsedQuery.tags}
|
||||
onChange={(filters) => {
|
||||
onChange({ ...parsedQuery, tags: filters });
|
||||
}}
|
||||
keyPlaceholder="tag"
|
||||
/>
|
||||
</InlineField>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{parsedQuery.queryType === VariableQueryType.LogGroups && (
|
||||
@ -264,6 +333,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
value={query.logGroupPrefix ?? ''}
|
||||
onBlur={(value: string) => onQueryChange({ ...parsedQuery, logGroupPrefix: value })}
|
||||
label="Log group prefix"
|
||||
newFormStylingEnabled={newFormStylingEnabled}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@ -1,9 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField } from '@grafana/experimental';
|
||||
import { InlineField, Select } from '@grafana/ui';
|
||||
|
||||
import { VariableQueryType } from '../../types';
|
||||
import { removeMarginBottom } from '../styles';
|
||||
|
||||
const LABEL_WIDTH = 20;
|
||||
|
||||
@ -15,6 +17,7 @@ interface VariableQueryFieldProps<T> {
|
||||
inputId?: string;
|
||||
allowCustomValue?: boolean;
|
||||
isLoading?: boolean;
|
||||
newFormStylingEnabled?: boolean;
|
||||
}
|
||||
|
||||
export const VariableQueryField = <T extends string | VariableQueryType>({
|
||||
@ -25,8 +28,21 @@ export const VariableQueryField = <T extends string | VariableQueryType>({
|
||||
allowCustomValue = false,
|
||||
isLoading = false,
|
||||
inputId = label,
|
||||
newFormStylingEnabled,
|
||||
}: VariableQueryFieldProps<T>) => {
|
||||
return (
|
||||
return newFormStylingEnabled ? (
|
||||
<EditorField label={label} htmlFor={inputId} className={removeMarginBottom}>
|
||||
<Select
|
||||
aria-label={label}
|
||||
allowCustomValue={allowCustomValue}
|
||||
value={value}
|
||||
onChange={({ value }) => onChange(value!)}
|
||||
options={options}
|
||||
isLoading={isLoading}
|
||||
inputId={inputId}
|
||||
/>
|
||||
</EditorField>
|
||||
) : (
|
||||
<InlineField label={label} labelWidth={LABEL_WIDTH} htmlFor={inputId}>
|
||||
<Select
|
||||
aria-label={label}
|
||||
|
@ -1,7 +1,10 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { EditorField } from '@grafana/experimental';
|
||||
import { InlineField, Input, PopoverContent } from '@grafana/ui';
|
||||
|
||||
import { removeMarginBottom } from '../styles';
|
||||
|
||||
const LABEL_WIDTH = 20;
|
||||
|
||||
interface Props {
|
||||
@ -11,11 +14,30 @@ interface Props {
|
||||
placeholder?: string;
|
||||
tooltip?: PopoverContent;
|
||||
interactive?: boolean;
|
||||
newFormStylingEnabled?: boolean;
|
||||
}
|
||||
|
||||
export const VariableTextField = ({ interactive, label, onBlur, placeholder, value, tooltip }: Props) => {
|
||||
export const VariableTextField = ({
|
||||
interactive,
|
||||
label,
|
||||
onBlur,
|
||||
placeholder,
|
||||
value,
|
||||
tooltip,
|
||||
newFormStylingEnabled,
|
||||
}: Props) => {
|
||||
const [localValue, setLocalValue] = useState(value);
|
||||
return (
|
||||
return newFormStylingEnabled ? (
|
||||
<EditorField label={label} tooltip={tooltip} tooltipInteractive={interactive} className={removeMarginBottom}>
|
||||
<Input
|
||||
aria-label={label}
|
||||
placeholder={placeholder}
|
||||
value={localValue}
|
||||
onChange={(e) => setLocalValue(e.currentTarget.value)}
|
||||
onBlur={() => onBlur(localValue)}
|
||||
/>
|
||||
</EditorField>
|
||||
) : (
|
||||
<InlineField interactive={interactive} label={label} labelWidth={LABEL_WIDTH} tooltip={tooltip} grow>
|
||||
<Input
|
||||
aria-label={label}
|
||||
|
@ -20,13 +20,22 @@ type Props = {
|
||||
logGroups?: LogGroup[];
|
||||
region: string;
|
||||
maxNoOfVisibleLogGroups?: number;
|
||||
newFormStylingEnabled?: boolean;
|
||||
onBeforeOpen?: () => void;
|
||||
};
|
||||
|
||||
const rowGap = css`
|
||||
gap: 3px;
|
||||
`;
|
||||
const rowGap = css({
|
||||
gap: 3,
|
||||
});
|
||||
|
||||
const logGroupNewStyles = css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
marginTop: 8,
|
||||
'& div:first-child': {
|
||||
marginBottom: 8,
|
||||
},
|
||||
});
|
||||
// used in Config Editor and in Log Query Editor
|
||||
export const LogGroupsField = ({
|
||||
datasource,
|
||||
@ -35,6 +44,7 @@ export const LogGroupsField = ({
|
||||
logGroups,
|
||||
region,
|
||||
maxNoOfVisibleLogGroups,
|
||||
newFormStylingEnabled,
|
||||
onBeforeOpen,
|
||||
}: Props) => {
|
||||
const accountState = useAccountOptions(datasource?.resources, region);
|
||||
@ -74,7 +84,7 @@ export const LogGroupsField = ({
|
||||
}, [datasource, legacyLogGroupNames, logGroups, onChange, region, loadingLogGroupsStarted]);
|
||||
|
||||
return (
|
||||
<div className={`gf-form gf-form--grow flex-grow-1 ${rowGap}`}>
|
||||
<div className={newFormStylingEnabled ? logGroupNewStyles : `gf-form gf-form--grow flex-grow-1 ${rowGap}`}>
|
||||
<LogGroupsSelector
|
||||
fetchLogGroups={async (params: Partial<DescribeLogGroupsRequest>) =>
|
||||
datasource?.resources.getLogGroups({ region: region, ...params }) ?? []
|
||||
@ -104,6 +114,7 @@ type WrapperProps = {
|
||||
region: string;
|
||||
maxNoOfVisibleLogGroups?: number;
|
||||
onBeforeOpen?: () => void;
|
||||
newFormStylingEnabled?: boolean;
|
||||
|
||||
// Legacy Props, can remove once we remove support for Legacy Log Group Selector
|
||||
legacyOnChange: (logGroups: string[]) => void;
|
||||
|
@ -96,5 +96,6 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
marginLeft: theme.spacing(0.5),
|
||||
}),
|
||||
});
|
||||
export const removeMarginBottom = css({ marginBottom: 8 });
|
||||
|
||||
export default getStyles;
|
||||
|
30
yarn.lock
30
yarn.lock
@ -2913,13 +2913,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/aws-sdk@npm:0.2.0":
|
||||
version: 0.2.0
|
||||
resolution: "@grafana/aws-sdk@npm:0.2.0"
|
||||
"@grafana/aws-sdk@npm:0.3.1":
|
||||
version: 0.3.1
|
||||
resolution: "@grafana/aws-sdk@npm:0.3.1"
|
||||
dependencies:
|
||||
"@grafana/async-query-data": "npm:0.1.4"
|
||||
"@grafana/experimental": "npm:1.1.0"
|
||||
checksum: 5f79f62c37dc5b0841a38c0b9bf3d9d687e8e28c40a583399a47f7b7370dd07b0f5fd4d26ede496a453051ae385ea20e86edfdef4c55c2dceb43ab4400de634b
|
||||
"@grafana/experimental": "npm:1.7.0"
|
||||
checksum: 89b42fa6351b78ce9760fb07ebfad37d45aa3327858ad4e88acc1699ccc9d6541ba0232ba8331d6b0a6b61a1d6b134e19ae5e91b2e90353a97958291c15bc9ef
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -3096,24 +3096,6 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/experimental@npm:1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "@grafana/experimental@npm:1.1.0"
|
||||
dependencies:
|
||||
"@types/uuid": "npm:^8.3.3"
|
||||
uuid: "npm:^8.3.2"
|
||||
peerDependencies:
|
||||
"@emotion/css": 11.1.3
|
||||
"@grafana/data": ^9.2.0
|
||||
"@grafana/runtime": ^9.2.0
|
||||
"@grafana/ui": ^9.2.0
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2
|
||||
react-select: ^5.2.1
|
||||
checksum: 5e6b7ddf1a33d84a8bae11b241aaf6bc3a7c672457a91e92745189a17cb60cc170260e584d281ec3be72cb440766fefdcf85a73889d384b975c061872a8a910a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/experimental@npm:1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "@grafana/experimental@npm:1.7.0"
|
||||
@ -17311,7 +17293,7 @@ __metadata:
|
||||
"@fingerprintjs/fingerprintjs": "npm:^3.4.2"
|
||||
"@glideapps/glide-data-grid": "npm:^5.2.1"
|
||||
"@grafana-plugins/grafana-testdata-datasource": "workspace:*"
|
||||
"@grafana/aws-sdk": "npm:0.2.0"
|
||||
"@grafana/aws-sdk": "npm:0.3.1"
|
||||
"@grafana/data": "workspace:*"
|
||||
"@grafana/e2e-selectors": "workspace:*"
|
||||
"@grafana/eslint-config": "npm:6.0.1"
|
||||
|
Loading…
Reference in New Issue
Block a user