diff --git a/public/app/core/redux/index.ts b/public/app/core/redux/index.ts new file mode 100644 index 00000000000..359f160b9ce --- /dev/null +++ b/public/app/core/redux/index.ts @@ -0,0 +1,4 @@ +import { actionCreatorFactory } from './actionCreatorFactory'; +import { reducerFactory } from './reducerFactory'; + +export { actionCreatorFactory, reducerFactory }; diff --git a/public/app/core/redux/reducerFactory.test.ts b/public/app/core/redux/reducerFactory.test.ts index 2d0acc93749..5057b6d84b5 100644 --- a/public/app/core/redux/reducerFactory.test.ts +++ b/public/app/core/redux/reducerFactory.test.ts @@ -28,7 +28,7 @@ const dummyActionCreator = actionCreatorFactory('dummy').crea const dummyReducer = reducerFactory(dummyReducerIntialState) .addHandler({ filter: dummyActionCreator, - handler: (state, action) => ({ ...state, ...action.payload }), + mapper: (state, action) => ({ ...state, ...action.payload }), }) .create(); @@ -78,7 +78,7 @@ describe('reducerFactory', () => { it('then is should throw', () => { const faultyReducer = reducerFactory(dummyReducerIntialState).addHandler({ filter: dummyActionCreator, - handler: (state, action) => { + mapper: (state, action) => { return { ...state, ...action.payload }; }, }); @@ -86,7 +86,7 @@ describe('reducerFactory', () => { expect(() => { faultyReducer.addHandler({ filter: dummyActionCreator, - handler: state => { + mapper: state => { return state; }, }); diff --git a/public/app/core/redux/reducerFactory.ts b/public/app/core/redux/reducerFactory.ts index 2df5217ea6e..6858dd41879 100644 --- a/public/app/core/redux/reducerFactory.ts +++ b/public/app/core/redux/reducerFactory.ts @@ -1,9 +1,10 @@ import { ActionOf, ActionCreator } from './actionCreatorFactory'; -import { Reducer } from 'redux'; + +export type Mapper = (state: State, action: ActionOf) => State; export interface HandlerConfig { filter: ActionCreator; - handler: (state: State, action: ActionOf) => State; + mapper: Mapper; } export interface AddHandler { @@ -11,7 +12,7 @@ export interface AddHandler { } export interface CreateReducer extends AddHandler { - create: () => Reducer>; + create: () => Mapper; } export const reducerFactory = (initialState: State): AddHandler => { @@ -27,18 +28,14 @@ export const reducerFactory = (initialState: State): AddHandler => return instance; }; - const create = (): Reducer> => { - const reducer: Reducer> = (state: State = initialState, action: ActionOf) => { - const validHandlers = allHandlerConfigs - .filter(config => config.filter.type === action.type) - .map(config => config.handler); + const create = () => (state: State = initialState, action: ActionOf): State => { + const handlerConfig = allHandlerConfigs.filter(config => config.filter.type === action.type)[0]; - return validHandlers.reduce((currentState, handler) => { - return handler(currentState, action); - }, state || initialState); - }; + if (handlerConfig) { + return handlerConfig.mapper(state, action); + } - return reducer; + return state; }; const instance: CreateReducer = { diff --git a/public/app/features/datasources/DataSourcesListPage.test.tsx b/public/app/features/datasources/DataSourcesListPage.test.tsx index 44ef7a1cc49..65077201f65 100644 --- a/public/app/features/datasources/DataSourcesListPage.test.tsx +++ b/public/app/features/datasources/DataSourcesListPage.test.tsx @@ -5,6 +5,7 @@ import { NavModel } from 'app/types'; import { DataSourceSettings } from '@grafana/ui/src/types'; import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector'; import { getMockDataSources } from './__mocks__/dataSourcesMocks'; +import { setDataSourcesSearchQuery, setDataSourcesLayoutMode } from './state/actions'; const setup = (propOverrides?: object) => { const props: Props = { @@ -13,16 +14,16 @@ const setup = (propOverrides?: object) => { loadDataSources: jest.fn(), navModel: { main: { - text: 'Configuration' + text: 'Configuration', }, node: { - text: 'Data Sources' - } + text: 'Data Sources', + }, } as NavModel, dataSourcesCount: 0, searchQuery: '', - setDataSourcesSearchQuery: jest.fn(), - setDataSourcesLayoutMode: jest.fn(), + setDataSourcesSearchQuery, + setDataSourcesLayoutMode, hasFetched: false, }; diff --git a/public/app/features/datasources/settings/DataSourceSettingsPage.test.tsx b/public/app/features/datasources/settings/DataSourceSettingsPage.test.tsx index 8efc92be5be..204eeb8b1e9 100644 --- a/public/app/features/datasources/settings/DataSourceSettingsPage.test.tsx +++ b/public/app/features/datasources/settings/DataSourceSettingsPage.test.tsx @@ -5,6 +5,7 @@ import { NavModel } from 'app/types'; import { DataSourceSettings } from '@grafana/ui'; import { getMockDataSource } from '../__mocks__/dataSourcesMocks'; import { getMockPlugin } from '../../plugins/__mocks__/pluginMocks'; +import { setDataSourceName, setIsDefault } from '../state/actions'; const setup = (propOverrides?: object) => { const props: Props = { @@ -14,9 +15,9 @@ const setup = (propOverrides?: object) => { pageId: 1, deleteDataSource: jest.fn(), loadDataSource: jest.fn(), - setDataSourceName: jest.fn(), + setDataSourceName, updateDataSource: jest.fn(), - setIsDefault: jest.fn(), + setIsDefault, }; Object.assign(props, propOverrides); diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index 0fa260ffafa..4cd61537cea 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -8,6 +8,8 @@ import { UpdateLocationAction } from 'app/core/actions/location'; import { buildNavModel } from './navModel'; import { DataSourceSettings } from '@grafana/ui/src/types'; import { Plugin, StoreState } from 'app/types'; +import { actionCreatorFactory } from 'app/core/redux'; +import { ActionOf } from 'app/core/redux/actionCreatorFactory'; export enum ActionTypes { LoadDataSources = 'LOAD_DATA_SOURCES', @@ -22,124 +24,36 @@ export enum ActionTypes { SetIsDefault = 'SET_IS_DEFAULT', } -interface LoadDataSourcesAction { - type: ActionTypes.LoadDataSources; - payload: DataSourceSettings[]; -} +export const dataSourceLoaded = actionCreatorFactory(ActionTypes.LoadDataSource).create(); -interface SetDataSourcesSearchQueryAction { - type: ActionTypes.SetDataSourcesSearchQuery; - payload: string; -} +export const dataSourcesLoaded = actionCreatorFactory(ActionTypes.LoadDataSources).create(); -interface SetDataSourcesLayoutModeAction { - type: ActionTypes.SetDataSourcesLayoutMode; - payload: LayoutMode; -} +export const dataSourceMetaLoaded = actionCreatorFactory(ActionTypes.LoadDataSourceMeta).create(); -interface LoadDataSourceTypesAction { - type: ActionTypes.LoadDataSourceTypes; -} +export const dataSourceTypesLoad = actionCreatorFactory(ActionTypes.LoadDataSourceTypes).create(); -interface LoadedDataSourceTypesAction { - type: ActionTypes.LoadedDataSourceTypes; - payload: Plugin[]; -} +export const dataSourceTypesLoaded = actionCreatorFactory(ActionTypes.LoadedDataSourceTypes).create(); -interface SetDataSourceTypeSearchQueryAction { - type: ActionTypes.SetDataSourceTypeSearchQuery; - payload: string; -} +export const setDataSourcesSearchQuery = actionCreatorFactory(ActionTypes.SetDataSourcesSearchQuery).create(); -interface LoadDataSourceAction { - type: ActionTypes.LoadDataSource; - payload: DataSourceSettings; -} +export const setDataSourcesLayoutMode = actionCreatorFactory(ActionTypes.SetDataSourcesLayoutMode).create(); -interface LoadDataSourceMetaAction { - type: ActionTypes.LoadDataSourceMeta; - payload: Plugin; -} +export const setDataSourceTypeSearchQuery = actionCreatorFactory( + ActionTypes.SetDataSourceTypeSearchQuery +).create(); -interface SetDataSourceNameAction { - type: ActionTypes.SetDataSourceName; - payload: string; -} +export const setDataSourceName = actionCreatorFactory(ActionTypes.SetDataSourceName).create(); -interface SetIsDefaultAction { - type: ActionTypes.SetIsDefault; - payload: boolean; -} +export const setIsDefault = actionCreatorFactory(ActionTypes.SetIsDefault).create(); -const dataSourcesLoaded = (dataSources: DataSourceSettings[]): LoadDataSourcesAction => ({ - type: ActionTypes.LoadDataSources, - payload: dataSources, -}); - -const dataSourceLoaded = (dataSource: DataSourceSettings): LoadDataSourceAction => ({ - type: ActionTypes.LoadDataSource, - payload: dataSource, -}); - -const dataSourceMetaLoaded = (dataSourceMeta: Plugin): LoadDataSourceMetaAction => ({ - type: ActionTypes.LoadDataSourceMeta, - payload: dataSourceMeta, -}); - -const dataSourceTypesLoad = (): LoadDataSourceTypesAction => ({ - type: ActionTypes.LoadDataSourceTypes, -}); - -const dataSourceTypesLoaded = (dataSourceTypes: Plugin[]): LoadedDataSourceTypesAction => ({ - type: ActionTypes.LoadedDataSourceTypes, - payload: dataSourceTypes, -}); - -export const setDataSourcesSearchQuery = (searchQuery: string): SetDataSourcesSearchQueryAction => ({ - type: ActionTypes.SetDataSourcesSearchQuery, - payload: searchQuery, -}); - -export const setDataSourcesLayoutMode = (layoutMode: LayoutMode): SetDataSourcesLayoutModeAction => ({ - type: ActionTypes.SetDataSourcesLayoutMode, - payload: layoutMode, -}); - -export const setDataSourceTypeSearchQuery = (query: string): SetDataSourceTypeSearchQueryAction => ({ - type: ActionTypes.SetDataSourceTypeSearchQuery, - payload: query, -}); - -export const setDataSourceName = (name: string) => ({ - type: ActionTypes.SetDataSourceName, - payload: name, -}); - -export const setIsDefault = (state: boolean) => ({ - type: ActionTypes.SetIsDefault, - payload: state, -}); - -export type Action = - | LoadDataSourcesAction - | SetDataSourcesSearchQueryAction - | SetDataSourcesLayoutModeAction - | UpdateLocationAction - | LoadDataSourceTypesAction - | LoadedDataSourceTypesAction - | SetDataSourceTypeSearchQueryAction - | LoadDataSourceAction - | UpdateNavIndexAction - | LoadDataSourceMetaAction - | SetDataSourceNameAction - | SetIsDefaultAction; +export type Action = UpdateLocationAction | UpdateNavIndexAction | ActionOf; type ThunkResult = ThunkAction; export function loadDataSources(): ThunkResult { return async dispatch => { const response = await getBackendSrv().get('/api/datasources'); - dispatch(dataSourcesLoaded(response)); + dataSourcesLoaded(response); }; } @@ -177,7 +91,7 @@ export function addDataSource(plugin: Plugin): ThunkResult { export function loadDataSourceTypes(): ThunkResult { return async dispatch => { - dispatch(dataSourceTypesLoad()); + dispatch(dataSourceTypesLoad({})); const result = await getBackendSrv().get('/api/plugins', { enabled: 1, type: 'datasource' }); dispatch(dataSourceTypesLoaded(result)); }; diff --git a/public/app/features/datasources/state/reducers.ts b/public/app/features/datasources/state/reducers.ts index 66151990aea..68bc30201d6 100644 --- a/public/app/features/datasources/state/reducers.ts +++ b/public/app/features/datasources/state/reducers.ts @@ -1,56 +1,87 @@ import { DataSourcesState, Plugin } from 'app/types'; import { DataSourceSettings } from '@grafana/ui/src/types'; -import { Action, ActionTypes } from './actions'; +import { + dataSourceLoaded, + dataSourcesLoaded, + setDataSourcesSearchQuery, + setDataSourcesLayoutMode, + dataSourceTypesLoad, + dataSourceTypesLoaded, + setDataSourceTypeSearchQuery, + dataSourceMetaLoaded, + setDataSourceName, + setIsDefault, +} from './actions'; import { LayoutModes } from 'app/core/components/LayoutSelector/LayoutSelector'; +import { reducerFactory } from 'app/core/redux'; const initialState: DataSourcesState = { - dataSources: [] as DataSourceSettings[], + dataSources: [], dataSource: {} as DataSourceSettings, layoutMode: LayoutModes.List, searchQuery: '', dataSourcesCount: 0, - dataSourceTypes: [] as Plugin[], + dataSourceTypes: [], dataSourceTypeSearchQuery: '', hasFetched: false, isLoadingDataSources: false, dataSourceMeta: {} as Plugin, }; -export const dataSourcesReducer = (state = initialState, action: Action): DataSourcesState => { - switch (action.type) { - case ActionTypes.LoadDataSources: - return { ...state, hasFetched: true, dataSources: action.payload, dataSourcesCount: action.payload.length }; - - case ActionTypes.LoadDataSource: - return { ...state, dataSource: action.payload }; - - case ActionTypes.SetDataSourcesSearchQuery: - return { ...state, searchQuery: action.payload }; - - case ActionTypes.SetDataSourcesLayoutMode: - return { ...state, layoutMode: action.payload }; - - case ActionTypes.LoadDataSourceTypes: - return { ...state, dataSourceTypes: [], isLoadingDataSources: true }; - - case ActionTypes.LoadedDataSourceTypes: - return { ...state, dataSourceTypes: action.payload, isLoadingDataSources: false }; - - case ActionTypes.SetDataSourceTypeSearchQuery: - return { ...state, dataSourceTypeSearchQuery: action.payload }; - - case ActionTypes.LoadDataSourceMeta: - return { ...state, dataSourceMeta: action.payload }; - - case ActionTypes.SetDataSourceName: - return { ...state, dataSource: { ...state.dataSource, name: action.payload } }; - - case ActionTypes.SetIsDefault: - return { ...state, dataSource: { ...state.dataSource, isDefault: action.payload } }; - } - - return state; -}; +export const dataSourcesReducer = reducerFactory(initialState) + .addHandler({ + filter: dataSourcesLoaded, + mapper: (state, action) => ({ + ...state, + hasFetched: true, + dataSources: action.payload, + dataSourcesCount: action.payload.length, + }), + }) + .addHandler({ + filter: dataSourceLoaded, + mapper: (state, action) => ({ ...state, dataSource: action.payload }), + }) + .addHandler({ + filter: setDataSourcesSearchQuery, + mapper: (state, action) => ({ ...state, searchQuery: action.payload }), + }) + .addHandler({ + filter: setDataSourcesLayoutMode, + mapper: (state, action) => ({ ...state, layoutMode: action.payload }), + }) + .addHandler({ + filter: dataSourceTypesLoad, + mapper: state => ({ ...state, dataSourceTypes: [], isLoadingDataSources: true }), + }) + .addHandler({ + filter: dataSourceTypesLoaded, + mapper: (state, action) => ({ + ...state, + dataSourceTypes: action.payload, + isLoadingDataSources: false, + }), + }) + .addHandler({ + filter: setDataSourceTypeSearchQuery, + mapper: (state, action) => ({ ...state, dataSourceTypeSearchQuery: action.payload }), + }) + .addHandler({ + filter: dataSourceMetaLoaded, + mapper: (state, action) => ({ ...state, dataSourceMeta: action.payload }), + }) + .addHandler({ + filter: setDataSourceName, + mapper: (state, action) => ({ ...state, dataSource: { ...state.dataSource, name: action.payload } }), + }) + .addHandler({ + filter: setIsDefault, + mapper: (state, action) => ({ + ...state, + dataSource: { ...state.dataSource, isDefault: action.payload }, + }), + }) + .create(); export default { dataSources: dataSourcesReducer,