refactored how testing state was handled, using redux for this felt way to require way to much code

This commit is contained in:
Torkel Ödegaard
2018-11-07 14:28:44 -08:00
parent 8ff6bb07bc
commit 29e18fddd0
7 changed files with 95 additions and 199 deletions

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { DataSourceSettings, Props } from './DataSourceSettings'; import { DataSourceSettings, Props } from './DataSourceSettings';
import { DataSource, DataSourceTest, NavModel } from '../../../types'; import { DataSource, NavModel } from '../../../types';
import { getMockDataSource } from '../__mocks__/dataSourcesMocks'; import { getMockDataSource } from '../__mocks__/dataSourcesMocks';
import { getMockPlugin } from '../../plugins/__mocks__/pluginMocks'; import { getMockPlugin } from '../../plugins/__mocks__/pluginMocks';
@@ -15,8 +15,6 @@ const setup = (propOverrides?: object) => {
loadDataSource: jest.fn(), loadDataSource: jest.fn(),
setDataSourceName: jest.fn(), setDataSourceName: jest.fn(),
updateDataSource: jest.fn(), updateDataSource: jest.fn(),
testing: {} as DataSourceTest,
clearTesting: jest.fn(),
}; };
Object.assign(props, propOverrides); Object.assign(props, propOverrides);

View File

@@ -1,33 +1,40 @@
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 { connect } from 'react-redux';
import { DataSource, DataSourceTest, NavModel, Plugin } from 'app/types/';
import PageHeader from '../../../core/components/PageHeader/PageHeader'; import PageHeader from 'app/core/components/PageHeader/PageHeader';
import PageLoader from '../../../core/components/PageLoader/PageLoader'; import PageLoader from 'app/core/components/PageLoader/PageLoader';
import PluginSettings from './PluginSettings'; import PluginSettings from './PluginSettings';
import BasicSettings from './BasicSettings'; import BasicSettings from './BasicSettings';
import ButtonRow from './ButtonRow'; import ButtonRow from './ButtonRow';
import appEvents from '../../../core/app_events';
import { clearTesting, deleteDataSource, loadDataSource, setDataSourceName, updateDataSource } from '../state/actions'; import appEvents from 'app/core/app_events';
import { getNavModel } from '../../../core/selectors/navModel'; import { getBackendSrv } from 'app/core/services/backend_srv';
import { getRouteParamsId } from '../../../core/selectors/location'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { getDataSource, getDataSourceMeta } from '../state/selectors'; import { getDataSource, getDataSourceMeta } from '../state/selectors';
import { deleteDataSource, loadDataSource, setDataSourceName, updateDataSource } from '../state/actions';
import { getNavModel } from 'app/core/selectors/navModel';
import { getRouteParamsId } from 'app/core/selectors/location';
import { DataSource, NavModel, Plugin } from 'app/types/';
export interface Props { export interface Props {
navModel: NavModel; navModel: NavModel;
dataSource: DataSource; dataSource: DataSource;
dataSourceMeta: Plugin; dataSourceMeta: Plugin;
pageId: number; pageId: number;
testing: DataSourceTest;
deleteDataSource: typeof deleteDataSource; deleteDataSource: typeof deleteDataSource;
loadDataSource: typeof loadDataSource; loadDataSource: typeof loadDataSource;
setDataSourceName: typeof setDataSourceName; setDataSourceName: typeof setDataSourceName;
updateDataSource: typeof updateDataSource; updateDataSource: typeof updateDataSource;
clearTesting: typeof clearTesting;
} }
interface State { interface State {
dataSource: DataSource; dataSource: DataSource;
hasClosedTest: boolean; isTesting?: boolean;
testingMessage?: string;
testingStatus?: string;
} }
enum DataSourceStates { enum DataSourceStates {
@@ -36,10 +43,13 @@ enum DataSourceStates {
} }
export class DataSourceSettings extends PureComponent<Props, State> { export class DataSourceSettings extends PureComponent<Props, State> {
state = { constructor(props) {
dataSource: {} as DataSource, super(props);
hasClosedTest: false,
}; this.state = {
dataSource: {} as DataSource,
};
}
async componentDidMount() { async componentDidMount() {
const { loadDataSource, pageId } = this.props; const { loadDataSource, pageId } = this.props;
@@ -47,27 +57,12 @@ export class DataSourceSettings extends PureComponent<Props, State> {
await loadDataSource(pageId); await loadDataSource(pageId);
} }
componentDidUpdate(prevProps) { onSubmit = async event => {
const { clearTesting } = this.props;
if (!this.state.hasClosedTest && prevProps.testing.status === 'success') {
this.setState({ hasClosedTest: true });
setTimeout(() => {
clearTesting();
this.setState({ hasClosedTest: false });
}, 3000);
}
}
componentWillUnmount() {
this.props.clearTesting();
}
onSubmit = event => {
event.preventDefault(); event.preventDefault();
this.props.updateDataSource({ ...this.state.dataSource, name: this.props.dataSource.name }); await this.props.updateDataSource({ ...this.state.dataSource, name: this.props.dataSource.name });
this.testDataSource();
}; };
onDelete = () => { onDelete = () => {
@@ -131,8 +126,45 @@ export class DataSourceSettings extends PureComponent<Props, State> {
); );
} }
async testDataSource() {
const dsApi = await getDatasourceSrv().get(this.state.dataSource.name);
if (!dsApi.testDatasource) {
return;
}
this.setState({ isTesting: true, testingMessage: 'Testing...', testingStatus: 'info' });
getBackendSrv().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,
});
}
});
}
render() { render() {
const { dataSource, dataSourceMeta, navModel, testing } = this.props; const { dataSource, dataSourceMeta, navModel } = this.props;
const { testingMessage, testingStatus } = this.state;
return ( return (
<div> <div>
@@ -160,26 +192,20 @@ export class DataSourceSettings extends PureComponent<Props, State> {
)} )}
<div className="gf-form-group section"> <div className="gf-form-group section">
{testing.inProgress && ( {testingMessage && (
<h5> <div className={`alert-${testingStatus} alert`}>
Testing.... <i className="fa fa-spiner fa-spin" /> <div className="alert-icon">
</h5> {testingStatus === 'error' ? (
)} <i className="fa fa-exclamation-triangle" />
{!testing.inProgress && ) : (
testing.status && ( <i className="fa fa-check" />
<div className={`alert-${testing.status} alert`}> )}
<div className="alert-icon">
{testing.status === 'error' ? (
<i className="fa fa-exclamation-triangle" />
) : (
<i className="fa fa-check" />
)}
</div>
<div className="alert-body">
<div className="alert-title">{testing.message}</div>
</div>
</div> </div>
)} <div className="alert-body">
<div className="alert-title">{testingMessage}</div>
</div>
</div>
)}
</div> </div>
<ButtonRow <ButtonRow
@@ -205,7 +231,6 @@ function mapStateToProps(state) {
dataSource: getDataSource(state.dataSources, pageId), dataSource: getDataSource(state.dataSources, pageId),
dataSourceMeta: getDataSourceMeta(state.dataSources, dataSource.type), dataSourceMeta: getDataSourceMeta(state.dataSources, dataSource.type),
pageId: pageId, pageId: pageId,
testing: state.dataSources.testing,
}; };
} }
@@ -214,7 +239,6 @@ const mapDispatchToProps = {
loadDataSource, loadDataSource,
setDataSourceName, setDataSourceName,
updateDataSource, updateDataSource,
clearTesting,
}; };
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DataSourceSettings)); export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DataSourceSettings));

View File

