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": [
|
"public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LegacyLogGroupNamesSelection.tsx:5381": [
|
||||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
[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": [
|
"public/app/plugins/datasource/cloudwatch/datasource.ts:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||||
],
|
],
|
||||||
|
@ -242,7 +242,7 @@
|
|||||||
"@fingerprintjs/fingerprintjs": "^3.4.2",
|
"@fingerprintjs/fingerprintjs": "^3.4.2",
|
||||||
"@glideapps/glide-data-grid": "^5.2.1",
|
"@glideapps/glide-data-grid": "^5.2.1",
|
||||||
"@grafana-plugins/grafana-testdata-datasource": "workspace:*",
|
"@grafana-plugins/grafana-testdata-datasource": "workspace:*",
|
||||||
"@grafana/aws-sdk": "0.2.0",
|
"@grafana/aws-sdk": "0.3.1",
|
||||||
"@grafana/data": "workspace:*",
|
"@grafana/data": "workspace:*",
|
||||||
"@grafana/e2e-selectors": "workspace:*",
|
"@grafana/e2e-selectors": "workspace:*",
|
||||||
"@grafana/experimental": "1.7.4",
|
"@grafana/experimental": "1.7.4",
|
||||||
|
@ -5,6 +5,7 @@ import { Provider } from 'react-redux';
|
|||||||
|
|
||||||
import { AwsAuthType } from '@grafana/aws-sdk';
|
import { AwsAuthType } from '@grafana/aws-sdk';
|
||||||
import { PluginContextProvider, PluginMeta, PluginMetaInfo, PluginType } from '@grafana/data';
|
import { PluginContextProvider, PluginMeta, PluginMetaInfo, PluginType } from '@grafana/data';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
import { configureStore } from 'app/store/configureStore';
|
import { configureStore } from 'app/store/configureStore';
|
||||||
|
|
||||||
import { CloudWatchSettings, setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource';
|
import { CloudWatchSettings, setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource';
|
||||||
@ -128,178 +129,205 @@ describe('Render', () => {
|
|||||||
datasource.getVariables = jest.fn().mockReturnValue([]);
|
datasource.getVariables = jest.fn().mockReturnValue([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('it should disable access key id field when the datasource has been previously configured', async () => {
|
const originalFormFeatureToggleValue = config.featureToggles.awsDatasourcesNewFormStyling;
|
||||||
setup({
|
|
||||||
secureJsonFields: {
|
|
||||||
secretKey: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await waitFor(async () => expect(screen.getByPlaceholderText('Configured')).toBeDisabled());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show credentials profile name field', async () => {
|
const cleanupFeatureToggle = () => {
|
||||||
setup({
|
config.featureToggles.awsDatasourcesNewFormStyling = originalFormFeatureToggleValue;
|
||||||
jsonData: {
|
};
|
||||||
authType: AwsAuthType.Credentials,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await waitFor(async () => expect(screen.getByLabelText('Credentials Profile Name')).toBeInTheDocument());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show access key and secret access key fields when the datasource has not been configured before', async () => {
|
function run() {
|
||||||
setup({
|
it('it should disable access key id field when the datasource has been previously configured', async () => {
|
||||||
jsonData: {
|
setup({
|
||||||
authType: AwsAuthType.Keys,
|
secureJsonFields: {
|
||||||
},
|
secretKey: true,
|
||||||
});
|
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
};
|
await waitFor(async () => expect(screen.getByPlaceholderText('Configured')).toBeDisabled());
|
||||||
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 () => {
|
it('should show credentials profile name field', async () => {
|
||||||
const newProps = {
|
setup({
|
||||||
...props,
|
jsonData: {
|
||||||
options: {
|
authType: AwsAuthType.Credentials,
|
||||||
...props.options,
|
},
|
||||||
version: 1,
|
});
|
||||||
},
|
await waitFor(async () => expect(screen.getByText('Credentials Profile Name')).toBeInTheDocument());
|
||||||
};
|
});
|
||||||
const meta: PluginMeta = {
|
|
||||||
...newProps.options,
|
it('should show access key and secret access key fields when the datasource has not been configured before', async () => {
|
||||||
id: 'cloudwatch',
|
setup({
|
||||||
type: PluginType.datasource,
|
jsonData: {
|
||||||
info: {} as PluginMetaInfo,
|
authType: AwsAuthType.Keys,
|
||||||
module: '',
|
},
|
||||||
baseUrl: '',
|
});
|
||||||
};
|
await waitFor(async () => {
|
||||||
const { rerender } = render(
|
expect(screen.getByLabelText('Access Key ID')).toBeInTheDocument();
|
||||||
<PluginContextProvider meta={meta}>
|
expect(screen.getByLabelText('Secret Access Key')).toBeInTheDocument();
|
||||||
<ConfigEditor {...newProps} />
|
});
|
||||||
</PluginContextProvider>
|
});
|
||||||
);
|
|
||||||
await waitFor(() => expect(screen.getByText('Select log groups')).toBeInTheDocument());
|
it('should show arn role field', async () => {
|
||||||
const rerenderProps = {
|
setup({
|
||||||
...newProps,
|
jsonData: {
|
||||||
options: {
|
authType: AwsAuthType.ARN,
|
||||||
...newProps.options,
|
},
|
||||||
|
});
|
||||||
|
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,
|
version: 2,
|
||||||
},
|
jsonData: {
|
||||||
};
|
logGroups: [
|
||||||
rerender(
|
{ arn: 'arn:aws:logs:us-east-2:123456789012:log-group:logGroup-foo:*', name: 'logGroup-foo' },
|
||||||
<PluginContextProvider meta={meta}>
|
{ arn: 'arn:aws:logs:us-east-2:123456789012:log-group:logGroup-bar:*', name: 'logGroup-bar' },
|
||||||
<ConfigEditor {...rerenderProps} />
|
{ arn: 'arn:aws:logs:us-east-2:123456789012:log-group:logGroup-baz:*', name: 'logGroup-baz' },
|
||||||
</PluginContextProvider>
|
],
|
||||||
);
|
},
|
||||||
await userEvent.click(screen.getByText('Select log groups'));
|
});
|
||||||
await waitFor(() => expect(screen.getByText('Log group name prefix')).toBeInTheDocument());
|
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,
|
DataSourceTestSucceeded,
|
||||||
DataSourceTestFailed,
|
DataSourceTestFailed,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
import { ConfigSection } from '@grafana/experimental';
|
||||||
import { getAppEvents, usePluginInteractionReporter } from '@grafana/runtime';
|
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 { notifyApp } from 'app/core/actions';
|
||||||
import { config } from 'app/core/config';
|
import { config } from 'app/core/config';
|
||||||
import { createWarningNotification } from 'app/core/copy/appNotification';
|
import { createWarningNotification } from 'app/core/copy/appNotification';
|
||||||
@ -23,6 +24,7 @@ import { SelectableResourceValue } from '../../resources/types';
|
|||||||
import { CloudWatchJsonData, CloudWatchSecureJsonData } from '../../types';
|
import { CloudWatchJsonData, CloudWatchSecureJsonData } from '../../types';
|
||||||
import { LogGroupsFieldWrapper } from '../shared/LogGroups/LogGroupsField';
|
import { LogGroupsFieldWrapper } from '../shared/LogGroups/LogGroupsField';
|
||||||
|
|
||||||
|
import { SecureSocksProxySettingsNewStyling } from './SecureSocksProxySettingsNewStyling';
|
||||||
import { XrayLinkConfig } from './XrayLinkConfig';
|
import { XrayLinkConfig } from './XrayLinkConfig';
|
||||||
|
|
||||||
export type Props = DataSourcePluginOptionsEditorProps<CloudWatchJsonData, CloudWatchSecureJsonData>;
|
export type Props = DataSourcePluginOptionsEditorProps<CloudWatchJsonData, CloudWatchSecureJsonData>;
|
||||||
@ -39,6 +41,7 @@ export const ConfigEditor = (props: Props) => {
|
|||||||
const [logGroupFieldState, setLogGroupFieldState] = useState<LogGroupFieldState>({
|
const [logGroupFieldState, setLogGroupFieldState] = useState<LogGroupFieldState>({
|
||||||
invalid: false,
|
invalid: false,
|
||||||
});
|
});
|
||||||
|
const newFormStylingEnabled = config.featureToggles.awsDatasourcesNewFormStyling;
|
||||||
useEffect(() => setLogGroupFieldState({ invalid: false }), [props.options]);
|
useEffect(() => setLogGroupFieldState({ invalid: false }), [props.options]);
|
||||||
const report = usePluginInteractionReporter();
|
const report = usePluginInteractionReporter();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -67,7 +70,103 @@ export const ConfigEditor = (props: Props) => {
|
|||||||
}
|
}
|
||||||
}, [datasource, externalId]);
|
}, [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
|
<ConnectionConfig
|
||||||
{...props}
|
{...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 React from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2, DataSourceInstanceSettings } from '@grafana/data';
|
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 { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
|
||||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
|
|
||||||
@ -16,16 +17,39 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
interface Props {
|
interface Props {
|
||||||
datasourceUid?: string;
|
datasourceUid?: string;
|
||||||
onChange: (uid: string) => void;
|
onChange: (uid: string) => void;
|
||||||
|
newFormStyling?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const xRayDsId = 'grafana-x-ray-datasource';
|
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 hasXrayDatasource = Boolean(getDatasourceSrv().getList({ pluginId: xRayDsId }).length);
|
||||||
|
|
||||||
const styles = useStyles2(getStyles);
|
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>
|
<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 React from 'react';
|
||||||
import { select } from 'react-select-event';
|
import { select } from 'react-select-event';
|
||||||
|
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
|
||||||
import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource';
|
import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource';
|
||||||
import { GetDimensionKeysRequest } from '../../resources/types';
|
import { GetDimensionKeysRequest } from '../../resources/types';
|
||||||
import { VariableQueryType } from '../../types';
|
import { VariableQueryType } from '../../types';
|
||||||
@ -23,6 +25,12 @@ const defaultQuery = {
|
|||||||
|
|
||||||
const ds = setupMockedDataSource();
|
const ds = setupMockedDataSource();
|
||||||
|
|
||||||
|
const originalFormFeatureToggleValue = config.featureToggles.awsDatasourcesNewFormStyling;
|
||||||
|
|
||||||
|
const cleanupFeatureToggle = () => {
|
||||||
|
config.featureToggles.awsDatasourcesNewFormStyling = originalFormFeatureToggleValue;
|
||||||
|
};
|
||||||
|
|
||||||
ds.datasource.resources.getRegions = jest.fn().mockResolvedValue([
|
ds.datasource.resources.getRegions = jest.fn().mockResolvedValue([
|
||||||
{ label: 'a1', value: 'a1' },
|
{ label: 'a1', value: 'a1' },
|
||||||
{ label: 'b1', value: 'b1' },
|
{ label: 'b1', value: 'b1' },
|
||||||
@ -75,195 +83,216 @@ describe('VariableEditor', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
onChange.mockClear();
|
onChange.mockClear();
|
||||||
});
|
});
|
||||||
describe('and a new variable is created', () => {
|
function run() {
|
||||||
it('should trigger a query using the first query type in the array', async () => {
|
describe('and a new variable is created', () => {
|
||||||
const props = defaultProps;
|
it('should trigger a query using the first query type in the array', async () => {
|
||||||
props.query = defaultQuery;
|
const props = defaultProps;
|
||||||
render(<VariableQueryEditor {...props} />);
|
props.query = defaultQuery;
|
||||||
|
|
||||||
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 () => {
|
|
||||||
render(<VariableQueryEditor {...props} />);
|
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
|
await waitFor(() => {
|
||||||
const keySelect = screen.getByRole('combobox', { name: 'Dimensions filter key' });
|
const querySelect = screen.queryByRole('combobox', { name: 'Query type' });
|
||||||
// confirms getDimensionKeys was called with filter and that the element uses keysForDimensionFilter
|
expect(querySelect).toBeInTheDocument();
|
||||||
await select(keySelect, 'v4', {
|
expect(screen.queryByText('Regions')).toBeInTheDocument();
|
||||||
container: document.body,
|
// Should not render any fields besides Query Type
|
||||||
});
|
const regionSelect = screen.queryByRole('combobox', { name: 'Region' });
|
||||||
expect(ds.datasource.resources.getDimensionKeys).toHaveBeenCalledWith({
|
expect(regionSelect).not.toBeInTheDocument();
|
||||||
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' },
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
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(() => {
|
describe('and an existing variable is edited', () => {
|
||||||
expect(screen.getByDisplayValue('Tags.blah')).toBeInTheDocument();
|
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');
|
// change filter key
|
||||||
expect(filterItem).toBeInTheDocument();
|
const keySelect = screen.getByRole('combobox', { name: 'Dimensions filter key' });
|
||||||
expect(within(filterItem).getByDisplayValue('foo, bar')).toBeInTheDocument();
|
// confirms getDimensionKeys was called with filter and that the element uses keysForDimensionFilter
|
||||||
|
await select(keySelect, 'v4', {
|
||||||
// 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', {
|
|
||||||
container: document.body,
|
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' });
|
// set filter value
|
||||||
expect(ds.datasource.resources.getDimensionKeys).toHaveBeenCalledWith({ namespace: 'z2', region: 'b1' });
|
const valueSelect = screen.getByRole('combobox', { name: 'Dimensions filter value' });
|
||||||
expect(props.onChange).toHaveBeenCalledWith({
|
await select(valueSelect, 'bar', {
|
||||||
...defaultQuery,
|
container: document.body,
|
||||||
refId: 'CloudWatchVariableQueryEditor-VariableQuery',
|
});
|
||||||
queryType: VariableQueryType.DimensionValues,
|
expect(onChange).toHaveBeenCalledWith({
|
||||||
namespace: 'z2',
|
...defaultQuery,
|
||||||
region: 'b1',
|
queryType: VariableQueryType.DimensionValues,
|
||||||
// metricName i3 exists in the new region and should not be removed
|
namespace: 'z2',
|
||||||
metricName: 'i3',
|
region: 'a1',
|
||||||
// dimensionKey s4 and valueDimension do not exist in the new region and should be removed
|
metricName: 'i3',
|
||||||
dimensionKey: '',
|
dimensionKey: 's4',
|
||||||
dimensionFilters: {},
|
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('and a different region is selected', () => {
|
||||||
describe('LogGroups queryType is selected', () => {
|
it('should clear invalid fields', async () => {
|
||||||
it('should only render region and prefix', async () => {
|
const props = defaultProps;
|
||||||
const props = defaultProps;
|
props.query = {
|
||||||
props.query = {
|
...defaultQuery,
|
||||||
...defaultQuery,
|
queryType: VariableQueryType.DimensionValues,
|
||||||
queryType: VariableQueryType.LogGroups,
|
namespace: 'z2',
|
||||||
};
|
region: 'a1',
|
||||||
render(<VariableQueryEditor {...props} />);
|
metricName: 'i3',
|
||||||
|
dimensionKey: 's4',
|
||||||
|
dimensionFilters: { s4: 'foo' },
|
||||||
|
};
|
||||||
|
render(<VariableQueryEditor {...props} />);
|
||||||
|
|
||||||
await waitFor(() => {
|
const querySelect = screen.queryByLabelText('Query type');
|
||||||
screen.getByLabelText('Log group prefix');
|
expect(querySelect).toBeInTheDocument();
|
||||||
screen.getByLabelText('Region');
|
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 React from 'react';
|
||||||
|
|
||||||
import { QueryEditorProps, SelectableValue } from '@grafana/data';
|
import { QueryEditorProps, SelectableValue } from '@grafana/data';
|
||||||
|
import { EditorField } from '@grafana/experimental';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { InlineField } from '@grafana/ui';
|
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 keysForDimensionFilter = useDimensionKeys(datasource, { region, namespace, metricName, dimensionFilters });
|
||||||
const accountState = useAccountOptions(datasource.resources, query.region);
|
const accountState = useAccountOptions(datasource.resources, query.region);
|
||||||
|
|
||||||
|
const newFormStylingEnabled = config.featureToggles.awsDatasourcesNewFormStyling;
|
||||||
const onRegionChange = async (region: string) => {
|
const onRegionChange = async (region: string) => {
|
||||||
const validatedQuery = await sanitizeQuery({
|
const validatedQuery = await sanitizeQuery({
|
||||||
...parsedQuery,
|
...parsedQuery,
|
||||||
@ -122,6 +124,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
}
|
}
|
||||||
label="Query type"
|
label="Query type"
|
||||||
inputId={`variable-query-type-${query.refId}`}
|
inputId={`variable-query-type-${query.refId}`}
|
||||||
|
newFormStylingEnabled={newFormStylingEnabled}
|
||||||
/>
|
/>
|
||||||
{hasRegionField && (
|
{hasRegionField && (
|
||||||
<VariableQueryField
|
<VariableQueryField
|
||||||
@ -131,6 +134,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
label="Region"
|
label="Region"
|
||||||
isLoading={regionIsLoading}
|
isLoading={regionIsLoading}
|
||||||
inputId={`variable-query-region-${query.refId}`}
|
inputId={`variable-query-region-${query.refId}`}
|
||||||
|
newFormStylingEnabled={newFormStylingEnabled}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasAccountIDField &&
|
{hasAccountIDField &&
|
||||||
@ -143,6 +147,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
onChange={(accountId?: string) => onQueryChange({ ...parsedQuery, accountId })}
|
onChange={(accountId?: string) => onQueryChange({ ...parsedQuery, accountId })}
|
||||||
options={[ALL_ACCOUNTS_OPTION, ...accountState?.value]}
|
options={[ALL_ACCOUNTS_OPTION, ...accountState?.value]}
|
||||||
allowCustomValue={false}
|
allowCustomValue={false}
|
||||||
|
newFormStylingEnabled={newFormStylingEnabled}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasNamespaceField && (
|
{hasNamespaceField && (
|
||||||
@ -153,6 +158,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
label="Namespace"
|
label="Namespace"
|
||||||
inputId={`variable-query-namespace-${query.refId}`}
|
inputId={`variable-query-namespace-${query.refId}`}
|
||||||
allowCustomValue
|
allowCustomValue
|
||||||
|
newFormStylingEnabled={newFormStylingEnabled}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{parsedQuery.queryType === VariableQueryType.DimensionValues && (
|
{parsedQuery.queryType === VariableQueryType.DimensionValues && (
|
||||||
@ -164,6 +170,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
label="Metric"
|
label="Metric"
|
||||||
inputId={`variable-query-metric-${query.refId}`}
|
inputId={`variable-query-metric-${query.refId}`}
|
||||||
allowCustomValue
|
allowCustomValue
|
||||||
|
newFormStylingEnabled={newFormStylingEnabled}
|
||||||
/>
|
/>
|
||||||
<VariableQueryField
|
<VariableQueryField
|
||||||
value={dimensionKey || null}
|
value={dimensionKey || null}
|
||||||
@ -172,18 +179,38 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
label="Dimension key"
|
label="Dimension key"
|
||||||
inputId={`variable-query-dimension-key-${query.refId}`}
|
inputId={`variable-query-dimension-key-${query.refId}`}
|
||||||
allowCustomValue
|
allowCustomValue
|
||||||
|
newFormStylingEnabled={newFormStylingEnabled}
|
||||||
/>
|
/>
|
||||||
<InlineField label="Dimensions" labelWidth={20} shrink tooltip="Dimensions to filter the returned values on">
|
{newFormStylingEnabled ? (
|
||||||
<Dimensions
|
<EditorField label="Dimensions" className="width-30" tooltip="Dimensions to filter the returned values on">
|
||||||
metricStat={{ ...parsedQuery, dimensions: parsedQuery.dimensionFilters }}
|
<Dimensions
|
||||||
onChange={(dimensions) => {
|
metricStat={{ ...parsedQuery, dimensions: parsedQuery.dimensionFilters }}
|
||||||
onChange({ ...parsedQuery, dimensionFilters: dimensions });
|
onChange={(dimensions) => {
|
||||||
}}
|
onChange({ ...parsedQuery, dimensionFilters: dimensions });
|
||||||
dimensionKeys={keysForDimensionFilter}
|
}}
|
||||||
disableExpressions={true}
|
dimensionKeys={keysForDimensionFilter}
|
||||||
datasource={datasource}
|
disableExpressions={true}
|
||||||
/>
|
datasource={datasource}
|
||||||
</InlineField>
|
/>
|
||||||
|
</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 && (
|
{parsedQuery.queryType === VariableQueryType.EBSVolumeIDs && (
|
||||||
@ -192,6 +219,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
placeholder="i-XXXXXXXXXXXXXXXXX"
|
placeholder="i-XXXXXXXXXXXXXXXXX"
|
||||||
onBlur={(value: string) => onQueryChange({ ...parsedQuery, instanceID: value })}
|
onBlur={(value: string) => onQueryChange({ ...parsedQuery, instanceID: value })}
|
||||||
label="Instance ID"
|
label="Instance ID"
|
||||||
|
newFormStylingEnabled={newFormStylingEnabled}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{parsedQuery.queryType === VariableQueryType.EC2InstanceAttributes && (
|
{parsedQuery.queryType === VariableQueryType.EC2InstanceAttributes && (
|
||||||
@ -201,6 +229,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
onBlur={(value: string) => onQueryChange({ ...parsedQuery, attributeName: value })}
|
onBlur={(value: string) => onQueryChange({ ...parsedQuery, attributeName: value })}
|
||||||
label="Attribute name"
|
label="Attribute name"
|
||||||
interactive={true}
|
interactive={true}
|
||||||
|
newFormStylingEnabled={newFormStylingEnabled}
|
||||||
tooltip={
|
tooltip={
|
||||||
<>
|
<>
|
||||||
{'Attribute or tag to query on. Tags should be formatted "Tags.<name>". '}
|
{'Attribute or tag to query on. Tags should be formatted "Tags.<name>". '}
|
||||||
@ -214,31 +243,58 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<InlineField
|
{newFormStylingEnabled ? (
|
||||||
label="Filters"
|
<EditorField
|
||||||
labelWidth={20}
|
label="Filters"
|
||||||
shrink
|
tooltipInteractive
|
||||||
tooltip={
|
tooltip={
|
||||||
<>
|
<>
|
||||||
<a
|
<a
|
||||||
href="https://grafana.com/docs/grafana/latest/datasources/aws-cloudwatch/template-queries-cloudwatch/#selecting-attributes"
|
href="https://grafana.com/docs/grafana/latest/datasources/aws-cloudwatch/template-queries-cloudwatch/#selecting-attributes"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
Pre-defined ec2:DescribeInstances filters/tags
|
Pre-defined ec2:DescribeInstances filters/tags
|
||||||
</a>
|
</a>
|
||||||
{' and the values to filter on. Tags should be formatted tag:<name>.'}
|
{' and the values to filter on. Tags should be formatted tag:<name>.'}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<MultiFilter
|
<MultiFilter
|
||||||
filters={parsedQuery.ec2Filters}
|
filters={parsedQuery.ec2Filters}
|
||||||
onChange={(filters) => {
|
onChange={(filters) => {
|
||||||
onChange({ ...parsedQuery, ec2Filters: filters });
|
onChange({ ...parsedQuery, ec2Filters: filters });
|
||||||
}}
|
}}
|
||||||
keyPlaceholder="filter/tag"
|
keyPlaceholder="filter/tag"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</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 && (
|
{parsedQuery.queryType === VariableQueryType.ResourceArns && (
|
||||||
@ -247,16 +303,29 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
value={parsedQuery.resourceType}
|
value={parsedQuery.resourceType}
|
||||||
onBlur={(value: string) => onQueryChange({ ...parsedQuery, resourceType: value })}
|
onBlur={(value: string) => onQueryChange({ ...parsedQuery, resourceType: value })}
|
||||||
label="Resource type"
|
label="Resource type"
|
||||||
|
newFormStylingEnabled={newFormStylingEnabled}
|
||||||
/>
|
/>
|
||||||
<InlineField label="Tags" shrink labelWidth={20} tooltip="Tags to filter the returned values on.">
|
{newFormStylingEnabled ? (
|
||||||
<MultiFilter
|
<EditorField label="Tags" tooltip="Tags to filter the returned values on.">
|
||||||
filters={parsedQuery.tags}
|
<MultiFilter
|
||||||
onChange={(filters) => {
|
filters={parsedQuery.tags}
|
||||||
onChange({ ...parsedQuery, tags: filters });
|
onChange={(filters) => {
|
||||||
}}
|
onChange({ ...parsedQuery, tags: filters });
|
||||||
keyPlaceholder="tag"
|
}}
|
||||||
/>
|
keyPlaceholder="tag"
|
||||||
</InlineField>
|
/>
|
||||||
|
</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 && (
|
{parsedQuery.queryType === VariableQueryType.LogGroups && (
|
||||||
@ -264,6 +333,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
value={query.logGroupPrefix ?? ''}
|
value={query.logGroupPrefix ?? ''}
|
||||||
onBlur={(value: string) => onQueryChange({ ...parsedQuery, logGroupPrefix: value })}
|
onBlur={(value: string) => onQueryChange({ ...parsedQuery, logGroupPrefix: value })}
|
||||||
label="Log group prefix"
|
label="Log group prefix"
|
||||||
|
newFormStylingEnabled={newFormStylingEnabled}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
import { EditorField } from '@grafana/experimental';
|
||||||
import { InlineField, Select } from '@grafana/ui';
|
import { InlineField, Select } from '@grafana/ui';
|
||||||
|
|
||||||
import { VariableQueryType } from '../../types';
|
import { VariableQueryType } from '../../types';
|
||||||
|
import { removeMarginBottom } from '../styles';
|
||||||
|
|
||||||
const LABEL_WIDTH = 20;
|
const LABEL_WIDTH = 20;
|
||||||
|
|
||||||
@ -15,6 +17,7 @@ interface VariableQueryFieldProps<T> {
|
|||||||
inputId?: string;
|
inputId?: string;
|
||||||
allowCustomValue?: boolean;
|
allowCustomValue?: boolean;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
newFormStylingEnabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VariableQueryField = <T extends string | VariableQueryType>({
|
export const VariableQueryField = <T extends string | VariableQueryType>({
|
||||||
@ -25,8 +28,21 @@ export const VariableQueryField = <T extends string | VariableQueryType>({
|
|||||||
allowCustomValue = false,
|
allowCustomValue = false,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
inputId = label,
|
inputId = label,
|
||||||
|
newFormStylingEnabled,
|
||||||
}: VariableQueryFieldProps<T>) => {
|
}: 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}>
|
<InlineField label={label} labelWidth={LABEL_WIDTH} htmlFor={inputId}>
|
||||||
<Select
|
<Select
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import { EditorField } from '@grafana/experimental';
|
||||||
import { InlineField, Input, PopoverContent } from '@grafana/ui';
|
import { InlineField, Input, PopoverContent } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { removeMarginBottom } from '../styles';
|
||||||
|
|
||||||
const LABEL_WIDTH = 20;
|
const LABEL_WIDTH = 20;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -11,11 +14,30 @@ interface Props {
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
tooltip?: PopoverContent;
|
tooltip?: PopoverContent;
|
||||||
interactive?: boolean;
|
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);
|
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>
|
<InlineField interactive={interactive} label={label} labelWidth={LABEL_WIDTH} tooltip={tooltip} grow>
|
||||||
<Input
|
<Input
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
|
@ -20,13 +20,22 @@ type Props = {
|
|||||||
logGroups?: LogGroup[];
|
logGroups?: LogGroup[];
|
||||||
region: string;
|
region: string;
|
||||||
maxNoOfVisibleLogGroups?: number;
|
maxNoOfVisibleLogGroups?: number;
|
||||||
|
newFormStylingEnabled?: boolean;
|
||||||
onBeforeOpen?: () => void;
|
onBeforeOpen?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const rowGap = css`
|
const rowGap = css({
|
||||||
gap: 3px;
|
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
|
// used in Config Editor and in Log Query Editor
|
||||||
export const LogGroupsField = ({
|
export const LogGroupsField = ({
|
||||||
datasource,
|
datasource,
|
||||||
@ -35,6 +44,7 @@ export const LogGroupsField = ({
|
|||||||
logGroups,
|
logGroups,
|
||||||
region,
|
region,
|
||||||
maxNoOfVisibleLogGroups,
|
maxNoOfVisibleLogGroups,
|
||||||
|
newFormStylingEnabled,
|
||||||
onBeforeOpen,
|
onBeforeOpen,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const accountState = useAccountOptions(datasource?.resources, region);
|
const accountState = useAccountOptions(datasource?.resources, region);
|
||||||
@ -74,7 +84,7 @@ export const LogGroupsField = ({
|
|||||||
}, [datasource, legacyLogGroupNames, logGroups, onChange, region, loadingLogGroupsStarted]);
|
}, [datasource, legacyLogGroupNames, logGroups, onChange, region, loadingLogGroupsStarted]);
|
||||||
|
|
||||||
return (
|
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
|
<LogGroupsSelector
|
||||||
fetchLogGroups={async (params: Partial<DescribeLogGroupsRequest>) =>
|
fetchLogGroups={async (params: Partial<DescribeLogGroupsRequest>) =>
|
||||||
datasource?.resources.getLogGroups({ region: region, ...params }) ?? []
|
datasource?.resources.getLogGroups({ region: region, ...params }) ?? []
|
||||||
@ -104,6 +114,7 @@ type WrapperProps = {
|
|||||||
region: string;
|
region: string;
|
||||||
maxNoOfVisibleLogGroups?: number;
|
maxNoOfVisibleLogGroups?: number;
|
||||||
onBeforeOpen?: () => void;
|
onBeforeOpen?: () => void;
|
||||||
|
newFormStylingEnabled?: boolean;
|
||||||
|
|
||||||
// Legacy Props, can remove once we remove support for Legacy Log Group Selector
|
// Legacy Props, can remove once we remove support for Legacy Log Group Selector
|
||||||
legacyOnChange: (logGroups: string[]) => void;
|
legacyOnChange: (logGroups: string[]) => void;
|
||||||
|
@ -96,5 +96,6 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
marginLeft: theme.spacing(0.5),
|
marginLeft: theme.spacing(0.5),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
export const removeMarginBottom = css({ marginBottom: 8 });
|
||||||
|
|
||||||
export default getStyles;
|
export default getStyles;
|
||||||
|
30
yarn.lock
30
yarn.lock
@ -2913,13 +2913,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@grafana/aws-sdk@npm:0.2.0":
|
"@grafana/aws-sdk@npm:0.3.1":
|
||||||
version: 0.2.0
|
version: 0.3.1
|
||||||
resolution: "@grafana/aws-sdk@npm:0.2.0"
|
resolution: "@grafana/aws-sdk@npm:0.3.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@grafana/async-query-data": "npm:0.1.4"
|
"@grafana/async-query-data": "npm:0.1.4"
|
||||||
"@grafana/experimental": "npm:1.1.0"
|
"@grafana/experimental": "npm:1.7.0"
|
||||||
checksum: 5f79f62c37dc5b0841a38c0b9bf3d9d687e8e28c40a583399a47f7b7370dd07b0f5fd4d26ede496a453051ae385ea20e86edfdef4c55c2dceb43ab4400de634b
|
checksum: 89b42fa6351b78ce9760fb07ebfad37d45aa3327858ad4e88acc1699ccc9d6541ba0232ba8331d6b0a6b61a1d6b134e19ae5e91b2e90353a97958291c15bc9ef
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -3096,24 +3096,6 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
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":
|
"@grafana/experimental@npm:1.7.0":
|
||||||
version: 1.7.0
|
version: 1.7.0
|
||||||
resolution: "@grafana/experimental@npm:1.7.0"
|
resolution: "@grafana/experimental@npm:1.7.0"
|
||||||
@ -17311,7 +17293,7 @@ __metadata:
|
|||||||
"@fingerprintjs/fingerprintjs": "npm:^3.4.2"
|
"@fingerprintjs/fingerprintjs": "npm:^3.4.2"
|
||||||
"@glideapps/glide-data-grid": "npm:^5.2.1"
|
"@glideapps/glide-data-grid": "npm:^5.2.1"
|
||||||
"@grafana-plugins/grafana-testdata-datasource": "workspace:*"
|
"@grafana-plugins/grafana-testdata-datasource": "workspace:*"
|
||||||
"@grafana/aws-sdk": "npm:0.2.0"
|
"@grafana/aws-sdk": "npm:0.3.1"
|
||||||
"@grafana/data": "workspace:*"
|
"@grafana/data": "workspace:*"
|
||||||
"@grafana/e2e-selectors": "workspace:*"
|
"@grafana/e2e-selectors": "workspace:*"
|
||||||
"@grafana/eslint-config": "npm:6.0.1"
|
"@grafana/eslint-config": "npm:6.0.1"
|
||||||
|
Loading…
Reference in New Issue
Block a user