Provisioning: Allows specifying uid for datasource and use that in derived fields (#23585)

* Add uid to datasource

* Fix uid passing when provisioning

* Better error handling and Uid column type change

* Fix test and strict null error counts

* Add backend tests

* Add tests

* Fix strict null checks

* Update test

* Improve tests

* Update pkg/services/sqlstore/datasource.go

Co-Authored-By: Arve Knudsen <arve.knudsen@gmail.com>

* Variable rename

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
Andrej Ocenas
2020-04-20 15:48:38 +02:00
committed by GitHub
parent d5f8d976f0
commit e5dd7efdee
34 changed files with 446 additions and 204 deletions

View File

@@ -39,7 +39,7 @@ export const ConfigEditor = (props: Props) => {
<div className="gf-form-inline">
<div className="gf-form">
<MaxLinesField
value={options.jsonData.maxLines}
value={options.jsonData.maxLines || ''}
onChange={value => onOptionsChange(setMaxLines(options, value))}
/>
</div>

View File

@@ -0,0 +1,50 @@
import React from 'react';
import { shallow } from 'enzyme';
import { DerivedField } from './DerivedField';
import DataSourcePicker from '../../../../core/components/Select/DataSourcePicker';
jest.mock('app/core/config', () => ({
config: {
featureToggles: {
tracingIntegration: true,
},
},
}));
jest.mock('app/features/plugins/datasource_srv', () => ({
getDatasourceSrv() {
return {
getExternal(): any[] {
return [];
},
};
},
}));
describe('DerivedField', () => {
it('shows internal link if uid is set', () => {
const value = {
matcherRegex: '',
name: '',
datasourceUid: 'test',
};
const wrapper = shallow(<DerivedField value={value} onChange={() => {}} onDelete={() => {}} suggestions={[]} />);
expect(
wrapper
.find('DataSourceSection')
.dive()
.find(DataSourcePicker).length
).toBe(1);
});
it('shows url link if uid is not set', () => {
const value = {
matcherRegex: '',
name: '',
url: 'test',
};
const wrapper = shallow(<DerivedField value={value} onChange={() => {}} onDelete={() => {}} suggestions={[]} />);
expect(wrapper.find('DataSourceSection').length).toBe(0);
});
});

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { css } from 'emotion';
import { Button, FormField, DataLinkInput, stylesFactory, LegacyForms } from '@grafana/ui';
const { Switch } = LegacyForms;
@@ -9,6 +9,7 @@ import { DerivedFieldConfig } from '../types';
import DataSourcePicker from 'app/core/components/Select/DataSourcePicker';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { config } from 'app/core/config';
import { usePrevious } from 'react-use';
const getStyles = stylesFactory(() => ({
row: css`
@@ -33,7 +34,18 @@ type Props = {
export const DerivedField = (props: Props) => {
const { value, onChange, onDelete, suggestions, className } = props;
const styles = getStyles();
const [hasIntenalLink, setHasInternalLink] = useState(!!value.datasourceName);
const [showInternalLink, setShowInternalLink] = useState(!!value.datasourceUid);
const previousUid = usePrevious(value.datasourceUid);
// Force internal link visibility change if uid changed outside of this component.
useEffect(() => {
if (!previousUid && value.datasourceUid && !showInternalLink) {
setShowInternalLink(true);
}
if (previousUid && !value.datasourceUid && showInternalLink) {
setShowInternalLink(false);
}
}, [previousUid, value.datasourceUid, showInternalLink]);
const handleChange = (field: keyof typeof value) => (event: React.ChangeEvent<HTMLInputElement>) => {
onChange({
@@ -81,11 +93,11 @@ export const DerivedField = (props: Props) => {
</div>
<FormField
label="URL"
label={showInternalLink ? 'Query' : 'URL'}
labelWidth={5}
inputEl={
<DataLinkInput
placeholder={'http://example.com/${__value.raw}'}
placeholder={showInternalLink ? '${__value.raw}' : 'http://example.com/${__value.raw}'}
value={value.url || ''}
onChange={newValue =>
onChange({
@@ -105,27 +117,27 @@ export const DerivedField = (props: Props) => {
<div className={styles.row}>
<Switch
label="Internal link"
checked={hasIntenalLink}
checked={showInternalLink}
onChange={() => {
if (hasIntenalLink) {
if (showInternalLink) {
onChange({
...value,
datasourceName: undefined,
datasourceUid: undefined,
});
}
setHasInternalLink(!hasIntenalLink);
setShowInternalLink(!showInternalLink);
}}
/>
{hasIntenalLink && (
{showInternalLink && (
<DataSourceSection
onChange={datasourceName => {
onChange={datasourceUid => {
onChange({
...value,
datasourceName,
datasourceUid,
});
}}
datasourceName={value.datasourceName}
datasourceUid={value.datasourceUid}
/>
)}
</div>
@@ -135,29 +147,30 @@ export const DerivedField = (props: Props) => {
};
type DataSourceSectionProps = {
datasourceName?: string;
onChange: (name: string) => void;
datasourceUid?: string;
onChange: (uid: string) => void;
};
const DataSourceSection = (props: DataSourceSectionProps) => {
const { datasourceName, onChange } = props;
const { datasourceUid, onChange } = props;
const datasources: DataSourceSelectItem[] = getDatasourceSrv()
.getExternal()
.map(
(ds: any) =>
ds =>
({
value: ds.name,
value: ds.uid,
name: ds.name,
meta: ds.meta,
} as DataSourceSelectItem)
);
const selectedDatasource = datasourceName && datasources.find(d => d.name === datasourceName);
let selectedDatasource = datasourceUid && datasources.find(d => d.value === datasourceUid);
return (
<DataSourcePicker
onChange={newValue => {
onChange(newValue.name);
}}
// Uid and value should be always set in the db and so in the items.
onChange={ds => onChange(ds.value!)}
datasources={datasources}
current={selectedDatasource}
current={selectedDatasource || undefined}
/>
);
};