mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Admin: Fix updating organization name not updating configuration subtitle (#26315)
* Fix updating organization name not updating configuration subtitle * PR feedback: Remove unnecessary square brackets * Refactor code to update redux state in a safer way, add org action test * Refactor updateConfigurationSubtitle test, remove jest.mock usage * Consolidate dependency type
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { clearAppNotification, notifyApp } from '../reducers/appNotification';
|
||||
import { updateLocation } from '../reducers/location';
|
||||
import { updateNavIndex } from '../reducers/navModel';
|
||||
import { updateNavIndex, updateConfigurationSubtitle } from '../reducers/navModel';
|
||||
|
||||
export { updateLocation, updateNavIndex, notifyApp, clearAppNotification };
|
||||
export { updateLocation, updateNavIndex, updateConfigurationSubtitle, notifyApp, clearAppNotification };
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { reducerTester } from '../../../test/core/redux/reducerTester';
|
||||
import { initialState, navIndexReducer, updateNavIndex } from './navModel';
|
||||
import { navIndexReducer, updateNavIndex, updateConfigurationSubtitle } from './navModel';
|
||||
import { NavIndex } from '@grafana/data';
|
||||
|
||||
describe('applicationReducer', () => {
|
||||
describe('navModelReducer', () => {
|
||||
describe('when updateNavIndex is dispatched', () => {
|
||||
it('then state should be correct', () => {
|
||||
reducerTester<NavIndex>()
|
||||
.givenReducer(navIndexReducer, { ...initialState })
|
||||
.givenReducer(navIndexReducer, {})
|
||||
.whenActionIsDispatched(
|
||||
updateNavIndex({
|
||||
id: 'parent',
|
||||
@@ -20,7 +20,6 @@ describe('applicationReducer', () => {
|
||||
})
|
||||
)
|
||||
.thenStateShouldEqual({
|
||||
...initialState,
|
||||
child: {
|
||||
id: 'child',
|
||||
text: 'Child',
|
||||
@@ -38,4 +37,44 @@ describe('applicationReducer', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when updateConfigurationSubtitle is dispatched', () => {
|
||||
it('then state should be correct', () => {
|
||||
const originalCfg = { id: 'cfg', subTitle: 'Organization: Org 1', text: 'Configuration' };
|
||||
const datasources = { id: 'datasources', text: 'Data Sources' };
|
||||
const users = { id: 'users', text: 'Users' };
|
||||
const teams = { id: 'teams', text: 'Teams' };
|
||||
const plugins = { id: 'plugins', text: 'Plugins' };
|
||||
const orgsettings = { id: 'org-settings', text: 'Preferences' };
|
||||
const apikeys = { id: 'apikeys', text: 'API Keys' };
|
||||
|
||||
const initialState = {
|
||||
cfg: { ...originalCfg, children: [datasources, users, teams, plugins, orgsettings, apikeys] },
|
||||
datasources: { ...datasources, parentItem: originalCfg },
|
||||
users: { ...users, parentItem: originalCfg },
|
||||
teams: { ...teams, parentItem: originalCfg },
|
||||
plugins: { ...plugins, parentItem: originalCfg },
|
||||
'org-settings': { ...orgsettings, parentItem: originalCfg },
|
||||
apikeys: { ...apikeys, parentItem: originalCfg },
|
||||
};
|
||||
|
||||
const newOrgName = 'Org 2';
|
||||
const subTitle = `Organization: ${newOrgName}`;
|
||||
const newCfg = { ...originalCfg, subTitle };
|
||||
const expectedState = {
|
||||
cfg: { ...newCfg, children: [datasources, users, teams, plugins, orgsettings, apikeys] },
|
||||
datasources: { ...datasources, parentItem: newCfg },
|
||||
users: { ...users, parentItem: newCfg },
|
||||
teams: { ...teams, parentItem: newCfg },
|
||||
plugins: { ...plugins, parentItem: newCfg },
|
||||
'org-settings': { ...orgsettings, parentItem: newCfg },
|
||||
apikeys: { ...apikeys, parentItem: newCfg },
|
||||
};
|
||||
|
||||
reducerTester<NavIndex>()
|
||||
.givenReducer(navIndexReducer, { ...initialState })
|
||||
.whenActionIsDispatched(updateConfigurationSubtitle(newOrgName))
|
||||
.thenStateShouldEqual(expectedState);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,6 +41,17 @@ function buildWarningNav(text: string, subTitle?: string): NavModel {
|
||||
export const initialState: NavIndex = {};
|
||||
|
||||
export const updateNavIndex = createAction<NavModelItem>('navIndex/updateNavIndex');
|
||||
// Since the configuration subtitle includes the organization name, we include this action to update the org name if it changes.
|
||||
export const updateConfigurationSubtitle = createAction<string>('navIndex/updateConfigurationSubtitle');
|
||||
|
||||
export const getItemWithNewSubTitle = (item: NavModelItem, subTitle: string): NavModelItem => ({
|
||||
...item,
|
||||
parentItem: {
|
||||
...item.parentItem,
|
||||
text: item.parentItem?.text ?? '',
|
||||
subTitle,
|
||||
},
|
||||
});
|
||||
|
||||
// Redux Toolkit uses ImmerJs as part of their solution to ensure that state objects are not mutated.
|
||||
// ImmerJs has an autoFreeze option that freezes objects from change which means this reducer can't be migrated to createSlice
|
||||
@@ -60,6 +71,19 @@ export const navIndexReducer = (state: NavIndex = initialState, action: AnyActio
|
||||
}
|
||||
|
||||
return { ...state, ...newPages };
|
||||
} else if (updateConfigurationSubtitle.match(action)) {
|
||||
const subTitle = `Organization: ${action.payload}`;
|
||||
|
||||
return {
|
||||
...state,
|
||||
cfg: { ...state.cfg, subTitle },
|
||||
datasources: getItemWithNewSubTitle(state.datasources, subTitle),
|
||||
users: getItemWithNewSubTitle(state.users, subTitle),
|
||||
teams: getItemWithNewSubTitle(state.teams, subTitle),
|
||||
plugins: getItemWithNewSubTitle(state.plugins, subTitle),
|
||||
'org-settings': getItemWithNewSubTitle(state['org-settings'], subTitle),
|
||||
apikeys: getItemWithNewSubTitle(state.apikeys, subTitle),
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NavModel, NavModelItem, NavIndex } from '@grafana/data';
|
||||
|
||||
function getNotFoundModel(): NavModel {
|
||||
const getNotFoundModel = (): NavModel => {
|
||||
const node: NavModelItem = {
|
||||
id: 'not-found',
|
||||
text: 'Page not found',
|
||||
@@ -13,9 +13,9 @@ function getNotFoundModel(): NavModel {
|
||||
node: node,
|
||||
main: node,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export function getNavModel(navIndex: NavIndex, id: string, fallback?: NavModel, onlyChild = false): NavModel {
|
||||
export const getNavModel = (navIndex: NavIndex, id: string, fallback?: NavModel, onlyChild = false): NavModel => {
|
||||
if (navIndex[id]) {
|
||||
const node = navIndex[id];
|
||||
|
||||
@@ -36,8 +36,8 @@ export function getNavModel(navIndex: NavIndex, id: string, fallback?: NavModel,
|
||||
}
|
||||
|
||||
return {
|
||||
node: node,
|
||||
main: main,
|
||||
node,
|
||||
main,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export function getNavModel(navIndex: NavIndex, id: string, fallback?: NavModel,
|
||||
}
|
||||
|
||||
return getNotFoundModel();
|
||||
}
|
||||
};
|
||||
|
||||
export const getTitleFromNavModel = (navModel: NavModel) => {
|
||||
return `${navModel.main.text}${navModel.node.text ? ': ' + navModel.node.text : ''}`;
|
||||
|
||||
40
public/app/features/org/state/actions.test.ts
Normal file
40
public/app/features/org/state/actions.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { updateOrganization } from './actions';
|
||||
import { updateConfigurationSubtitle } from 'app/core/actions';
|
||||
import { thunkTester } from 'test/core/thunk/thunkTester';
|
||||
|
||||
const setup = () => {
|
||||
const initialState = {
|
||||
organization: {
|
||||
organization: {
|
||||
id: 1,
|
||||
name: 'New Org Name',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
initialState,
|
||||
};
|
||||
};
|
||||
|
||||
describe('updateOrganization', () => {
|
||||
describe('when updateOrganization thunk is dispatched', () => {
|
||||
const getMock = jest.fn().mockResolvedValue({ id: 1, name: 'New Org Name' });
|
||||
const putMock = jest.fn().mockResolvedValue({ id: 1, name: 'New Org Name' });
|
||||
const backendSrvMock: any = {
|
||||
get: getMock,
|
||||
put: putMock,
|
||||
};
|
||||
|
||||
it('then it should dispatch updateConfigurationSubtitle', async () => {
|
||||
const { initialState } = setup();
|
||||
|
||||
const dispatchedActions = await thunkTester(initialState)
|
||||
.givenThunk(updateOrganization)
|
||||
.whenThunkIsDispatched({ getBackendSrv: () => backendSrvMock });
|
||||
|
||||
expect(dispatchedActions[0].type).toEqual(updateConfigurationSubtitle.type);
|
||||
expect(dispatchedActions[0].payload).toEqual(initialState.organization.organization.name);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,22 +1,30 @@
|
||||
import { ThunkResult } from 'app/types';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { organizationLoaded } from './reducers';
|
||||
import { updateConfigurationSubtitle } from 'app/core/actions';
|
||||
|
||||
export function loadOrganization(): ThunkResult<any> {
|
||||
type OrganizationDependencies = { getBackendSrv: typeof getBackendSrv };
|
||||
|
||||
export function loadOrganization(
|
||||
dependencies: OrganizationDependencies = { getBackendSrv: getBackendSrv }
|
||||
): ThunkResult<any> {
|
||||
return async dispatch => {
|
||||
const organizationResponse = await getBackendSrv().get('/api/org');
|
||||
const organizationResponse = await dependencies.getBackendSrv().get('/api/org');
|
||||
dispatch(organizationLoaded(organizationResponse));
|
||||
|
||||
return organizationResponse;
|
||||
};
|
||||
}
|
||||
|
||||
export function updateOrganization(): ThunkResult<any> {
|
||||
export function updateOrganization(
|
||||
dependencies: OrganizationDependencies = { getBackendSrv: getBackendSrv }
|
||||
): ThunkResult<any> {
|
||||
return async (dispatch, getStore) => {
|
||||
const organization = getStore().organization.organization;
|
||||
|
||||
await getBackendSrv().put('/api/org', { name: organization.name });
|
||||
await dependencies.getBackendSrv().put('/api/org', { name: organization.name });
|
||||
|
||||
dispatch(loadOrganization());
|
||||
dispatch(updateConfigurationSubtitle(organization.name));
|
||||
dispatch(loadOrganization(dependencies));
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user