Commit b3d5e678 by Shavonn Brown Committed by GitHub

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
parent c0b839ef
...@@ -24,6 +24,8 @@ const setup = (propOverrides?: object) => { ...@@ -24,6 +24,8 @@ const setup = (propOverrides?: object) => {
loadDataSource: jest.fn(), loadDataSource: jest.fn(),
setDataSourceName, setDataSourceName,
updateDataSource: jest.fn(), updateDataSource: jest.fn(),
initDataSourceSettings: jest.fn(),
testDataSource: jest.fn(),
setIsDefault, setIsDefault,
dataSourceLoaded, dataSourceLoaded,
query: {}, query: {},
......
// Libraries // Libraries
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
import isString from 'lodash/isString'; import isString from 'lodash/isString';
import { e2e } from '@grafana/e2e'; import { e2e } from '@grafana/e2e';
// Components // Components
...@@ -11,11 +10,15 @@ import BasicSettings from './BasicSettings'; ...@@ -11,11 +10,15 @@ import BasicSettings from './BasicSettings';
import ButtonRow from './ButtonRow'; import ButtonRow from './ButtonRow';
// Services & Utils // Services & Utils
import appEvents from 'app/core/app_events'; 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 // Actions & selectors
import { getDataSource, getDataSourceMeta } from '../state/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 { getNavModel } from 'app/core/selectors/navModel';
import { getRouteParamsId } from 'app/core/selectors/location'; import { getRouteParamsId } from 'app/core/selectors/location';
// Types // Types
...@@ -24,8 +27,8 @@ import { UrlQueryMap } from '@grafana/runtime'; ...@@ -24,8 +27,8 @@ import { UrlQueryMap } from '@grafana/runtime';
import { DataSourcePluginMeta, DataSourceSettings, NavModel } from '@grafana/data'; import { DataSourcePluginMeta, DataSourceSettings, NavModel } from '@grafana/data';
import { getDataSourceLoadingNav } from '../state/navModel'; import { getDataSourceLoadingNav } from '../state/navModel';
import PluginStateinfo from 'app/features/plugins/PluginStateInfo'; import PluginStateinfo from 'app/features/plugins/PluginStateInfo';
import { importDataSourcePlugin } from 'app/features/plugins/plugin_loader';
import { dataSourceLoaded, setDataSourceName, setIsDefault } from '../state/reducers'; import { dataSourceLoaded, setDataSourceName, setIsDefault } from '../state/reducers';
import { connectWithCleanUp } from 'app/core/components/connectWithCleanUp';
export interface Props { export interface Props {
navModel: NavModel; navModel: NavModel;
...@@ -38,55 +41,22 @@ export interface Props { ...@@ -38,55 +41,22 @@ export interface Props {
updateDataSource: typeof updateDataSource; updateDataSource: typeof updateDataSource;
setIsDefault: typeof setIsDefault; setIsDefault: typeof setIsDefault;
dataSourceLoaded: typeof dataSourceLoaded; dataSourceLoaded: typeof dataSourceLoaded;
initDataSourceSettings: typeof initDataSourceSettings;
testDataSource: typeof testDataSource;
plugin?: GenericDataSourcePlugin; plugin?: GenericDataSourcePlugin;
query: UrlQueryMap; query: UrlQueryMap;
page?: string; page?: string;
testingStatus?: {
message?: string;
status?: string;
};
loadError?: Error | string;
} }
interface State { export class DataSourceSettingsPage extends PureComponent<Props> {
plugin?: GenericDataSourcePlugin; componentDidMount() {
isTesting?: boolean; const { initDataSourceSettings, pageId } = this.props;
testingMessage?: string; initDataSourceSettings(pageId);
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 });
}
} }
onSubmit = async (evt: React.FormEvent<HTMLFormElement>) => { onSubmit = async (evt: React.FormEvent<HTMLFormElement>) => {
...@@ -136,40 +106,9 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> { ...@@ -136,40 +106,9 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
); );
} }
async testDataSource() { testDataSource() {
const dsApi = await getDatasourceSrv().get(this.props.dataSource.name); const { dataSource, testDataSource } = this.props;
testDataSource(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,
});
}
});
} }
get hasDataSource() { get hasDataSource() {
...@@ -218,7 +157,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> { ...@@ -218,7 +157,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
} }
renderConfigPageBody(page: string) { renderConfigPageBody(page: string) {
const { plugin } = this.state; const { plugin } = this.props;
if (!plugin || !plugin.configPages) { if (!plugin || !plugin.configPages) {
return null; // still loading return null; // still loading
} }
...@@ -233,8 +172,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> { ...@@ -233,8 +172,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
} }
renderSettings() { renderSettings() {
const { dataSourceMeta, setDataSourceName, setIsDefault, dataSource } = this.props; const { dataSourceMeta, setDataSourceName, setIsDefault, dataSource, testingStatus, plugin } = this.props;
const { testingMessage, testingStatus, plugin } = this.state;
return ( return (
<form onSubmit={this.onSubmit}> <form onSubmit={this.onSubmit}>
...@@ -265,10 +203,10 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> { ...@@ -265,10 +203,10 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
)} )}
<div className="gf-form-group"> <div className="gf-form-group">
{testingMessage && ( {testingStatus && testingStatus.message && (
<div className={`alert-${testingStatus} alert`} aria-label={e2e.pages.DataSource.selectors.alert}> <div className={`alert-${testingStatus.status} alert`} aria-label={e2e.pages.DataSource.selectors.alert}>
<div className="alert-icon"> <div className="alert-icon">
{testingStatus === 'error' ? ( {testingStatus.status === 'error' ? (
<i className="fa fa-exclamation-triangle" /> <i className="fa fa-exclamation-triangle" />
) : ( ) : (
<i className="fa fa-check" /> <i className="fa fa-check" />
...@@ -276,7 +214,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> { ...@@ -276,7 +214,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
</div> </div>
<div className="alert-body"> <div className="alert-body">
<div className="alert-title" aria-label={e2e.pages.DataSource.selectors.alertMessage}> <div className="alert-title" aria-label={e2e.pages.DataSource.selectors.alertMessage}>
{testingMessage} {testingStatus.message}
</div> </div>
</div> </div>
</div> </div>
...@@ -294,8 +232,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> { ...@@ -294,8 +232,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
} }
render() { render() {
const { navModel, page } = this.props; const { navModel, page, loadError } = this.props;
const { loadError } = this.state;
if (loadError) { if (loadError) {
return this.renderLoadError(loadError); return this.renderLoadError(loadError);
...@@ -315,6 +252,7 @@ function mapStateToProps(state: StoreState) { ...@@ -315,6 +252,7 @@ function mapStateToProps(state: StoreState) {
const pageId = getRouteParamsId(state.location); const pageId = getRouteParamsId(state.location);
const dataSource = getDataSource(state.dataSources, pageId); const dataSource = getDataSource(state.dataSources, pageId);
const page = state.location.query.page as string; const page = state.location.query.page as string;
const { plugin, loadError, testingStatus } = state.dataSourceSettings;
return { return {
navModel: getNavModel( navModel: getNavModel(
...@@ -327,6 +265,9 @@ function mapStateToProps(state: StoreState) { ...@@ -327,6 +265,9 @@ function mapStateToProps(state: StoreState) {
pageId: pageId, pageId: pageId,
query: state.location.query, query: state.location.query,
page, page,
plugin,
loadError,
testingStatus,
}; };
} }
...@@ -337,6 +278,10 @@ const mapDispatchToProps = { ...@@ -337,6 +278,10 @@ const mapDispatchToProps = {
updateDataSource, updateDataSource,
setIsDefault, setIsDefault,
dataSourceLoaded, dataSourceLoaded,
initDataSourceSettings,
testDataSource,
}; };
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DataSourceSettingsPage)); export default hot(module)(
connectWithCleanUp(mapStateToProps, mapDispatchToProps, state => state.dataSourceSettings)(DataSourceSettingsPage)
);
import { findNewName, nameExits } from './actions'; import { findNewName, nameExits, InitDataSourceSettingDependencies, testDataSource } from './actions';
import { getMockPlugin, getMockPlugins } from '../../plugins/__mocks__/pluginMocks'; 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', () => { describe('Name exists', () => {
const plugins = getMockPlugins(5); const plugins = getMockPlugins(5);
...@@ -42,3 +57,129 @@ describe('Find new name', () => { ...@@ -42,3 +57,129 @@ describe('Find new name', () => {
expect(findNewName(plugins, name)).toEqual('pretty cool plugin-'); 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)]);
});
});
});
import config from '../../../core/config'; 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 { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { updateLocation, updateNavIndex } from 'app/core/actions'; import { updateLocation, updateNavIndex } from 'app/core/actions';
import { buildNavModel } from './navModel'; import { buildNavModel } from './navModel';
import { DataSourcePluginMeta, DataSourceSettings } from '@grafana/data'; 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 { getPluginSettings } from 'app/features/plugins/PluginSettingsCache';
import { importDataSourcePlugin } from 'app/features/plugins/plugin_loader'; import { importDataSourcePlugin } from 'app/features/plugins/plugin_loader';
import { import {
...@@ -13,14 +13,90 @@ import { ...@@ -13,14 +13,90 @@ import {
dataSourcePluginsLoad, dataSourcePluginsLoad,
dataSourcePluginsLoaded, dataSourcePluginsLoaded,
dataSourcesLoaded, dataSourcesLoaded,
initDataSourceSettingsFailed,
initDataSourceSettingsSucceeded,
testDataSourceStarting,
testDataSourceSucceeded,
testDataSourceFailed,
} from './reducers'; } from './reducers';
import { buildCategories } from './buildCategories'; import { buildCategories } from './buildCategories';
import { getDataSource, getDataSourceMeta } from './selectors';
export interface DataSourceTypesLoadedPayload { export interface DataSourceTypesLoadedPayload {
plugins: DataSourcePluginMeta[]; plugins: DataSourcePluginMeta[];
categories: DataSourcePluginCategory[]; 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> { export function loadDataSources(): ThunkResult<void> {
return async dispatch => { return async dispatch => {
const response = await getBackendSrv().get('/api/datasources'); const response = await getBackendSrv().get('/api/datasources');
...@@ -123,7 +199,7 @@ export function findNewName(dataSources: ItemWithName[], name: string) { ...@@ -123,7 +199,7 @@ export function findNewName(dataSources: ItemWithName[], name: string) {
function updateFrontendSettings() { function updateFrontendSettings() {
return getBackendSrv() return getBackendSrv()
.get('/api/frontend/settings') .get('/api/frontend/settings')
.then(settings => { .then((settings: any) => {
config.datasources = settings.datasources; config.datasources = settings.datasources;
config.defaultDatasource = settings.defaultDatasource; config.defaultDatasource = settings.defaultDatasource;
getDatasourceSrv().init(); getDatasourceSrv().init();
......
...@@ -12,11 +12,16 @@ import { ...@@ -12,11 +12,16 @@ import {
setDataSourcesSearchQuery, setDataSourcesSearchQuery,
setDataSourceTypeSearchQuery, setDataSourceTypeSearchQuery,
setIsDefault, setIsDefault,
dataSourceSettingsReducer,
initialDataSourceSettingsState,
initDataSourceSettingsSucceeded,
initDataSourceSettingsFailed,
} from './reducers'; } from './reducers';
import { getMockDataSource, getMockDataSources } from '../__mocks__/dataSourcesMocks'; import { getMockDataSource, getMockDataSources } from '../__mocks__/dataSourcesMocks';
import { LayoutModes } from 'app/core/components/LayoutSelector/LayoutSelector'; 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 { PluginMeta, PluginMetaInfo, PluginType } from '@grafana/data';
import { GenericDataSourcePlugin } from '../settings/PluginSettings';
const mockPlugin = () => const mockPlugin = () =>
({ ({
...@@ -136,3 +141,34 @@ describe('dataSourcesReducer', () => { ...@@ -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;
});
});
});
});
import { AnyAction, createAction } from '@reduxjs/toolkit'; import { AnyAction, createAction } from '@reduxjs/toolkit';
import { DataSourcePluginMeta, DataSourceSettings } from '@grafana/data'; 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 { LayoutMode, LayoutModes } from 'app/core/components/LayoutSelector/LayoutSelector';
import { DataSourceTypesLoadedPayload } from './actions'; import { DataSourceTypesLoadedPayload } from './actions';
import { GenericDataSourcePlugin } from '../settings/PluginSettings';
export const initialState: DataSourcesState = { export const initialState: DataSourcesState = {
dataSources: [], dataSources: [],
...@@ -94,6 +95,76 @@ export const dataSourcesReducer = (state: DataSourcesState = initialState, actio ...@@ -94,6 +95,76 @@ export const dataSourcesReducer = (state: DataSourcesState = initialState, actio
return state; 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 { export default {
dataSources: dataSourcesReducer, dataSources: dataSourcesReducer,
dataSourceSettings: dataSourceSettingsReducer,
}; };
import { LayoutMode } from '../core/components/LayoutSelector/LayoutSelector'; import { LayoutMode } from '../core/components/LayoutSelector/LayoutSelector';
import { DataSourceSettings, DataSourcePluginMeta } from '@grafana/data'; import { DataSourceSettings, DataSourcePluginMeta } from '@grafana/data';
import { GenericDataSourcePlugin } from 'app/features/datasources/settings/PluginSettings';
export interface DataSourcesState { export interface DataSourcesState {
dataSources: DataSourceSettings[]; dataSources: DataSourceSettings[];
...@@ -15,6 +16,15 @@ export interface DataSourcesState { ...@@ -15,6 +16,15 @@ export interface DataSourcesState {
categories: DataSourcePluginCategory[]; categories: DataSourcePluginCategory[];
} }
export interface DataSourceSettingsState {
plugin?: GenericDataSourcePlugin;
testingStatus?: {
message?: string;
status?: string;
};
loadError?: string;
}
export interface DataSourcePluginCategory { export interface DataSourcePluginCategory {
id: string; id: string;
title: string; title: string;
......
...@@ -7,7 +7,7 @@ import { AlertRulesState } from './alerting'; ...@@ -7,7 +7,7 @@ import { AlertRulesState } from './alerting';
import { TeamsState, TeamState } from './teams'; import { TeamsState, TeamState } from './teams';
import { FolderState } from './folders'; import { FolderState } from './folders';
import { DashboardState } from './dashboard'; import { DashboardState } from './dashboard';
import { DataSourcesState } from './datasources'; import { DataSourcesState, DataSourceSettingsState } from './datasources';
import { ExploreState } from './explore'; import { ExploreState } from './explore';
import { UsersState, UserState, UserAdminState } from './user'; import { UsersState, UserState, UserAdminState } from './user';
import { OrganizationState } from './organization'; import { OrganizationState } from './organization';
...@@ -28,6 +28,7 @@ export interface StoreState { ...@@ -28,6 +28,7 @@ export interface StoreState {
dashboard: DashboardState; dashboard: DashboardState;
panelEditor: PanelEditorState; panelEditor: PanelEditorState;
dataSources: DataSourcesState; dataSources: DataSourcesState;
dataSourceSettings: DataSourceSettingsState;
explore: ExploreState; explore: ExploreState;
users: UsersState; users: UsersState;
organization: OrganizationState; organization: OrganizationState;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment