mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
CloudWatch: Datasource improvements (#20268)
* CloudWatch: Datasource improvements * Add statistic as template variale * Add wildcard to list of values * Template variable intercept dimension key * Return row specific errors when transformation error occured * Add meta feedback * Make it possible to retrieve values without known metrics * Add curated dashboard for EC2 * Fix broken tests * Use correct dashboard name * Display alert in case multi template var is being used for some certain props in the cloudwatch query * Minor fixes after feedback * Update dashboard json * Update snapshot test * Make sure region default is intercepted in cloudwatch link * Update dashboards * Include ec2 dashboard in ds * Do not include ec2 dashboard in beta1 * Display actual region
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { Alias } from './Alias';
|
||||
|
||||
describe('Alias', () => {
|
||||
it('should render component', () => {
|
||||
const tree = renderer.create(<Alias value={'legend'} onChange={() => {}} />).toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import React, { FunctionComponent, useState } from 'react';
|
||||
import { debounce } from 'lodash';
|
||||
import { Input } from '@grafana/ui';
|
||||
|
||||
export interface Props {
|
||||
onChange: (alias: any) => void;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const Alias: FunctionComponent<Props> = ({ value = '', onChange }) => {
|
||||
const [alias, setAlias] = useState(value);
|
||||
|
||||
const propagateOnChange = debounce(onChange, 1500);
|
||||
|
||||
onChange = (e: any) => {
|
||||
setAlias(e.target.value);
|
||||
propagateOnChange(e.target.value);
|
||||
};
|
||||
|
||||
return <Input type="text" className="gf-form-input width-16" value={alias} onChange={onChange} />;
|
||||
};
|
||||
@@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import ConfigEditor, { Props } from './ConfigEditor';
|
||||
|
||||
jest.mock('app/features/plugins/datasource_srv', () => ({
|
||||
getDatasourceSrv: () => ({
|
||||
loadDatasource: jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
getRegions: jest.fn().mockReturnValue([
|
||||
{
|
||||
label: 'ap-east-1',
|
||||
value: 'ap-east-1',
|
||||
},
|
||||
]),
|
||||
})
|
||||
),
|
||||
}),
|
||||
}));
|
||||
|
||||
const setup = (propOverrides?: object) => {
|
||||
const props: Props = {
|
||||
options: {
|
||||
id: 1,
|
||||
orgId: 1,
|
||||
typeLogoUrl: '',
|
||||
name: 'CloudWatch',
|
||||
access: 'proxy',
|
||||
url: '',
|
||||
database: '',
|
||||
type: 'cloudwatch',
|
||||
user: '',
|
||||
password: '',
|
||||
basicAuth: false,
|
||||
basicAuthPassword: '',
|
||||
basicAuthUser: '',
|
||||
isDefault: true,
|
||||
readOnly: false,
|
||||
withCredentials: false,
|
||||
secureJsonFields: {
|
||||
accessKey: false,
|
||||
secretKey: false,
|
||||
},
|
||||
jsonData: {
|
||||
assumeRoleArn: '',
|
||||
database: '',
|
||||
customMetricsNamespaces: '',
|
||||
authType: 'keys',
|
||||
defaultRegion: 'us-east-2',
|
||||
timeField: '@timestamp',
|
||||
},
|
||||
secureJsonData: {
|
||||
secretKey: '',
|
||||
accessKey: '',
|
||||
},
|
||||
},
|
||||
onOptionsChange: jest.fn(),
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
|
||||
return shallow(<ConfigEditor {...props} />);
|
||||
};
|
||||
|
||||
describe('Render', () => {
|
||||
it('should render component', () => {
|
||||
const wrapper = setup();
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should disable access key id field', () => {
|
||||
const wrapper = setup({
|
||||
secureJsonFields: {
|
||||
secretKey: true,
|
||||
},
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should should show credentials profile name field', () => {
|
||||
const wrapper = setup({
|
||||
jsonData: {
|
||||
authType: 'credentials',
|
||||
},
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should should show access key and secret access key fields', () => {
|
||||
const wrapper = setup({
|
||||
jsonData: {
|
||||
authType: 'keys',
|
||||
},
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should should show arn role field', () => {
|
||||
const wrapper = setup({
|
||||
jsonData: {
|
||||
authType: 'arn',
|
||||
},
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,390 @@
|
||||
import React, { PureComponent, ChangeEvent } from 'react';
|
||||
import { FormLabel, Select, Input, Button } from '@grafana/ui';
|
||||
import { DataSourcePluginOptionsEditorProps, DataSourceSettings } from '@grafana/data';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import CloudWatchDatasource from '../datasource';
|
||||
import { CloudWatchJsonData, CloudWatchSecureJsonData } from '../types';
|
||||
|
||||
export type Props = DataSourcePluginOptionsEditorProps<CloudWatchJsonData>;
|
||||
|
||||
type CloudwatchSettings = DataSourceSettings<CloudWatchJsonData, CloudWatchSecureJsonData>;
|
||||
|
||||
export interface State {
|
||||
config: CloudwatchSettings;
|
||||
authProviderOptions: SelectableValue[];
|
||||
regions: SelectableValue[];
|
||||
}
|
||||
|
||||
export class ConfigEditor extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const { options } = this.props;
|
||||
|
||||
this.state = {
|
||||
config: ConfigEditor.defaults(options),
|
||||
authProviderOptions: [
|
||||
{ label: 'Access & secret key', value: 'keys' },
|
||||
{ label: 'Credentials file', value: 'credentials' },
|
||||
{ label: 'ARN', value: 'arn' },
|
||||
],
|
||||
regions: [],
|
||||
};
|
||||
|
||||
this.updateDatasource(this.state.config);
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props: Props, state: State) {
|
||||
return {
|
||||
...state,
|
||||
config: ConfigEditor.defaults(props.options),
|
||||
};
|
||||
}
|
||||
|
||||
static defaults = (options: any) => {
|
||||
options.jsonData.authType = options.jsonData.authType || 'credentials';
|
||||
options.jsonData.timeField = options.jsonData.timeField || '@timestamp';
|
||||
|
||||
if (!options.hasOwnProperty('secureJsonData')) {
|
||||
options.secureJsonData = {};
|
||||
}
|
||||
|
||||
if (!options.hasOwnProperty('jsonData')) {
|
||||
options.jsonData = {};
|
||||
}
|
||||
|
||||
if (!options.hasOwnProperty('secureJsonFields')) {
|
||||
options.secureJsonFields = {};
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
this.loadRegions();
|
||||
}
|
||||
|
||||
loadRegions() {
|
||||
getDatasourceSrv()
|
||||
.loadDatasource(this.state.config.name)
|
||||
.then((ds: CloudWatchDatasource) => {
|
||||
return ds.getRegions();
|
||||
})
|
||||
.then(
|
||||
(regions: any) => {
|
||||
this.setState({
|
||||
regions: regions.map((region: any) => {
|
||||
return {
|
||||
value: region.value,
|
||||
label: region.text,
|
||||
};
|
||||
}),
|
||||
});
|
||||
},
|
||||
(err: any) => {
|
||||
const regions = [
|
||||
'ap-east-1',
|
||||
'ap-northeast-1',
|
||||
'ap-northeast-2',
|
||||
'ap-northeast-3',
|
||||
'ap-south-1',
|
||||
'ap-southeast-1',
|
||||
'ap-southeast-2',
|
||||
'ca-central-1',
|
||||
'cn-north-1',
|
||||
'cn-northwest-1',
|
||||
'eu-central-1',
|
||||
'eu-north-1',
|
||||
'eu-west-1',
|
||||
'eu-west-2',
|
||||
'eu-west-3',
|
||||
'me-south-1',
|
||||
'sa-east-1',
|
||||
'us-east-1',
|
||||
'us-east-2',
|
||||
'us-gov-east-1',
|
||||
'us-gov-west-1',
|
||||
'us-iso-east-1',
|
||||
'us-isob-east-1',
|
||||
'us-west-1',
|
||||
'us-west-2',
|
||||
];
|
||||
|
||||
this.setState({
|
||||
regions: regions.map((region: string) => {
|
||||
return {
|
||||
value: region,
|
||||
label: region,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
// expected to fail when creating new datasource
|
||||
// console.error('failed to get latest regions', err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
updateDatasource = async (config: any) => {
|
||||
for (const j in config.jsonData) {
|
||||
if (config.jsonData[j].length === 0) {
|
||||
delete config.jsonData[j];
|
||||
}
|
||||
}
|
||||
|
||||
for (const k in config.secureJsonData) {
|
||||
if (config.secureJsonData[k].length === 0) {
|
||||
delete config.secureJsonData[k];
|
||||
}
|
||||
}
|
||||
|
||||
this.props.onOptionsChange({
|
||||
...config,
|
||||
});
|
||||
};
|
||||
|
||||
onAuthProviderChange = (authType: SelectableValue<string>) => {
|
||||
this.updateDatasource({
|
||||
...this.state.config,
|
||||
jsonData: {
|
||||
...this.state.config.jsonData,
|
||||
authType: authType.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onRegionChange = (defaultRegion: SelectableValue<string>) => {
|
||||
this.updateDatasource({
|
||||
...this.state.config,
|
||||
jsonData: {
|
||||
...this.state.config.jsonData,
|
||||
defaultRegion: defaultRegion.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onResetAccessKey = () => {
|
||||
this.updateDatasource({
|
||||
...this.state.config,
|
||||
secureJsonFields: {
|
||||
...this.state.config.secureJsonFields,
|
||||
accessKey: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onAccessKeyChange = (accessKey: string) => {
|
||||
this.updateDatasource({
|
||||
...this.state.config,
|
||||
secureJsonData: {
|
||||
...this.state.config.secureJsonData,
|
||||
accessKey,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onResetSecretKey = () => {
|
||||
this.updateDatasource({
|
||||
...this.state.config,
|
||||
secureJsonFields: {
|
||||
...this.state.config.secureJsonFields,
|
||||
secretKey: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onSecretKeyChange = (secretKey: string) => {
|
||||
this.updateDatasource({
|
||||
...this.state.config,
|
||||
secureJsonData: {
|
||||
...this.state.config.secureJsonData,
|
||||
secretKey,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onCredentialProfileNameChange = (database: string) => {
|
||||
this.updateDatasource({
|
||||
...this.state.config,
|
||||
database,
|
||||
});
|
||||
};
|
||||
|
||||
onArnAssumeRoleChange = (assumeRoleArn: string) => {
|
||||
this.updateDatasource({
|
||||
...this.state.config,
|
||||
jsonData: {
|
||||
...this.state.config.jsonData,
|
||||
assumeRoleArn,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onCustomMetricsNamespacesChange = (customMetricsNamespaces: string) => {
|
||||
this.updateDatasource({
|
||||
...this.state.config,
|
||||
jsonData: {
|
||||
...this.state.config.jsonData,
|
||||
customMetricsNamespaces,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { config, authProviderOptions, regions } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="page-heading">CloudWatch Details</h3>
|
||||
<div className="gf-form-group">
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<FormLabel className="width-14">Auth Provider</FormLabel>
|
||||
<Select
|
||||
className="width-30"
|
||||
value={authProviderOptions.find(authProvider => authProvider.value === config.jsonData.authType)}
|
||||
options={authProviderOptions}
|
||||
defaultValue={config.jsonData.authType}
|
||||
onChange={this.onAuthProviderChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{config.jsonData.authType === 'credentials' && (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<FormLabel
|
||||
className="width-14"
|
||||
tooltip="Credentials profile name, as specified in ~/.aws/credentials, leave blank for default."
|
||||
>
|
||||
Credentials Profile Name
|
||||
</FormLabel>
|
||||
<div className="width-30">
|
||||
<Input
|
||||
className="width-30"
|
||||
placeholder="default"
|
||||
value={config.jsonData.database}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
this.onCredentialProfileNameChange(event.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{config.jsonData.authType === 'keys' && (
|
||||
<div>
|
||||
{config.secureJsonFields.accessKey ? (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<FormLabel className="width-14">Access Key ID</FormLabel>
|
||||
<Input className="width-25" placeholder="Configured" disabled={true} />
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<div className="max-width-30 gf-form-inline">
|
||||
<Button variant="secondary" type="button" onClick={this.onResetAccessKey}>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<FormLabel className="width-14">Access Key ID</FormLabel>
|
||||
<div className="width-30">
|
||||
<Input
|
||||
className="width-30"
|
||||
value={config.secureJsonData.accessKey || ''}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => this.onAccessKeyChange(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{config.secureJsonFields.secretKey ? (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<FormLabel className="width-14">Secret Access Key</FormLabel>
|
||||
<Input className="width-25" placeholder="Configured" disabled={true} />
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<div className="max-width-30 gf-form-inline">
|
||||
<Button variant="secondary" type="button" onClick={this.onResetSecretKey}>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<FormLabel className="width-14">Secret Access Key</FormLabel>
|
||||
<div className="width-30">
|
||||
<Input
|
||||
className="width-30"
|
||||
value={config.secureJsonData.secretKey || ''}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => this.onSecretKeyChange(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{config.jsonData.authType === 'arn' && (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<FormLabel className="width-14" tooltip="ARN of Assume Role">
|
||||
Assume Role ARN
|
||||
</FormLabel>
|
||||
<div className="width-30">
|
||||
<Input
|
||||
className="width-30"
|
||||
placeholder="arn:aws:iam:*"
|
||||
value={config.jsonData.assumeRoleArn || ''}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => this.onArnAssumeRoleChange(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<FormLabel
|
||||
className="width-14"
|
||||
tooltip="Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region."
|
||||
>
|
||||
Default Region
|
||||
</FormLabel>
|
||||
<Select
|
||||
className="width-30"
|
||||
value={regions.find(region => region.value === config.jsonData.defaultRegion)}
|
||||
options={regions}
|
||||
defaultValue={config.jsonData.defaultRegion}
|
||||
onChange={this.onRegionChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<FormLabel className="width-14" tooltip="Namespaces of Custom Metrics.">
|
||||
Custom Metrics
|
||||
</FormLabel>
|
||||
<Input
|
||||
className="width-30"
|
||||
placeholder="Namespace1,Namespace2"
|
||||
value={config.jsonData.customMetricsNamespaces || ''}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
this.onCustomMetricsNamespacesChange(event.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ConfigEditor;
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import { Dimensions } from './';
|
||||
import { SelectableStrings } from '../types';
|
||||
|
||||
describe('Dimensions', () => {
|
||||
it('renders', () => {
|
||||
mount(
|
||||
<Dimensions
|
||||
dimensions={{}}
|
||||
onChange={dimensions => console.log(dimensions)}
|
||||
loadKeys={() => Promise.resolve<SelectableStrings>([])}
|
||||
loadValues={() => Promise.resolve<SelectableStrings>([])}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
describe('and no dimension were passed to the component', () => {
|
||||
it('initially displays just an add button', () => {
|
||||
const wrapper = shallow(
|
||||
<Dimensions
|
||||
dimensions={{}}
|
||||
onChange={() => {}}
|
||||
loadKeys={() => Promise.resolve<SelectableStrings>([])}
|
||||
loadValues={() => Promise.resolve<SelectableStrings>([])}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper.html()).toEqual(
|
||||
`<div class="gf-form"><a class="gf-form-label query-part"><i class="fa fa-plus"></i></a></div>`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and one dimension key along with a value were passed to the component', () => {
|
||||
it('initially displays the dimension key, value and an add button', () => {
|
||||
const wrapper = shallow(
|
||||
<Dimensions
|
||||
dimensions={{ somekey: 'somevalue' }}
|
||||
onChange={() => {}}
|
||||
loadKeys={() => Promise.resolve<SelectableStrings>([])}
|
||||
loadValues={() => Promise.resolve<SelectableStrings>([])}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.html()).toEqual(
|
||||
`<div class="gf-form"><a class="gf-form-label query-part">somekey</a></div><label class="gf-form-label query-segment-operator">=</label><div class="gf-form"><a class="gf-form-label query-part">somevalue</a></div><div class="gf-form"><a class="gf-form-label query-part"><i class="fa fa-plus"></i></a></div>`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
import React, { FunctionComponent, Fragment, useState, useEffect } from 'react';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { SegmentAsync } from '@grafana/ui';
|
||||
import { SelectableStrings } from '../types';
|
||||
|
||||
export interface Props {
|
||||
dimensions: { [key: string]: string | string[] };
|
||||
onChange: (dimensions: { [key: string]: string }) => void;
|
||||
loadValues: (key: string) => Promise<SelectableStrings>;
|
||||
loadKeys: () => Promise<SelectableStrings>;
|
||||
}
|
||||
|
||||
const removeText = '-- remove dimension --';
|
||||
const removeOption: SelectableValue<string> = { label: removeText, value: removeText };
|
||||
|
||||
// The idea of this component is that is should only trigger the onChange event in the case
|
||||
// there is a complete dimension object. E.g, when a new key is added is doesn't have a value.
|
||||
// That should not trigger onChange.
|
||||
export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, loadKeys, onChange }) => {
|
||||
const [data, setData] = useState(dimensions);
|
||||
|
||||
useEffect(() => {
|
||||
const completeDimensions = Object.entries(data).reduce(
|
||||
(res, [key, value]) => (value ? { ...res, [key]: value } : res),
|
||||
{}
|
||||
);
|
||||
if (!isEqual(completeDimensions, dimensions)) {
|
||||
onChange(completeDimensions);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const excludeUsedKeys = (options: SelectableStrings) => {
|
||||
return options.filter(({ value }) => !Object.keys(data).includes(value));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.entries(data).map(([key, value], index) => (
|
||||
<Fragment key={index}>
|
||||
<SegmentAsync
|
||||
allowCustomValue
|
||||
value={key}
|
||||
loadOptions={() => loadKeys().then(keys => [removeOption, ...excludeUsedKeys(keys)])}
|
||||
onChange={newKey => {
|
||||
const { [key]: value, ...newDimensions } = data;
|
||||
if (newKey === removeText) {
|
||||
setData({ ...newDimensions });
|
||||
} else {
|
||||
setData({ ...newDimensions, [newKey]: '' });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<label className="gf-form-label query-segment-operator">=</label>
|
||||
<SegmentAsync
|
||||
allowCustomValue
|
||||
value={value || 'select dimension value'}
|
||||
loadOptions={() => loadValues(key)}
|
||||
onChange={newValue => setData({ ...data, [key]: newValue })}
|
||||
/>
|
||||
{Object.values(data).length > 1 && index + 1 !== Object.values(data).length && (
|
||||
<label className="gf-form-label query-keyword">AND</label>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
{Object.values(data).every(v => v) && (
|
||||
<SegmentAsync
|
||||
allowCustomValue
|
||||
Component={
|
||||
<a className="gf-form-label query-part">
|
||||
<i className="fa fa-plus" />
|
||||
</a>
|
||||
}
|
||||
loadOptions={() => loadKeys().then(excludeUsedKeys)}
|
||||
onChange={(newKey: string) => setData({ ...data, [newKey]: '' })}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import React, { InputHTMLAttributes, FunctionComponent } from 'react';
|
||||
import { FormLabel } from '@grafana/ui';
|
||||
|
||||
export interface Props extends InputHTMLAttributes<HTMLInputElement> {
|
||||
label: string;
|
||||
tooltip?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const QueryField: FunctionComponent<Partial<Props>> = ({ label, tooltip, children }) => (
|
||||
<>
|
||||
<FormLabel width={8} className="query-keyword" tooltip={tooltip}>
|
||||
{label}
|
||||
</FormLabel>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
|
||||
export const QueryInlineField: FunctionComponent<Props> = ({ ...props }) => {
|
||||
return (
|
||||
<div className={'gf-form-inline'}>
|
||||
<QueryField {...props} />
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label gf-form-label--grow" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { mount } from 'enzyme';
|
||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { CustomVariable } from 'app/features/templating/all';
|
||||
import { QueryEditor, Props } from './QueryEditor';
|
||||
import CloudWatchDatasource from '../datasource';
|
||||
|
||||
const setup = () => {
|
||||
const instanceSettings = {
|
||||
jsonData: { defaultRegion: 'us-east-1' },
|
||||
} as DataSourceInstanceSettings;
|
||||
|
||||
const templateSrv = new TemplateSrv();
|
||||
templateSrv.init([
|
||||
new CustomVariable(
|
||||
{
|
||||
name: 'var3',
|
||||
options: [
|
||||
{ selected: true, value: 'var3-foo' },
|
||||
{ selected: false, value: 'var3-bar' },
|
||||
{ selected: true, value: 'var3-baz' },
|
||||
],
|
||||
current: {
|
||||
value: ['var3-foo', 'var3-baz'],
|
||||
},
|
||||
multi: true,
|
||||
},
|
||||
{} as any
|
||||
),
|
||||
]);
|
||||
|
||||
const datasource = new CloudWatchDatasource(instanceSettings, {} as any, {} as any, templateSrv as any, {} as any);
|
||||
datasource.metricFindQuery = async param => [{ value: 'test', label: 'test' }];
|
||||
|
||||
const props: Props = {
|
||||
query: {
|
||||
refId: '',
|
||||
id: '',
|
||||
region: 'us-east-1',
|
||||
namespace: 'ec2',
|
||||
metricName: 'CPUUtilization',
|
||||
dimensions: { somekey: 'somevalue' },
|
||||
statistics: new Array<string>(),
|
||||
period: '',
|
||||
expression: '',
|
||||
alias: '',
|
||||
highResolution: false,
|
||||
matchExact: true,
|
||||
},
|
||||
datasource,
|
||||
onChange: jest.fn(),
|
||||
onRunQuery: jest.fn(),
|
||||
};
|
||||
|
||||
return props;
|
||||
};
|
||||
|
||||
describe('QueryEditor', () => {
|
||||
it('should render component', () => {
|
||||
const props = setup();
|
||||
const tree = renderer.create(<QueryEditor {...props} />).toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('should use correct default values', () => {
|
||||
it('when region is null is display default in the label', () => {
|
||||
const props = setup();
|
||||
props.query.region = null;
|
||||
const wrapper = mount(<QueryEditor {...props} />);
|
||||
expect(
|
||||
wrapper
|
||||
.find('.gf-form-inline')
|
||||
.first()
|
||||
.find('.gf-form-label.query-part')
|
||||
.first()
|
||||
.text()
|
||||
).toEqual('default');
|
||||
});
|
||||
|
||||
it('should init props correctly', () => {
|
||||
const props = setup();
|
||||
props.query.namespace = null;
|
||||
props.query.metricName = null;
|
||||
props.query.expression = null;
|
||||
props.query.dimensions = null;
|
||||
props.query.region = null;
|
||||
props.query.statistics = null;
|
||||
const wrapper = mount(<QueryEditor {...props} />);
|
||||
const {
|
||||
query: { namespace, region, metricName, dimensions, statistics, expression },
|
||||
} = wrapper.props();
|
||||
expect(namespace).toEqual('');
|
||||
expect(metricName).toEqual('');
|
||||
expect(expression).toEqual('');
|
||||
expect(region).toEqual('default');
|
||||
expect(statistics).toEqual(['Average']);
|
||||
expect(dimensions).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,277 @@
|
||||
import React, { PureComponent, ChangeEvent } from 'react';
|
||||
import { SelectableValue, QueryEditorProps } from '@grafana/data';
|
||||
import { Input, Segment, SegmentAsync, ValidationEvents, EventsWithValidation, Switch } from '@grafana/ui';
|
||||
import { CloudWatchQuery } from '../types';
|
||||
import CloudWatchDatasource from '../datasource';
|
||||
import { SelectableStrings } from '../types';
|
||||
import { Stats, Dimensions, QueryInlineField, QueryField, Alias } from './';
|
||||
|
||||
export type Props = QueryEditorProps<CloudWatchDatasource, CloudWatchQuery>;
|
||||
|
||||
interface State {
|
||||
regions: SelectableStrings;
|
||||
namespaces: SelectableStrings;
|
||||
metricNames: SelectableStrings;
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
showMeta: boolean;
|
||||
}
|
||||
|
||||
const idValidationEvents: ValidationEvents = {
|
||||
[EventsWithValidation.onBlur]: [
|
||||
{
|
||||
rule: value => new RegExp(/^$|^[a-z][a-zA-Z0-9_]*$/).test(value),
|
||||
errorMessage: 'Invalid format. Only alphanumeric characters and underscores are allowed',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export class QueryEditor extends PureComponent<Props, State> {
|
||||
state: State = { regions: [], namespaces: [], metricNames: [], variableOptionGroup: {}, showMeta: false };
|
||||
|
||||
componentWillMount() {
|
||||
const { query } = this.props;
|
||||
|
||||
if (!query.namespace) {
|
||||
query.namespace = '';
|
||||
}
|
||||
|
||||
if (!query.metricName) {
|
||||
query.metricName = '';
|
||||
}
|
||||
|
||||
if (!query.expression) {
|
||||
query.expression = '';
|
||||
}
|
||||
|
||||
if (!query.dimensions) {
|
||||
query.dimensions = {};
|
||||
}
|
||||
|
||||
if (!query.region) {
|
||||
query.region = 'default';
|
||||
}
|
||||
|
||||
if (!query.statistics || !query.statistics.length) {
|
||||
query.statistics = ['Average'];
|
||||
}
|
||||
|
||||
if (!query.hasOwnProperty('highResolution')) {
|
||||
query.highResolution = false;
|
||||
}
|
||||
|
||||
if (!query.hasOwnProperty('matchExact')) {
|
||||
query.matchExact = true;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { datasource } = this.props;
|
||||
const variableOptionGroup = {
|
||||
label: 'Template Variables',
|
||||
options: this.props.datasource.variables.map(this.toOption),
|
||||
};
|
||||
Promise.all([datasource.metricFindQuery('regions()'), datasource.metricFindQuery('namespaces()')]).then(
|
||||
([regions, namespaces]) => {
|
||||
this.setState({
|
||||
...this.state,
|
||||
regions: [...regions, variableOptionGroup],
|
||||
namespaces: [...namespaces, variableOptionGroup],
|
||||
variableOptionGroup,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadMetricNames = async () => {
|
||||
const { namespace, region } = this.props.query;
|
||||
return this.props.datasource.metricFindQuery(`metrics(${namespace},${region})`).then(this.appendTemplateVariables);
|
||||
};
|
||||
|
||||
appendTemplateVariables = (values: SelectableValue[]) => [
|
||||
...values,
|
||||
{ label: 'Template Variables', options: this.props.datasource.variables.map(this.toOption) },
|
||||
];
|
||||
|
||||
toOption = (value: any) => ({ label: value, value });
|
||||
|
||||
onChange(query: CloudWatchQuery) {
|
||||
const { onChange, onRunQuery } = this.props;
|
||||
onChange(query);
|
||||
onRunQuery();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { query, datasource, onChange, onRunQuery, data } = this.props;
|
||||
const { regions, namespaces, variableOptionGroup: variableOptionGroup, showMeta } = this.state;
|
||||
const metaDataExist = data && Object.values(data).length && data.state === 'Done';
|
||||
return (
|
||||
<>
|
||||
<QueryInlineField label="Region">
|
||||
<Segment
|
||||
value={query.region || 'Select region'}
|
||||
options={regions}
|
||||
allowCustomValue
|
||||
onChange={region => this.onChange({ ...query, region })}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
|
||||
{query.expression.length === 0 && (
|
||||
<>
|
||||
<QueryInlineField label="Namespace">
|
||||
<Segment
|
||||
value={query.namespace || 'Select namespace'}
|
||||
allowCustomValue
|
||||
options={namespaces}
|
||||
onChange={namespace => this.onChange({ ...query, namespace })}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
|
||||
<QueryInlineField label="Metric Name">
|
||||
<SegmentAsync
|
||||
value={query.metricName || 'Select metric name'}
|
||||
allowCustomValue
|
||||
loadOptions={this.loadMetricNames}
|
||||
onChange={metricName => this.onChange({ ...query, metricName })}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
|
||||
<QueryInlineField label="Stats">
|
||||
<Stats
|
||||
stats={datasource.standardStatistics.map(this.toOption)}
|
||||
values={query.statistics}
|
||||
onChange={statistics => this.onChange({ ...query, statistics })}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
|
||||
<QueryInlineField label="Dimensions">
|
||||
<Dimensions
|
||||
dimensions={query.dimensions}
|
||||
onChange={dimensions => this.onChange({ ...query, dimensions })}
|
||||
loadKeys={() =>
|
||||
datasource.getDimensionKeys(query.namespace, query.region).then(this.appendTemplateVariables)
|
||||
}
|
||||
loadValues={newKey => {
|
||||
const { [newKey]: value, ...newDimensions } = query.dimensions;
|
||||
return datasource
|
||||
.getDimensionValues(query.region, query.namespace, query.metricName, newKey, newDimensions)
|
||||
.then(this.appendTemplateVariables);
|
||||
}}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
</>
|
||||
)}
|
||||
{query.statistics.length <= 1 && (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<QueryField
|
||||
className="query-keyword"
|
||||
label="Id"
|
||||
tooltip="Id can include numbers, letters, and underscore, and must start with a lowercase letter."
|
||||
>
|
||||
<Input
|
||||
className="gf-form-input width-8"
|
||||
onBlur={onRunQuery}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => onChange({ ...query, id: event.target.value })}
|
||||
validationEvents={idValidationEvents}
|
||||
value={query.id || ''}
|
||||
/>
|
||||
</QueryField>
|
||||
</div>
|
||||
<div className="gf-form gf-form--grow">
|
||||
<QueryField
|
||||
className="gf-form--grow"
|
||||
label="Expression"
|
||||
tooltip="Optionally you can add an expression here. Please note that if a math expression that is referencing other queries is being used, it will not be possible to create an alert rule based on this query"
|
||||
>
|
||||
<Input
|
||||
className="gf-form-input"
|
||||
onBlur={onRunQuery}
|
||||
value={query.expression || ''}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
onChange({ ...query, expression: event.target.value })
|
||||
}
|
||||
/>
|
||||
</QueryField>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<QueryField
|
||||
className="query-keyword"
|
||||
label="Min Period"
|
||||
tooltip="Minimum interval between points in seconds"
|
||||
>
|
||||
<Input
|
||||
className="gf-form-input width-8"
|
||||
value={query.period || ''}
|
||||
placeholder="auto"
|
||||
onBlur={onRunQuery}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => onChange({ ...query, period: event.target.value })}
|
||||
/>
|
||||
</QueryField>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<QueryField
|
||||
className="query-keyword"
|
||||
label="Alias"
|
||||
tooltip="Alias replacement variables: {{metric}}, {{stat}}, {{namespace}}, {{region}}, {{period}}, {{label}}, {{YOUR_DIMENSION_NAME}}"
|
||||
>
|
||||
<Alias value={query.alias} onChange={(value: string) => this.onChange({ ...query, alias: value })} />
|
||||
</QueryField>
|
||||
<Switch
|
||||
label="HighRes"
|
||||
labelClass="query-keyword"
|
||||
checked={query.highResolution}
|
||||
onChange={() => this.onChange({ ...query, highResolution: !query.highResolution })}
|
||||
/>
|
||||
<Switch
|
||||
label="Match Exact"
|
||||
labelClass="query-keyword"
|
||||
tooltip="Only show metrics that exactly match all defined dimension names."
|
||||
checked={query.matchExact}
|
||||
onChange={() => this.onChange({ ...query, matchExact: !query.matchExact })}
|
||||
/>
|
||||
<label className="gf-form-label">
|
||||
<a
|
||||
onClick={() =>
|
||||
metaDataExist &&
|
||||
this.setState({
|
||||
...this.state,
|
||||
showMeta: !showMeta,
|
||||
})
|
||||
}
|
||||
>
|
||||
<i className={`fa fa-caret-${showMeta ? 'down' : 'right'}`} /> {showMeta ? 'Hide' : 'Show'} Query
|
||||
Preview
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label gf-form-label--grow" />
|
||||
</div>
|
||||
{showMeta && metaDataExist && (
|
||||
<table className="filter-table form-inline">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Metric Data Query ID</th>
|
||||
<th>Metric Data Query Expression</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.series[0].meta.gmdMeta.map(({ ID, Expression }: any) => (
|
||||
<tr key={ID}>
|
||||
<td>{ID}</td>
|
||||
<td>{Expression}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { Stats } from './Stats';
|
||||
|
||||
const toOption = (value: any) => ({ label: value, value });
|
||||
|
||||
describe('Stats', () => {
|
||||
it('should render component', () => {
|
||||
const tree = renderer
|
||||
.create(
|
||||
<Stats
|
||||
values={['Average', 'Minimum']}
|
||||
variableOptionGroup={{ label: 'templateVar', value: 'templateVar' }}
|
||||
onChange={() => {}}
|
||||
stats={['Average', 'Maximum', 'Minimum', 'Sum', 'SampleCount'].map(toOption)}
|
||||
/>
|
||||
)
|
||||
.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { SelectableStrings } from '../types';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Segment } from '@grafana/ui';
|
||||
|
||||
export interface Props {
|
||||
values: string[];
|
||||
onChange: (values: string[]) => void;
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
stats: SelectableStrings;
|
||||
}
|
||||
|
||||
const removeText = '-- remove stat --';
|
||||
const removeOption: SelectableValue<string> = { label: removeText, value: removeText };
|
||||
|
||||
export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, variableOptionGroup }) => (
|
||||
<>
|
||||
{values &&
|
||||
values.map((value, index) => (
|
||||
<Segment
|
||||
allowCustomValue
|
||||
key={value + index}
|
||||
value={value}
|
||||
options={[removeOption, ...stats, variableOptionGroup]}
|
||||
onChange={value =>
|
||||
onChange(
|
||||
value === removeText
|
||||
? values.filter((_, i) => i !== index)
|
||||
: values.map((v, i) => (i === index ? value : v))
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{values.length !== stats.length && (
|
||||
<Segment
|
||||
Component={
|
||||
<a className="gf-form-label query-part">
|
||||
<i className="fa fa-plus" />
|
||||
</a>
|
||||
}
|
||||
allowCustomValue
|
||||
onChange={(value: string) => onChange([...values, value])}
|
||||
options={[...stats.filter(({ value }) => !values.includes(value)), variableOptionGroup]}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
@@ -0,0 +1,27 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
export interface Props {
|
||||
region: string;
|
||||
}
|
||||
|
||||
export const ThrottlingErrorMessage: FunctionComponent<Props> = ({ region }) => (
|
||||
<p>
|
||||
Please visit the
|
||||
<a
|
||||
target="_blank"
|
||||
className="text-link"
|
||||
href={`https://${region}.console.aws.amazon.com/servicequotas/home?region=${region}#!/services/monitoring/quotas/L-5E141212`}
|
||||
>
|
||||
AWS Service Quotas console
|
||||
</a>
|
||||
to request a quota increase or see our
|
||||
<a
|
||||
target="_blank"
|
||||
className="text-link"
|
||||
href={`https://grafana.com/docs/features/datasources/cloudwatch/#service-quotas`}
|
||||
>
|
||||
documentation
|
||||
</a>
|
||||
to learn more.
|
||||
</p>
|
||||
);
|
||||
@@ -0,0 +1,18 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Alias should render component 1`] = `
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"flexGrow": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
className="gf-form-input gf-form-input width-16"
|
||||
onChange={[Function]}
|
||||
type="text"
|
||||
value="legend"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,901 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Render should disable access key id field 1`] = `
|
||||
<Fragment>
|
||||
<h3
|
||||
className="page-heading"
|
||||
>
|
||||
CloudWatch Details
|
||||
</h3>
|
||||
<div
|
||||
className="gf-form-group"
|
||||
>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
>
|
||||
Auth Provider
|
||||
</Component>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-30"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
defaultValue="keys"
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "Access & secret key",
|
||||
"value": "keys",
|
||||
},
|
||||
Object {
|
||||
"label": "Credentials file",
|
||||
"value": "credentials",
|
||||
},
|
||||
Object {
|
||||
"label": "ARN",
|
||||
"value": "arn",
|
||||
},
|
||||
]
|
||||
}
|
||||
tabSelectsValue={true}
|
||||
value={
|
||||
Object {
|
||||
"label": "Access & secret key",
|
||||
"value": "keys",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
>
|
||||
Access Key ID
|
||||
</Component>
|
||||
<div
|
||||
className="width-30"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
>
|
||||
Secret Access Key
|
||||
</Component>
|
||||
<div
|
||||
className="width-30"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
tooltip="Specify the region, such as for US West (Oregon) use \` us-west-2 \` as the region."
|
||||
>
|
||||
Default Region
|
||||
</Component>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-30"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
defaultValue="us-east-2"
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={Array []}
|
||||
tabSelectsValue={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
tooltip="Namespaces of Custom Metrics."
|
||||
>
|
||||
Custom Metrics
|
||||
</Component>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="Namespace1,Namespace2"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`Render should render component 1`] = `
|
||||
<Fragment>
|
||||
<h3
|
||||
className="page-heading"
|
||||
>
|
||||
CloudWatch Details
|
||||
</h3>
|
||||
<div
|
||||
className="gf-form-group"
|
||||
>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
>
|
||||
Auth Provider
|
||||
</Component>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-30"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
defaultValue="keys"
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "Access & secret key",
|
||||
"value": "keys",
|
||||
},
|
||||
Object {
|
||||
"label": "Credentials file",
|
||||
"value": "credentials",
|
||||
},
|
||||
Object {
|
||||
"label": "ARN",
|
||||
"value": "arn",
|
||||
},
|
||||
]
|
||||
}
|
||||
tabSelectsValue={true}
|
||||
value={
|
||||
Object {
|
||||
"label": "Access & secret key",
|
||||
"value": "keys",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
>
|
||||
Access Key ID
|
||||
</Component>
|
||||
<div
|
||||
className="width-30"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
>
|
||||
Secret Access Key
|
||||
</Component>
|
||||
<div
|
||||
className="width-30"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
tooltip="Specify the region, such as for US West (Oregon) use \` us-west-2 \` as the region."
|
||||
>
|
||||
Default Region
|
||||
</Component>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-30"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
defaultValue="us-east-2"
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={Array []}
|
||||
tabSelectsValue={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
tooltip="Namespaces of Custom Metrics."
|
||||
>
|
||||
Custom Metrics
|
||||
</Component>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="Namespace1,Namespace2"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`Render should should show access key and secret access key fields 1`] = `
|
||||
<Fragment>
|
||||
<h3
|
||||
className="page-heading"
|
||||
>
|
||||
CloudWatch Details
|
||||
</h3>
|
||||
<div
|
||||
className="gf-form-group"
|
||||
>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
>
|
||||
Auth Provider
|
||||
</Component>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-30"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
defaultValue="keys"
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "Access & secret key",
|
||||
"value": "keys",
|
||||
},
|
||||
Object {
|
||||
"label": "Credentials file",
|
||||
"value": "credentials",
|
||||
},
|
||||
Object {
|
||||
"label": "ARN",
|
||||
"value": "arn",
|
||||
},
|
||||
]
|
||||
}
|
||||
tabSelectsValue={true}
|
||||
value={
|
||||
Object {
|
||||
"label": "Access & secret key",
|
||||
"value": "keys",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
>
|
||||
Access Key ID
|
||||
</Component>
|
||||
<div
|
||||
className="width-30"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
>
|
||||
Secret Access Key
|
||||
</Component>
|
||||
<div
|
||||
className="width-30"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
tooltip="Specify the region, such as for US West (Oregon) use \` us-west-2 \` as the region."
|
||||
>
|
||||
Default Region
|
||||
</Component>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-30"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
defaultValue="us-east-2"
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={Array []}
|
||||
tabSelectsValue={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
tooltip="Namespaces of Custom Metrics."
|
||||
>
|
||||
Custom Metrics
|
||||
</Component>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="Namespace1,Namespace2"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`Render should should show arn role field 1`] = `
|
||||
<Fragment>
|
||||
<h3
|
||||
className="page-heading"
|
||||
>
|
||||
CloudWatch Details
|
||||
</h3>
|
||||
<div
|
||||
className="gf-form-group"
|
||||
>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
>
|
||||
Auth Provider
|
||||
</Component>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-30"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
defaultValue="keys"
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "Access & secret key",
|
||||
"value": "keys",
|
||||
},
|
||||
Object {
|
||||
"label": "Credentials file",
|
||||
"value": "credentials",
|
||||
},
|
||||
Object {
|
||||
"label": "ARN",
|
||||
"value": "arn",
|
||||
},
|
||||
]
|
||||
}
|
||||
tabSelectsValue={true}
|
||||
value={
|
||||
Object {
|
||||
"label": "Access & secret key",
|
||||
"value": "keys",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
>
|
||||
Access Key ID
|
||||
</Component>
|
||||
<div
|
||||
className="width-30"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
>
|
||||
Secret Access Key
|
||||
</Component>
|
||||
<div
|
||||
className="width-30"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
tooltip="Specify the region, such as for US West (Oregon) use \` us-west-2 \` as the region."
|
||||
>
|
||||
Default Region
|
||||
</Component>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-30"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
defaultValue="us-east-2"
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={Array []}
|
||||
tabSelectsValue={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
tooltip="Namespaces of Custom Metrics."
|
||||
>
|
||||
Custom Metrics
|
||||
</Component>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="Namespace1,Namespace2"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`Render should should show credentials profile name field 1`] = `
|
||||
<Fragment>
|
||||
<h3
|
||||
className="page-heading"
|
||||
>
|
||||
CloudWatch Details
|
||||
</h3>
|
||||
<div
|
||||
className="gf-form-group"
|
||||
>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
>
|
||||
Auth Provider
|
||||
</Component>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-30"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
defaultValue="keys"
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "Access & secret key",
|
||||
"value": "keys",
|
||||
},
|
||||
Object {
|
||||
"label": "Credentials file",
|
||||
"value": "credentials",
|
||||
},
|
||||
Object {
|
||||
"label": "ARN",
|
||||
"value": "arn",
|
||||
},
|
||||
]
|
||||
}
|
||||
tabSelectsValue={true}
|
||||
value={
|
||||
Object {
|
||||
"label": "Access & secret key",
|
||||
"value": "keys",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
>
|
||||
Access Key ID
|
||||
</Component>
|
||||
<div
|
||||
className="width-30"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
>
|
||||
Secret Access Key
|
||||
</Component>
|
||||
<div
|
||||
className="width-30"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
tooltip="Specify the region, such as for US West (Oregon) use \` us-west-2 \` as the region."
|
||||
>
|
||||
Default Region
|
||||
</Component>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-30"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
defaultValue="us-east-2"
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={Array []}
|
||||
tabSelectsValue={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
className="width-14"
|
||||
tooltip="Namespaces of Custom Metrics."
|
||||
>
|
||||
Custom Metrics
|
||||
</Component>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="Namespace1,Namespace2"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -0,0 +1,396 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`QueryEditor should render component 1`] = `
|
||||
Array [
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label width-8 query-keyword"
|
||||
>
|
||||
Region
|
||||
</label>
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<a
|
||||
className="gf-form-label query-part"
|
||||
>
|
||||
us-east-1
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
>
|
||||
<div
|
||||
className="gf-form-label gf-form-label--grow"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label width-8 query-keyword"
|
||||
>
|
||||
Namespace
|
||||
</label>
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<a
|
||||
className="gf-form-label query-part"
|
||||
>
|
||||
ec2
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
>
|
||||
<div
|
||||
className="gf-form-label gf-form-label--grow"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label width-8 query-keyword"
|
||||
>
|
||||
Metric Name
|
||||
</label>
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<a
|
||||
className="gf-form-label query-part"
|
||||
>
|
||||
CPUUtilization
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
>
|
||||
<div
|
||||
className="gf-form-label gf-form-label--grow"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label width-8 query-keyword"
|
||||
>
|
||||
Stats
|
||||
</label>
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<a
|
||||
className="gf-form-label query-part"
|
||||
>
|
||||
Average
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<a
|
||||
className="gf-form-label query-part"
|
||||
>
|
||||
<i
|
||||
className="fa fa-plus"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
>
|
||||
<div
|
||||
className="gf-form-label gf-form-label--grow"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label width-8 query-keyword"
|
||||
>
|
||||
Dimensions
|
||||
</label>
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<a
|
||||
className="gf-form-label query-part"
|
||||
>
|
||||
somekey
|
||||
</a>
|
||||
</div>
|
||||
<label
|
||||
className="gf-form-label query-segment-operator"
|
||||
>
|
||||
=
|
||||
</label>
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<a
|
||||
className="gf-form-label query-part"
|
||||
>
|
||||
somevalue
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<a
|
||||
className="gf-form-label query-part"
|
||||
>
|
||||
<i
|
||||
className="fa fa-plus"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
>
|
||||
<div
|
||||
className="gf-form-label gf-form-label--grow"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label width-8 query-keyword"
|
||||
>
|
||||
Id
|
||||
<div
|
||||
className="gf-form-help-icon gf-form-help-icon--right-normal"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-info-circle"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"flexGrow": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
className="gf-form-input gf-form-input width-8"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label width-8 query-keyword"
|
||||
>
|
||||
Expression
|
||||
<div
|
||||
className="gf-form-help-icon gf-form-help-icon--right-normal"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-info-circle"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"flexGrow": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
className="gf-form-input gf-form-input"
|
||||
onBlur={[MockFunction]}
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label width-8 query-keyword"
|
||||
>
|
||||
Min Period
|
||||
<div
|
||||
className="gf-form-help-icon gf-form-help-icon--right-normal"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-info-circle"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"flexGrow": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
className="gf-form-input gf-form-input width-8"
|
||||
onBlur={[MockFunction]}
|
||||
onChange={[Function]}
|
||||
placeholder="auto"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label width-8 query-keyword"
|
||||
>
|
||||
Alias
|
||||
<div
|
||||
className="gf-form-help-icon gf-form-help-icon--right-normal"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-info-circle"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"flexGrow": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
className="gf-form-input gf-form-input width-16"
|
||||
onChange={[Function]}
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-switch-container-react"
|
||||
>
|
||||
<label
|
||||
className="gf-form gf-form-switch-container "
|
||||
htmlFor="1"
|
||||
>
|
||||
<div
|
||||
className="gf-form-label query-keyword pointer"
|
||||
>
|
||||
HighRes
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-switch "
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="1"
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className="gf-form-switch__slider"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-switch-container-react"
|
||||
>
|
||||
<label
|
||||
className="gf-form gf-form-switch-container "
|
||||
htmlFor="2"
|
||||
>
|
||||
<div
|
||||
className="gf-form-label query-keyword pointer"
|
||||
>
|
||||
Match Exact
|
||||
<div
|
||||
className="gf-form-help-icon gf-form-help-icon--right-normal"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-info-circle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-switch "
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
id="2"
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className="gf-form-switch__slider"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<label
|
||||
className="gf-form-label"
|
||||
>
|
||||
<a
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-caret-right"
|
||||
/>
|
||||
|
||||
Show
|
||||
Query Preview
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
>
|
||||
<div
|
||||
className="gf-form-label gf-form-label--grow"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
@@ -0,0 +1,38 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Stats should render component 1`] = `
|
||||
Array [
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<a
|
||||
className="gf-form-label query-part"
|
||||
>
|
||||
Average
|
||||
</a>
|
||||
</div>,
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<a
|
||||
className="gf-form-label query-part"
|
||||
>
|
||||
Minimum
|
||||
</a>
|
||||
</div>,
|
||||
<div
|
||||
className="gf-form"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<a
|
||||
className="gf-form-label query-part"
|
||||
>
|
||||
<i
|
||||
className="fa fa-plus"
|
||||
/>
|
||||
</a>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
export { Stats } from './Stats';
|
||||
export { Dimensions } from './Dimensions';
|
||||
export { QueryInlineField, QueryField } from './Forms';
|
||||
export { Alias } from './Alias';
|
||||
Reference in New Issue
Block a user