@@ -1,13 +1,12 @@
import { ThunkAction } from 'redux-thunk'; import { ThunkAction } from 'redux-thunk';
import { DataSource, Plugin, StoreState } from 'app/types';
import { getBackendSrv } from '../../../core/services/backend_srv';
import { getDatasourceSrv } from '../../plugins/datasource_srv';
import { LayoutMode } from '../../../core/components/LayoutSelector/LayoutSelector';
import { updateLocation, updateNavIndex, UpdateNavIndexAction } from '../../../core/actions';
import { UpdateLocationAction } from '../../../core/actions/location';
import { buildNavModel } from './navModel';
import config from '../../../core/config'; import config from '../../../core/config';
import { getBackendSrv } from 'app/core/services/backend_srv';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { LayoutMode } from 'app/core/components/LayoutSelector/LayoutSelector';
import { updateLocation, updateNavIndex, UpdateNavIndexAction } from 'app/core/actions';
import { UpdateLocationAction } from 'app/core/actions/location';
import { buildNavModel } from './navModel';
import { DataSource, Plugin, StoreState } from 'app/types';
export enum ActionTypes { export enum ActionTypes {
LoadDataSources = 'LOAD_DATA_SOURCES', LoadDataSources = 'LOAD_DATA_SOURCES',
@@ -18,10 +17,6 @@ export enum ActionTypes {
SetDataSourcesLayoutMode = 'SET_DATA_SOURCES_LAYOUT_MODE', SetDataSourcesLayoutMode = 'SET_DATA_SOURCES_LAYOUT_MODE',
SetDataSourceTypeSearchQuery = 'SET_DATA_SOURCE_TYPE_SEARCH_QUERY', SetDataSourceTypeSearchQuery = 'SET_DATA_SOURCE_TYPE_SEARCH_QUERY',
SetDataSourceName = 'SET_DATA_SOURCE_NAME', SetDataSourceName = 'SET_DATA_SOURCE_NAME',
SetDataSourceTestingProgess = 'SET_TESTING_PROGRESS',
SetDataSourceTestingSuccess = 'SET_DATA_SOURCE_TESTING_SUCCESS',
SetDataSourceTestingFail = 'SET_DATA_SOURCE_TESTING_FAIL',
ClearTesting = 'CLEAR_TEST',
} }
interface LoadDataSourcesAction { interface LoadDataSourcesAction {
@@ -64,25 +59,6 @@ interface SetDataSourceNameAction {
payload: string; payload: string;
} }
interface SetDataSourceTestingProgessAction {
type: ActionTypes.SetDataSourceTestingProgess;
payload: boolean;
}
interface SetDataSourceTestingSuccessAction {
type: ActionTypes.SetDataSourceTestingSuccess;
payload: { status: string; message: string };
}
interface SetDataSourceTestingFailAction {
type: ActionTypes.SetDataSourceTestingFail;
payload: string;
}
interface ClearTestingAction {
type: ActionTypes.ClearTesting;
}
const dataSourcesLoaded = (dataSources: DataSource[]): LoadDataSourcesAction => ({ const dataSourcesLoaded = (dataSources: DataSource[]): LoadDataSourcesAction => ({
type: ActionTypes.LoadDataSources, type: ActionTypes.LoadDataSources,
payload: dataSources, payload: dataSources,
@@ -123,28 +99,6 @@ export const setDataSourceName = (name: string) => ({
payload: name, payload: name,
}); });
export const clearTesting = (): ClearTestingAction => ({
type: ActionTypes.ClearTesting,
});
const setDataSourceTestingProgress = (state: boolean): SetDataSourceTestingProgessAction => ({
type: ActionTypes.SetDataSourceTestingProgess,
payload: state,
});
const setDataSourceTestingSuccess = (status: string, message: string): SetDataSourceTestingSuccessAction => ({
type: ActionTypes.SetDataSourceTestingSuccess,
payload: {
status: status,
message: message,
},
});
const setDataSourceTestingFail = (message: string): SetDataSourceTestingFailAction => ({
type: ActionTypes.SetDataSourceTestingFail,
payload: message,
});
export type Action = export type Action =
| LoadDataSourcesAction | LoadDataSourcesAction
| SetDataSourcesSearchQueryAction | SetDataSourcesSearchQueryAction
@@ -155,11 +109,7 @@ export type Action =
| LoadDataSourceAction | LoadDataSourceAction
| UpdateNavIndexAction | UpdateNavIndexAction
| LoadDataSourceMetaAction | LoadDataSourceMetaAction
| SetDataSourceNameAction | SetDataSourceNameAction;
| SetDataSourceTestingProgessAction
| SetDataSourceTestingSuccessAction
| SetDataSourceTestingFailAction
| ClearTestingAction;
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>; type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>;
@@ -211,15 +161,9 @@ export function loadDataSourceTypes(): ThunkResult<void> {
export function updateDataSource(dataSource: DataSource): ThunkResult<void> { export function updateDataSource(dataSource: DataSource): ThunkResult<void> {
return async dispatch => { return async dispatch => {
await getBackendSrv() await getBackendSrv().put(`/api/datasources/${dataSource.id}`, dataSource);
.put(`/api/datasources/${dataSource.id}`, dataSource) await updateFrontendSettings();
.then(response => { return dispatch(loadDataSource(dataSource.id));
updateFrontendSettings().then(() => {
testDataSource(dispatch, response.name);
});
});
dispatch(loadDataSource(dataSource.id));
}; };
} }
@@ -270,40 +214,6 @@ function updateFrontendSettings() {
}); });
} }
function testDataSource(dispatch, name) {
dispatch(setDataSourceTestingProgress(true));
getDatasourceSrv()
.get(name)
.then(dataSource => {
if (!dataSource.testDatasource) {
return;
}
// make test call in no backend cache context
getBackendSrv()
.withNoBackendCache(() => {
return dataSource
.testDatasource()
.then(result => {
dispatch(setDataSourceTestingSuccess(result.status, result.message));
})
.catch(err => {
let message = '';
if (err.statusText) {
message = 'HTTP Error ' + err.statusText;
} else {
message = err.message;
}
dispatch(setDataSourceTestingFail(message));
});
})
.finally(() => {
dispatch(setDataSourceTestingProgress(false));
});
});
}
function nameHasSuffix(name) { function nameHasSuffix(name) {
return name.endsWith('-', name.length - 1); return name.endsWith('-', name.length - 1);
} }

