DataSourceSettings: use details from HealthCheckResult (#32759)

* add custom HealthCheckError

* allow details from HealthCheckResult to be passed in the error

* pass in details.message from testing status into Alert component

* add chance

* add aria label to read only message

* update tests and add error message tests

* extract HealthCheckResultDetails type out and add comment

* extract TestingStatus interface out

* remove chance from test

* remove chance
This commit is contained in:
Vicky Lee
2021-04-08 13:32:12 +01:00
committed by GitHub
parent fe67680c42
commit 59a33d98ec
8 changed files with 165 additions and 528 deletions

View File

@@ -1,85 +1,142 @@
import React from 'react';
import { shallow } from 'enzyme';
import { DataSourceSettingsPage, Props } from './DataSourceSettingsPage';
import { DataSourceConstructor, DataSourcePlugin, DataSourceSettings, NavModel } from '@grafana/data';
import { getMockDataSource } from '../__mocks__/dataSourcesMocks';
import { getMockPlugin } from '../../plugins/__mocks__/pluginMocks';
import { dataSourceLoaded, setDataSourceName, setIsDefault } from '../state/reducers';
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
import { cleanUpAction } from 'app/core/actions/cleanUp';
import { screen, render } from '@testing-library/react';
import { selectors } from '@grafana/e2e-selectors';
import { PluginState } from '@grafana/data';
const pluginMock = new DataSourcePlugin({} as DataSourceConstructor<any>);
jest.mock('app/features/plugins/plugin_loader', () => {
return {
importDataSourcePlugin: () => Promise.resolve(pluginMock),
};
const getMockNode = () => ({
text: 'text',
subTitle: 'subtitle',
icon: 'icon',
});
const setup = (propOverrides?: object) => {
const props: Props = {
...getRouteComponentProps(),
navModel: {} as NavModel,
dataSource: getMockDataSource(),
dataSourceMeta: getMockPlugin(),
dataSourceId: 1,
deleteDataSource: jest.fn(),
loadDataSource: jest.fn(),
setDataSourceName,
updateDataSource: jest.fn(),
initDataSourceSettings: jest.fn(),
testDataSource: jest.fn(),
setIsDefault,
dataSourceLoaded,
cleanUpAction,
page: null,
plugin: null,
loadError: null,
testingStatus: {},
...propOverrides,
};
return shallow(<DataSourceSettingsPage {...props} />);
};
const getProps = (): Props => ({
...getRouteComponentProps(),
navModel: {
node: getMockNode(),
main: getMockNode(),
},
dataSource: getMockDataSource(),
dataSourceMeta: getMockPlugin(),
dataSourceId: 1,
deleteDataSource: jest.fn(),
loadDataSource: jest.fn(),
setDataSourceName,
updateDataSource: jest.fn(),
initDataSourceSettings: jest.fn(),
testDataSource: jest.fn(),
setIsDefault,
dataSourceLoaded,
cleanUpAction,
page: null,
plugin: null,
loadError: null,
testingStatus: {},
});
describe('Render', () => {
it('should render component', () => {
const wrapper = setup();
expect(wrapper).toMatchSnapshot();
it('should not render loading when props are ready', () => {
render(<DataSourceSettingsPage {...getProps()} />);
expect(screen.queryByText('Loading ...')).not.toBeInTheDocument();
});
it('should render loader', () => {
const wrapper = setup({
dataSource: {} as DataSourceSettings,
plugin: pluginMock,
});
it('should render loading if datasource is not ready', () => {
const mockProps = getProps();
mockProps.dataSource.id = 0;
expect(wrapper).toMatchSnapshot();
render(<DataSourceSettingsPage {...mockProps} />);
expect(screen.getByText('Loading ...')).toBeInTheDocument();
});
it('should render beta info text', () => {
const wrapper = setup({
dataSourceMeta: { ...getMockPlugin(), state: 'beta' },
});
it('should render beta info text if plugin state is beta', () => {
const mockProps = getProps();
mockProps.dataSourceMeta.state = PluginState.beta;
expect(wrapper).toMatchSnapshot();
render(<DataSourceSettingsPage {...mockProps} />);
expect(
screen.getByTitle('Beta Plugin: There could be bugs and minor breaking changes to this plugin')
).toBeInTheDocument();
});
it('should render alpha info text', () => {
const wrapper = setup({
dataSourceMeta: { ...getMockPlugin(), state: 'alpha' },
plugin: pluginMock,
});
it('should render alpha info text if plugin state is alpha', () => {
const mockProps = getProps();
mockProps.dataSourceMeta.state = PluginState.alpha;
expect(wrapper).toMatchSnapshot();
render(<DataSourceSettingsPage {...mockProps} />);
expect(
screen.getByTitle('Alpha Plugin: This plugin is a work in progress and updates may include breaking changes')
).toBeInTheDocument();
});
it('should render is ready only message', () => {
const wrapper = setup({
dataSource: { ...getMockDataSource(), readOnly: true },
plugin: pluginMock,
});
it('should not render is ready only message is readOnly is false', () => {
const mockProps = getProps();
mockProps.dataSource.readOnly = false;
expect(wrapper).toMatchSnapshot();
render(<DataSourceSettingsPage {...mockProps} />);
expect(screen.queryByLabelText(selectors.pages.DataSource.readOnly)).not.toBeInTheDocument();
});
it('should render is ready only message is readOnly is true', () => {
const mockProps = getProps();
mockProps.dataSource.readOnly = true;
render(<DataSourceSettingsPage {...mockProps} />);
expect(screen.getByLabelText(selectors.pages.DataSource.readOnly)).toBeInTheDocument();
});
it('should render error message with detailed message', () => {
const mockProps = {
...getProps(),
testingStatus: {
message: 'message',
status: 'error',
details: { message: 'detailed message' },
},
};
render(<DataSourceSettingsPage {...mockProps} />);
expect(screen.getByText(mockProps.testingStatus.message)).toBeInTheDocument();
expect(screen.getByText(mockProps.testingStatus.details.message)).toBeInTheDocument();
});
it('should render error message with empty details', () => {
const mockProps = {
...getProps(),
testingStatus: {
message: 'message',
status: 'error',
details: {},
},
};
render(<DataSourceSettingsPage {...mockProps} />);
expect(screen.getByText(mockProps.testingStatus.message)).toBeInTheDocument();
});
it('should render error message without details', () => {
const mockProps = {
...getProps(),
testingStatus: {
message: 'message',
status: 'error',
},
};
render(<DataSourceSettingsPage {...mockProps} />);
expect(screen.getByText(mockProps.testingStatus.message)).toBeInTheDocument();
});
});

View File

@@ -127,7 +127,7 @@ export class DataSourceSettingsPage extends PureComponent<Props> {
renderIsReadOnlyMessage() {
return (
<InfoBox severity="info">
<InfoBox aria-label={selectors.pages.DataSource.readOnly} severity="info">
This data source was added by config and cannot be modified using the UI. Please contact your server admin to
update this data source.
</InfoBox>
@@ -234,12 +234,14 @@ export class DataSourceSettingsPage extends PureComponent<Props> {
)}
<div className="gf-form-group">
{testingStatus && testingStatus.message && (
{testingStatus?.message && (
<Alert
severity={testingStatus.status === 'error' ? 'error' : 'success'}
title={testingStatus.message}
aria-label={selectors.pages.DataSource.alert}
/>
>
{testingStatus.details?.message ?? null}
</Alert>
)}
</div>

View File

@@ -1,439 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render alpha info text 1`] = `
<Page
navModel={Object {}}
>
<PageContents
isLoading={false}
>
<div>
<form
onSubmit={[Function]}
>
<div
className="gf-form"
>
<label
className="gf-form-label width-10"
>
Plugin state
</label>
<label
className="gf-form-label gf-form-label--transparent"
>
<PluginStateinfo
state="alpha"
/>
</label>
</div>
<CloudInfoBox
dataSource={
Object {
"access": "",
"basicAuth": false,
"basicAuthPassword": "",
"basicAuthUser": "",
"database": "",
"id": 13,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "gdev-cloudwatch",
"orgId": 1,
"password": "",
"readOnly": false,
"secureJsonFields": Object {},
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"typeName": "Cloudwatch",
"url": "",
"user": "",
"withCredentials": false,
}
}
/>
<BasicSettings
dataSourceName="gdev-cloudwatch"
isDefault={false}
onDefaultChange={[Function]}
onNameChange={[Function]}
/>
<PluginSettings
dataSource={
Object {
"access": "",
"basicAuth": false,
"basicAuthPassword": "",
"basicAuthUser": "",
"database": "",
"id": 13,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "gdev-cloudwatch",
"orgId": 1,
"password": "",
"readOnly": false,
"secureJsonFields": Object {},
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"typeName": "Cloudwatch",
"url": "",
"user": "",
"withCredentials": false,
}
}
dataSourceMeta={
Object {
"baseUrl": "path/to/plugin",
"defaultNavUrl": "some/url",
"enabled": false,
"hasUpdate": false,
"id": "1",
"info": Object {
"author": Object {
"name": "Grafana Labs",
"url": "url/to/GrafanaLabs",
},
"description": "pretty decent plugin",
"links": Array [
Object {
"name": "project",
"url": "one link",
},
],
"logos": Object {
"large": "large/logo",
"small": "small/logo",
},
"screenshots": Array [
Object {
"name": "test",
"path": "screenshot",
},
],
"updated": "2018-09-26",
"version": "1",
},
"latestVersion": "1",
"module": "path/to/module",
"name": "pretty cool plugin 1",
"pinned": false,
"state": "alpha",
"type": "panel",
}
}
onModelChange={[Function]}
plugin={
DataSourcePlugin {
"DataSourceClass": Object {},
"components": Object {},
"meta": Object {},
}
}
/>
<div
className="gf-form-group"
/>
<ButtonRow
isReadOnly={false}
onDelete={[Function]}
onSubmit={[Function]}
onTest={[Function]}
/>
</form>
</div>
</PageContents>
</Page>
`;
exports[`Render should render beta info text 1`] = `
<Page
navModel={Object {}}
>
<PageContents
isLoading={false}
>
<div>
<form
onSubmit={[Function]}
>
<div
className="gf-form"
>
<label
className="gf-form-label width-10"
>
Plugin state
</label>
<label
className="gf-form-label gf-form-label--transparent"
>
<PluginStateinfo
state="beta"
/>
</label>
</div>
<CloudInfoBox
dataSource={
Object {
"access": "",
"basicAuth": false,
"basicAuthPassword": "",
"basicAuthUser": "",
"database": "",
"id": 13,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "gdev-cloudwatch",
"orgId": 1,
"password": "",
"readOnly": false,
"secureJsonFields": Object {},
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"typeName": "Cloudwatch",
"url": "",
"user": "",
"withCredentials": false,
}
}
/>
<BasicSettings
dataSourceName="gdev-cloudwatch"
isDefault={false}
onDefaultChange={[Function]}
onNameChange={[Function]}
/>
<div
className="gf-form-group"
/>
<ButtonRow
isReadOnly={false}
onDelete={[Function]}
onSubmit={[Function]}
onTest={[Function]}
/>
</form>
</div>
</PageContents>
</Page>
`;
exports[`Render should render component 1`] = `
<Page
navModel={Object {}}
>
<PageContents
isLoading={false}
>
<div>
<form
onSubmit={[Function]}
>
<CloudInfoBox
dataSource={
Object {
"access": "",
"basicAuth": false,
"basicAuthPassword": "",
"basicAuthUser": "",
"database": "",
"id": 13,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "gdev-cloudwatch",
"orgId": 1,
"password": "",
"readOnly": false,
"secureJsonFields": Object {},
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"typeName": "Cloudwatch",
"url": "",
"user": "",
"withCredentials": false,
}
}
/>
<BasicSettings
dataSourceName="gdev-cloudwatch"
isDefault={false}
onDefaultChange={[Function]}
onNameChange={[Function]}
/>
<div
className="gf-form-group"
/>
<ButtonRow
isReadOnly={false}
onDelete={[Function]}
onSubmit={[Function]}
onTest={[Function]}
/>
</form>
</div>
</PageContents>
</Page>
`;
exports[`Render should render is ready only message 1`] = `
<Page
navModel={Object {}}
>
<PageContents
isLoading={false}
>
<div>
<form
onSubmit={[Function]}
>
<InfoBox
severity="info"
>
This data source was added by config and cannot be modified using the UI. Please contact your server admin to update this data source.
</InfoBox>
<CloudInfoBox
dataSource={
Object {
"access": "",
"basicAuth": false,
"basicAuthPassword": "",
"basicAuthUser": "",
"database": "",
"id": 13,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "gdev-cloudwatch",
"orgId": 1,
"password": "",
"readOnly": true,
"secureJsonFields": Object {},
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"typeName": "Cloudwatch",
"url": "",
"user": "",
"withCredentials": false,
}
}
/>
<BasicSettings
dataSourceName="gdev-cloudwatch"
isDefault={false}
onDefaultChange={[Function]}
onNameChange={[Function]}
/>
<PluginSettings
dataSource={
Object {
"access": "",
"basicAuth": false,
"basicAuthPassword": "",
"basicAuthUser": "",
"database": "",
"id": 13,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "gdev-cloudwatch",
"orgId": 1,
"password": "",
"readOnly": true,
"secureJsonFields": Object {},
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"typeName": "Cloudwatch",
"url": "",
"user": "",
"withCredentials": false,
}
}
dataSourceMeta={
Object {
"baseUrl": "path/to/plugin",
"defaultNavUrl": "some/url",
"enabled": false,
"hasUpdate": false,
"id": "1",
"info": Object {
"author": Object {
"name": "Grafana Labs",
"url": "url/to/GrafanaLabs",
},
"description": "pretty decent plugin",
"links": Array [
Object {
"name": "project",
"url": "one link",
},
],
"logos": Object {
"large": "large/logo",
"small": "small/logo",
},
"screenshots": Array [
Object {
"name": "test",
"path": "screenshot",
},
],
"updated": "2018-09-26",
"version": "1",
},
"latestVersion": "1",
"module": "path/to/module",
"name": "pretty cool plugin 1",
"pinned": false,
"type": "panel",
}
}
onModelChange={[Function]}
plugin={
DataSourcePlugin {
"DataSourceClass": Object {},
"components": Object {},
"meta": Object {},
}
}
/>
<div
className="gf-form-group"
/>
<ButtonRow
isReadOnly={true}
onDelete={[Function]}
onSubmit={[Function]}
onTest={[Function]}
/>
</form>
</div>
</PageContents>
</Page>
`;
exports[`Render should render loader 1`] = `
<Page
navModel={Object {}}
>
<PageContents
isLoading={true}
/>
</Page>
`;