mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Make importDataSourcePlugin cancelable (#21430)
* make importDataSourcePlugin cancelable * fix imported plugin assignment * init datasource plugin to redux * remove commented * testDataSource to redux * add err console log * isTesting is never used * tests, loadError type * more tests, testingStatus obj
This commit is contained in:
@@ -24,6 +24,8 @@ const setup = (propOverrides?: object) => {
|
||||
loadDataSource: jest.fn(),
|
||||
setDataSourceName,
|
||||
updateDataSource: jest.fn(),
|
||||
initDataSourceSettings: jest.fn(),
|
||||
testDataSource: jest.fn(),
|
||||
setIsDefault,
|
||||
dataSourceLoaded,
|
||||
query: {},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { connect } from 'react-redux';
|
||||
import isString from 'lodash/isString';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
// Components
|
||||
@@ -11,11 +10,15 @@ import BasicSettings from './BasicSettings';
|
||||
import ButtonRow from './ButtonRow';
|
||||
// Services & Utils
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
// Actions & selectors
|
||||
import { getDataSource, getDataSourceMeta } from '../state/selectors';
|
||||
import { deleteDataSource, loadDataSource, updateDataSource } from '../state/actions';
|
||||
import {
|
||||
deleteDataSource,
|
||||
loadDataSource,
|
||||
updateDataSource,
|
||||
initDataSourceSettings,
|
||||
testDataSource,
|
||||
} from '../state/actions';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { getRouteParamsId } from 'app/core/selectors/location';
|
||||
// Types
|
||||
@@ -24,8 +27,8 @@ import { UrlQueryMap } from '@grafana/runtime';
|
||||
import { DataSourcePluginMeta, DataSourceSettings, NavModel } from '@grafana/data';
|
||||
import { getDataSourceLoadingNav } from '../state/navModel';
|
||||
import PluginStateinfo from 'app/features/plugins/PluginStateInfo';
|
||||
import { importDataSourcePlugin } from 'app/features/plugins/plugin_loader';
|
||||
import { dataSourceLoaded, setDataSourceName, setIsDefault } from '../state/reducers';
|
||||
import { connectWithCleanUp } from 'app/core/components/connectWithCleanUp';
|
||||
|
||||
export interface Props {
|
||||
navModel: NavModel;
|
||||
@@ -38,55 +41,22 @@ export interface Props {
|
||||
updateDataSource: typeof updateDataSource;
|
||||
setIsDefault: typeof setIsDefault;
|
||||
dataSourceLoaded: typeof dataSourceLoaded;
|
||||
initDataSourceSettings: typeof initDataSourceSettings;
|
||||
testDataSource: typeof testDataSource;
|
||||
plugin?: GenericDataSourcePlugin;
|
||||
query: UrlQueryMap;
|
||||
page?: string;
|
||||
testingStatus?: {
|
||||
message?: string;
|
||||
status?: string;
|
||||
};
|
||||
loadError?: Error | string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
plugin?: GenericDataSourcePlugin;
|
||||
isTesting?: boolean;
|
||||
testingMessage?: string;
|
||||
testingStatus?: string;
|
||||
loadError?: any;
|
||||
}
|
||||
|
||||
export class DataSourceSettingsPage extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
plugin: props.plugin,
|
||||
};
|
||||
}
|
||||
|
||||
async loadPlugin(pluginId?: string) {
|
||||
const { dataSourceMeta } = this.props;
|
||||
let importedPlugin: GenericDataSourcePlugin;
|
||||
|
||||
try {
|
||||
importedPlugin = await importDataSourcePlugin(dataSourceMeta);
|
||||
} catch (e) {
|
||||
console.log('Failed to import plugin module', e);
|
||||
}
|
||||
|
||||
this.setState({ plugin: importedPlugin });
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const { loadDataSource, pageId } = this.props;
|
||||
if (isNaN(pageId)) {
|
||||
this.setState({ loadError: 'Invalid ID' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await loadDataSource(pageId);
|
||||
if (!this.state.plugin) {
|
||||
await this.loadPlugin();
|
||||
}
|
||||
} catch (err) {
|
||||
this.setState({ loadError: err });
|
||||
}
|
||||
export class DataSourceSettingsPage extends PureComponent<Props> {
|
||||
componentDidMount() {
|
||||
const { initDataSourceSettings, pageId } = this.props;
|
||||
initDataSourceSettings(pageId);
|
||||
}
|
||||
|
||||
onSubmit = async (evt: React.FormEvent<HTMLFormElement>) => {
|
||||
@@ -136,40 +106,9 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
async testDataSource() {
|
||||
const dsApi = await getDatasourceSrv().get(this.props.dataSource.name);
|
||||
|
||||
if (!dsApi.testDatasource) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ isTesting: true, testingMessage: 'Testing...', testingStatus: 'info' });
|
||||
|
||||
backendSrv.withNoBackendCache(async () => {
|
||||
try {
|
||||
const result = await dsApi.testDatasource();
|
||||
|
||||
this.setState({
|
||||
isTesting: false,
|
||||
testingStatus: result.status,
|
||||
testingMessage: result.message,
|
||||
});
|
||||
} catch (err) {
|
||||
let message = '';
|
||||
|
||||
if (err.statusText) {
|
||||
message = 'HTTP Error ' + err.statusText;
|
||||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isTesting: false,
|
||||
testingStatus: 'error',
|
||||
testingMessage: message,
|
||||
});
|
||||
}
|
||||
});
|
||||
testDataSource() {
|
||||
const { dataSource, testDataSource } = this.props;
|
||||
testDataSource(dataSource.name);
|
||||
}
|
||||
|
||||
get hasDataSource() {
|
||||
@@ -218,7 +157,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
renderConfigPageBody(page: string) {
|
||||
const { plugin } = this.state;
|
||||
const { plugin } = this.props;
|
||||
if (!plugin || !plugin.configPages) {
|
||||
return null; // still loading
|
||||
}
|
||||
@@ -233,8 +172,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
renderSettings() {
|
||||
const { dataSourceMeta, setDataSourceName, setIsDefault, dataSource } = this.props;
|
||||
const { testingMessage, testingStatus, plugin } = this.state;
|
||||
const { dataSourceMeta, setDataSourceName, setIsDefault, dataSource, testingStatus, plugin } = this.props;
|
||||
|
||||
return (
|
||||
<form onSubmit={this.onSubmit}>
|
||||
@@ -265,10 +203,10 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
|
||||
)}
|
||||
|
||||
<div className="gf-form-group">
|
||||
{testingMessage && (
|
||||
<div className={`alert-${testingStatus} alert`} aria-label={e2e.pages.DataSource.selectors.alert}>
|
||||
{testingStatus && testingStatus.message && (
|
||||
<div className={`alert-${testingStatus.status} alert`} aria-label={e2e.pages.DataSource.selectors.alert}>
|
||||
<div className="alert-icon">
|
||||
{testingStatus === 'error' ? (
|
||||
{testingStatus.status === 'error' ? (
|
||||
<i className="fa fa-exclamation-triangle" />
|
||||
) : (
|
||||
<i className="fa fa-check" />
|
||||
@@ -276,7 +214,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
|
||||
</div>
|
||||
<div className="alert-body">
|
||||
<div className="alert-title" aria-label={e2e.pages.DataSource.selectors.alertMessage}>
|
||||
{testingMessage}
|
||||
{testingStatus.message}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -294,8 +232,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { navModel, page } = this.props;
|
||||
const { loadError } = this.state;
|
||||
const { navModel, page, loadError } = this.props;
|
||||
|
||||
if (loadError) {
|
||||
return this.renderLoadError(loadError);
|
||||
@@ -315,6 +252,7 @@ function mapStateToProps(state: StoreState) {
|
||||
const pageId = getRouteParamsId(state.location);
|
||||
const dataSource = getDataSource(state.dataSources, pageId);
|
||||
const page = state.location.query.page as string;
|
||||
const { plugin, loadError, testingStatus } = state.dataSourceSettings;
|
||||
|
||||
return {
|
||||
navModel: getNavModel(
|
||||
@@ -327,6 +265,9 @@ function mapStateToProps(state: StoreState) {
|
||||
pageId: pageId,
|
||||
query: state.location.query,
|
||||
page,
|
||||
plugin,
|
||||
loadError,
|
||||
testingStatus,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -337,6 +278,10 @@ const mapDispatchToProps = {
|
||||
updateDataSource,
|
||||
setIsDefault,
|
||||
dataSourceLoaded,
|
||||
initDataSourceSettings,
|
||||
testDataSource,
|
||||
};
|
||||
|
||||
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DataSourceSettingsPage));
|
||||
export default hot(module)(
|
||||
connectWithCleanUp(mapStateToProps, mapDispatchToProps, state => state.dataSourceSettings)(DataSourceSettingsPage)
|
||||
);
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
import { findNewName, nameExits } from './actions';
|
||||
import { findNewName, nameExits, InitDataSourceSettingDependencies, testDataSource } from './actions';
|
||||
import { getMockPlugin, getMockPlugins } from '../../plugins/__mocks__/pluginMocks';
|
||||
import { thunkTester } from 'test/core/thunk/thunkTester';
|
||||
import {
|
||||
initDataSourceSettingsSucceeded,
|
||||
initDataSourceSettingsFailed,
|
||||
testDataSourceStarting,
|
||||
testDataSourceSucceeded,
|
||||
testDataSourceFailed,
|
||||
} from './reducers';
|
||||
import { initDataSourceSettings } from '../state/actions';
|
||||
import { ThunkResult, ThunkDispatch } from 'app/types';
|
||||
import { GenericDataSourcePlugin } from '../settings/PluginSettings';
|
||||
import * as DatasourceSrv from 'app/features/plugins/datasource_srv';
|
||||
|
||||
jest.mock('app/features/plugins/datasource_srv');
|
||||
const getDatasourceSrvMock = (DatasourceSrv.getDatasourceSrv as any) as jest.Mock<DatasourceSrv.DatasourceSrv>;
|
||||
|
||||
describe('Name exists', () => {
|
||||
const plugins = getMockPlugins(5);
|
||||
@@ -42,3 +57,129 @@ describe('Find new name', () => {
|
||||
expect(findNewName(plugins, name)).toEqual('pretty cool plugin-');
|
||||
});
|
||||
});
|
||||
|
||||
describe('initDataSourceSettings', () => {
|
||||
describe('when pageId is not a number', () => {
|
||||
it('then initDataSourceSettingsFailed should be dispatched', async () => {
|
||||
const dispatchedActions = await thunkTester({})
|
||||
.givenThunk(initDataSourceSettings)
|
||||
.whenThunkIsDispatched('some page');
|
||||
|
||||
expect(dispatchedActions).toEqual([initDataSourceSettingsFailed(new Error('Invalid ID'))]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when pageId is a number', () => {
|
||||
it('then initDataSourceSettingsSucceeded should be dispatched', async () => {
|
||||
const thunkMock = (): ThunkResult<void> => (dispatch: ThunkDispatch, getState) => {};
|
||||
const dataSource = { type: 'app' };
|
||||
const dataSourceMeta = { id: 'some id' };
|
||||
const dependencies: InitDataSourceSettingDependencies = {
|
||||
loadDataSource: jest.fn(thunkMock),
|
||||
getDataSource: jest.fn().mockReturnValue(dataSource),
|
||||
getDataSourceMeta: jest.fn().mockReturnValue(dataSourceMeta),
|
||||
importDataSourcePlugin: jest.fn().mockReturnValue({} as GenericDataSourcePlugin),
|
||||
};
|
||||
const state = {
|
||||
dataSourceSettings: {},
|
||||
dataSources: {},
|
||||
};
|
||||
const dispatchedActions = await thunkTester(state)
|
||||
.givenThunk(initDataSourceSettings)
|
||||
.whenThunkIsDispatched(256, dependencies);
|
||||
|
||||
expect(dispatchedActions).toEqual([initDataSourceSettingsSucceeded({} as GenericDataSourcePlugin)]);
|
||||
expect(dependencies.loadDataSource).toHaveBeenCalledTimes(1);
|
||||
expect(dependencies.loadDataSource).toHaveBeenCalledWith(256);
|
||||
|
||||
expect(dependencies.getDataSource).toHaveBeenCalledTimes(1);
|
||||
expect(dependencies.getDataSource).toHaveBeenCalledWith({}, 256);
|
||||
|
||||
expect(dependencies.getDataSourceMeta).toHaveBeenCalledTimes(1);
|
||||
expect(dependencies.getDataSourceMeta).toHaveBeenCalledWith({}, 'app');
|
||||
|
||||
expect(dependencies.importDataSourcePlugin).toHaveBeenCalledTimes(1);
|
||||
expect(dependencies.importDataSourcePlugin).toHaveBeenCalledWith(dataSourceMeta);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when plugin loading fails', () => {
|
||||
it('then initDataSourceSettingsFailed should be dispatched', async () => {
|
||||
const dependencies: InitDataSourceSettingDependencies = {
|
||||
loadDataSource: jest.fn().mockImplementation(() => {
|
||||
throw new Error('Error loading plugin');
|
||||
}),
|
||||
getDataSource: jest.fn(),
|
||||
getDataSourceMeta: jest.fn(),
|
||||
importDataSourcePlugin: jest.fn(),
|
||||
};
|
||||
const state = {
|
||||
dataSourceSettings: {},
|
||||
dataSources: {},
|
||||
};
|
||||
const dispatchedActions = await thunkTester(state)
|
||||
.givenThunk(initDataSourceSettings)
|
||||
.whenThunkIsDispatched(301, dependencies);
|
||||
|
||||
expect(dispatchedActions).toEqual([initDataSourceSettingsFailed(new Error('Error loading plugin'))]);
|
||||
expect(dependencies.loadDataSource).toHaveBeenCalledTimes(1);
|
||||
expect(dependencies.loadDataSource).toHaveBeenCalledWith(301);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('testDataSource', () => {
|
||||
describe('when a datasource is tested', () => {
|
||||
it('then testDataSourceStarting and testDataSourceSucceeded should be dispatched', async () => {
|
||||
getDatasourceSrvMock.mockImplementation(
|
||||
() =>
|
||||
({
|
||||
get: jest.fn().mockReturnValue({
|
||||
testDatasource: jest.fn().mockReturnValue({
|
||||
status: '',
|
||||
message: '',
|
||||
}),
|
||||
}),
|
||||
} as any)
|
||||
);
|
||||
const state = {
|
||||
testingStatus: {
|
||||
status: '',
|
||||
message: '',
|
||||
},
|
||||
};
|
||||
const dispatchedActions = await thunkTester(state)
|
||||
.givenThunk(testDataSource)
|
||||
.whenThunkIsDispatched('Azure Monitor');
|
||||
|
||||
expect(dispatchedActions).toEqual([testDataSourceStarting(), testDataSourceSucceeded(state.testingStatus)]);
|
||||
});
|
||||
|
||||
it('then testDataSourceFailed should be dispatched', async () => {
|
||||
getDatasourceSrvMock.mockImplementation(
|
||||
() =>
|
||||
({
|
||||
get: jest.fn().mockReturnValue({
|
||||
testDatasource: jest.fn().mockImplementation(() => {
|
||||
throw new Error('Error testing datasource');
|
||||
}),
|
||||
}),
|
||||
} as any)
|
||||
);
|
||||
const result = {
|
||||
message: 'Error testing datasource',
|
||||
};
|
||||
const state = {
|
||||
testingStatus: {
|
||||
message: '',
|
||||
status: '',
|
||||
},
|
||||
};
|
||||
const dispatchedActions = await thunkTester(state)
|
||||
.givenThunk(testDataSource)
|
||||
.whenThunkIsDispatched('Azure Monitor');
|
||||
|
||||
expect(dispatchedActions).toEqual([testDataSourceStarting(), testDataSourceFailed(result)]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import config from '../../../core/config';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { updateLocation, updateNavIndex } from 'app/core/actions';
|
||||
import { buildNavModel } from './navModel';
|
||||
import { DataSourcePluginMeta, DataSourceSettings } from '@grafana/data';
|
||||
import { DataSourcePluginCategory, ThunkResult } from 'app/types';
|
||||
import { DataSourcePluginCategory, ThunkResult, ThunkDispatch } from 'app/types';
|
||||
import { getPluginSettings } from 'app/features/plugins/PluginSettingsCache';
|
||||
import { importDataSourcePlugin } from 'app/features/plugins/plugin_loader';
|
||||
import {
|
||||
@@ -13,14 +13,90 @@ import {
|
||||
dataSourcePluginsLoad,
|
||||
dataSourcePluginsLoaded,
|
||||
dataSourcesLoaded,
|
||||
initDataSourceSettingsFailed,
|
||||
initDataSourceSettingsSucceeded,
|
||||
testDataSourceStarting,
|
||||
testDataSourceSucceeded,
|
||||
testDataSourceFailed,
|
||||
} from './reducers';
|
||||
import { buildCategories } from './buildCategories';
|
||||
import { getDataSource, getDataSourceMeta } from './selectors';
|
||||
|
||||
export interface DataSourceTypesLoadedPayload {
|
||||
plugins: DataSourcePluginMeta[];
|
||||
categories: DataSourcePluginCategory[];
|
||||
}
|
||||
|
||||
export interface InitDataSourceSettingDependencies {
|
||||
loadDataSource: typeof loadDataSource;
|
||||
getDataSource: typeof getDataSource;
|
||||
getDataSourceMeta: typeof getDataSourceMeta;
|
||||
importDataSourcePlugin: typeof importDataSourcePlugin;
|
||||
}
|
||||
|
||||
export const initDataSourceSettings = (
|
||||
pageId: number,
|
||||
dependencies: InitDataSourceSettingDependencies = {
|
||||
loadDataSource,
|
||||
getDataSource,
|
||||
getDataSourceMeta,
|
||||
importDataSourcePlugin,
|
||||
}
|
||||
): ThunkResult<void> => {
|
||||
return async (dispatch: ThunkDispatch, getState) => {
|
||||
if (isNaN(pageId)) {
|
||||
dispatch(initDataSourceSettingsFailed(new Error('Invalid ID')));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await dispatch(dependencies.loadDataSource(pageId));
|
||||
if (getState().dataSourceSettings.plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataSource = dependencies.getDataSource(getState().dataSources, pageId);
|
||||
const dataSourceMeta = dependencies.getDataSourceMeta(getState().dataSources, dataSource.type);
|
||||
const importedPlugin = await dependencies.importDataSourcePlugin(dataSourceMeta);
|
||||
|
||||
dispatch(initDataSourceSettingsSucceeded(importedPlugin));
|
||||
} catch (err) {
|
||||
console.log('Failed to import plugin module', err);
|
||||
dispatch(initDataSourceSettingsFailed(err));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const testDataSource = (dataSourceName: string): ThunkResult<void> => {
|
||||
return async (dispatch: ThunkDispatch, getState) => {
|
||||
const dsApi = await getDatasourceSrv().get(dataSourceName);
|
||||
|
||||
if (!dsApi.testDatasource) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(testDataSourceStarting());
|
||||
|
||||
getBackendSrv().withNoBackendCache(async () => {
|
||||
try {
|
||||
const result = await dsApi.testDatasource();
|
||||
|
||||
dispatch(testDataSourceSucceeded(result));
|
||||
} catch (err) {
|
||||
let message = '';
|
||||
|
||||
if (err.statusText) {
|
||||
message = 'HTTP Error ' + err.statusText;
|
||||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
|
||||
dispatch(testDataSourceFailed({ message }));
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function loadDataSources(): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
const response = await getBackendSrv().get('/api/datasources');
|
||||
@@ -123,7 +199,7 @@ export function findNewName(dataSources: ItemWithName[], name: string) {
|
||||
function updateFrontendSettings() {
|
||||
return getBackendSrv()
|
||||
.get('/api/frontend/settings')
|
||||
.then(settings => {
|
||||
.then((settings: any) => {
|
||||
config.datasources = settings.datasources;
|
||||
config.defaultDatasource = settings.defaultDatasource;
|
||||
getDatasourceSrv().init();
|
||||
|
||||
@@ -12,11 +12,16 @@ import {
|
||||
setDataSourcesSearchQuery,
|
||||
setDataSourceTypeSearchQuery,
|
||||
setIsDefault,
|
||||
dataSourceSettingsReducer,
|
||||
initialDataSourceSettingsState,
|
||||
initDataSourceSettingsSucceeded,
|
||||
initDataSourceSettingsFailed,
|
||||
} from './reducers';
|
||||
import { getMockDataSource, getMockDataSources } from '../__mocks__/dataSourcesMocks';
|
||||
import { LayoutModes } from 'app/core/components/LayoutSelector/LayoutSelector';
|
||||
import { DataSourcesState } from 'app/types';
|
||||
import { DataSourcesState, DataSourceSettingsState } from 'app/types';
|
||||
import { PluginMeta, PluginMetaInfo, PluginType } from '@grafana/data';
|
||||
import { GenericDataSourcePlugin } from '../settings/PluginSettings';
|
||||
|
||||
const mockPlugin = () =>
|
||||
({
|
||||
@@ -136,3 +141,34 @@ describe('dataSourcesReducer', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dataSourceSettingsReducer', () => {
|
||||
describe('when initDataSourceSettingsSucceeded is dispatched', () => {
|
||||
it('then state should be correct', () => {
|
||||
reducerTester<DataSourceSettingsState>()
|
||||
.givenReducer(dataSourceSettingsReducer, { ...initialDataSourceSettingsState })
|
||||
.whenActionIsDispatched(initDataSourceSettingsSucceeded({} as GenericDataSourcePlugin))
|
||||
.thenStateShouldEqual({
|
||||
...initialDataSourceSettingsState,
|
||||
plugin: {} as GenericDataSourcePlugin,
|
||||
loadError: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when initDataSourceSettingsFailed is dispatched', () => {
|
||||
it('then state should be correct', () => {
|
||||
reducerTester<DataSourceSettingsState>()
|
||||
.givenReducer(dataSourceSettingsReducer, {
|
||||
...initialDataSourceSettingsState,
|
||||
plugin: {} as GenericDataSourcePlugin,
|
||||
})
|
||||
.whenActionIsDispatched(initDataSourceSettingsFailed(new Error('Some error')))
|
||||
.thenStatePredicateShouldEqual(resultingState => {
|
||||
expect(resultingState.plugin).toEqual(null);
|
||||
expect(resultingState.loadError).toEqual('Some error');
|
||||
return true;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { AnyAction, createAction } from '@reduxjs/toolkit';
|
||||
import { DataSourcePluginMeta, DataSourceSettings } from '@grafana/data';
|
||||
|
||||
import { DataSourcesState } from 'app/types';
|
||||
import { DataSourcesState, DataSourceSettingsState } from 'app/types';
|
||||
import { LayoutMode, LayoutModes } from 'app/core/components/LayoutSelector/LayoutSelector';
|
||||
import { DataSourceTypesLoadedPayload } from './actions';
|
||||
import { GenericDataSourcePlugin } from '../settings/PluginSettings';
|
||||
|
||||
export const initialState: DataSourcesState = {
|
||||
dataSources: [],
|
||||
@@ -94,6 +95,76 @@ export const dataSourcesReducer = (state: DataSourcesState = initialState, actio
|
||||
return state;
|
||||
};
|
||||
|
||||
export const initialDataSourceSettingsState: DataSourceSettingsState = {
|
||||
testingStatus: {
|
||||
status: null,
|
||||
message: null,
|
||||
},
|
||||
loadError: null,
|
||||
plugin: null,
|
||||
};
|
||||
|
||||
export const initDataSourceSettingsSucceeded = createAction<GenericDataSourcePlugin>(
|
||||
'dataSourceSettings/initDataSourceSettingsSucceeded'
|
||||
);
|
||||
|
||||
export const initDataSourceSettingsFailed = createAction<Error>('dataSourceSettings/initDataSourceSettingsFailed');
|
||||
|
||||
export const testDataSourceStarting = createAction<undefined>('dataSourceSettings/testDataSourceStarting');
|
||||
|
||||
export const testDataSourceSucceeded = createAction<{
|
||||
status: string;
|
||||
message: string;
|
||||
}>('dataSourceSettings/testDataSourceSucceeded');
|
||||
|
||||
export const testDataSourceFailed = createAction<{ message: string }>('dataSourceSettings/testDataSourceFailed');
|
||||
|
||||
export const dataSourceSettingsReducer = (
|
||||
state: DataSourceSettingsState = initialDataSourceSettingsState,
|
||||
action: AnyAction
|
||||
): DataSourceSettingsState => {
|
||||
if (initDataSourceSettingsSucceeded.match(action)) {
|
||||
return { ...state, plugin: action.payload, loadError: null };
|
||||
}
|
||||
|
||||
if (initDataSourceSettingsFailed.match(action)) {
|
||||
return { ...state, plugin: null, loadError: action.payload.message };
|
||||
}
|
||||
|
||||
if (testDataSourceStarting.match(action)) {
|
||||
return {
|
||||
...state,
|
||||
testingStatus: {
|
||||
message: 'Testing...',
|
||||
status: 'info',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (testDataSourceSucceeded.match(action)) {
|
||||
return {
|
||||
...state,
|
||||
testingStatus: {
|
||||
status: action.payload.status,
|
||||
message: action.payload.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (testDataSourceFailed.match(action)) {
|
||||
return {
|
||||
...state,
|
||||
testingStatus: {
|
||||
status: 'error',
|
||||
message: action.payload.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
export default {
|
||||
dataSources: dataSourcesReducer,
|
||||
dataSourceSettings: dataSourceSettingsReducer,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { LayoutMode } from '../core/components/LayoutSelector/LayoutSelector';
|
||||
import { DataSourceSettings, DataSourcePluginMeta } from '@grafana/data';
|
||||
import { GenericDataSourcePlugin } from 'app/features/datasources/settings/PluginSettings';
|
||||
|
||||
export interface DataSourcesState {
|
||||
dataSources: DataSourceSettings[];
|
||||
@@ -15,6 +16,15 @@ export interface DataSourcesState {
|
||||
categories: DataSourcePluginCategory[];
|
||||
}
|
||||
|
||||
export interface DataSourceSettingsState {
|
||||
plugin?: GenericDataSourcePlugin;
|
||||
testingStatus?: {
|
||||
message?: string;
|
||||
status?: string;
|
||||
};
|
||||
loadError?: string;
|
||||
}
|
||||
|
||||
export interface DataSourcePluginCategory {
|
||||
id: string;
|
||||
title: string;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { AlertRulesState } from './alerting';
|
||||
import { TeamsState, TeamState } from './teams';
|
||||
import { FolderState } from './folders';
|
||||
import { DashboardState } from './dashboard';
|
||||
import { DataSourcesState } from './datasources';
|
||||
import { DataSourcesState, DataSourceSettingsState } from './datasources';
|
||||
import { ExploreState } from './explore';
|
||||
import { UsersState, UserState, UserAdminState } from './user';
|
||||
import { OrganizationState } from './organization';
|
||||
@@ -28,6 +28,7 @@ export interface StoreState {
|
||||
dashboard: DashboardState;
|
||||
panelEditor: PanelEditorState;
|
||||
dataSources: DataSourcesState;
|
||||
dataSourceSettings: DataSourceSettingsState;
|
||||
explore: ExploreState;
|
||||
users: UsersState;
|
||||
organization: OrganizationState;
|
||||
|
||||
Reference in New Issue
Block a user