View File

@@ -12,7 +12,6 @@ const initialState: DataSourcesState = {
dataSourceTypeSearchQuery: '', dataSourceTypeSearchQuery: '',
hasFetched: false, hasFetched: false,
dataSourceMeta: {} as Plugin, dataSourceMeta: {} as Plugin,
testing: { inProgress: false, status: '', message: '' },
}; };
export const dataSourcesReducer = (state = initialState, action: Action): DataSourcesState => { export const dataSourcesReducer = (state = initialState, action: Action): DataSourcesState => {
@@ -40,28 +39,6 @@ export const dataSourcesReducer = (state = initialState, action: Action): DataSo
case ActionTypes.SetDataSourceName: case ActionTypes.SetDataSourceName:
return { ...state, dataSource: { ...state.dataSource, name: action.payload } }; return { ...state, dataSource: { ...state.dataSource, name: action.payload } };
case ActionTypes.SetDataSourceTestingProgess:
return { ...state, testing: { ...state.testing, inProgress: action.payload } };
case ActionTypes.SetDataSourceTestingSuccess:
return {
...state,
testing: {
status: action.payload.status,
message: action.payload.message,
inProgress: false,
},
};
case ActionTypes.SetDataSourceTestingFail:
return {
...state,
testing: { status: 'error', message: action.payload, inProgress: false },
};
case ActionTypes.ClearTesting:
return { ...state, testing: { inProgress: false, status: '', message: '' } };
} }
return state; return state;

View File

@@ -362,14 +362,9 @@ export default class CloudWatchDatasource {
const metricName = 'EstimatedCharges'; const metricName = 'EstimatedCharges';
const dimensions = {}; const dimensions = {};
return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then( return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then(() => {
() => { return { status: 'success', message: 'Data source is working' };
return { status: 'success', message: 'Data source is working' }; });
},
err => {
return { status: 'error', message: err.message };
}
);
} }
awsRequest(url, data) { awsRequest(url, data) {

View File

@@ -25,12 +25,6 @@ export interface DataSource {
testDatasource?: () => Promise<any>; testDatasource?: () => Promise<any>;
} }
export interface DataSourceTest {
inProgress: boolean;
message: string;
status: string;
}
export interface DataSourcesState { export interface DataSourcesState {
dataSources: DataSource[]; dataSources: DataSource[];
searchQuery: string; searchQuery: string;
@@ -41,5 +35,4 @@ export interface DataSourcesState {
dataSource: DataSource; dataSource: DataSource;
dataSourceMeta: Plugin; dataSourceMeta: Plugin;
hasFetched: boolean; hasFetched: boolean;
testing: DataSourceTest;
} }

View File

@@ -7,7 +7,7 @@ import { DashboardState } from './dashboard';
import { DashboardAcl, OrgRole, PermissionLevel } from './acl'; import { DashboardAcl, OrgRole, PermissionLevel } from './acl';
import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys'; import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys';
import { Invitee, OrgUser, User, UsersState, UserState } from './user'; import { Invitee, OrgUser, User, UsersState, UserState } from './user';
import { DataSource, DataSourceTest, DataSourcesState } from './datasources'; import { DataSource, DataSourcesState } from './datasources';
import { import {
TimeRange, TimeRange,
LoadingState, LoadingState,
@@ -88,7 +88,6 @@ export {
AppNotificationTimeout, AppNotificationTimeout,
DashboardSearchHit, DashboardSearchHit,
UserState, UserState,
DataSourceTest,
}; };
export interface StoreState { export interface StoreState {