Orgs: move duplicate API calls to Redux (#43030)

* WIP: move user/using api call to redux

* WIP: use redux instead in NewOrgPage component

* moved all the createOrg logic into redux

* type the redux state and action creators automatically

* adds test for setUserOrganization thunk

* update selectOrgPage to use Redux instead

* removes unnecessary dispatch call
This commit is contained in:
Uchechukwu Obasi
2021-12-15 09:42:52 +01:00
committed by GitHub
parent 00e06874e5
commit ea71dafbfb
4 changed files with 83 additions and 28 deletions
+19 -19
View File
@@ -2,18 +2,10 @@ import React, { FC } from 'react';
import { getBackendSrv } from '@grafana/runtime';
import Page from 'app/core/components/Page/Page';
import { Button, Input, Field, Form } from '@grafana/ui';
import { getConfig } from 'app/core/config';
import { StoreState } from 'app/types';
import { connect } from 'react-redux';
import { NavModel } from '@grafana/data';
import { connect, ConnectedProps } from 'react-redux';
import { getNavModel } from '../../core/selectors/navModel';
const createOrg = async (newOrg: { name: string }) => {
const result = await getBackendSrv().post('/api/orgs/', newOrg);
await getBackendSrv().post('/api/user/using/' + result.orgId);
window.location.href = getConfig().appSubUrl + '/org';
};
import { createOrganization } from './state/actions';
const validateOrg = async (orgName: string) => {
try {
@@ -28,15 +20,27 @@ const validateOrg = async (orgName: string) => {
return 'Organization already exists';
};
interface PropsWithState {
navModel: NavModel;
}
const mapStateToProps = (state: StoreState) => {
return { navModel: getNavModel(state.navIndex, 'global-orgs') };
};
const mapDispatchToProps = {
createOrganization,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
type Props = ConnectedProps<typeof connector>;
interface CreateOrgFormDTO {
name: string;
}
export const NewOrgPage: FC<PropsWithState> = ({ navModel }) => {
export const NewOrgPage: FC<Props> = ({ navModel, createOrganization }) => {
const createOrg = (newOrg: { name: string }) => {
createOrganization(newOrg);
};
return (
<Page navModel={navModel}>
<Page.Contents>
@@ -71,8 +75,4 @@ export const NewOrgPage: FC<PropsWithState> = ({ navModel }) => {
);
};
const mapStateToProps = (state: StoreState) => {
return { navModel: getNavModel(state.navIndex, 'global-orgs') };
};
export default connect(mapStateToProps)(NewOrgPage);
export default connector(NewOrgPage);
+16 -8
View File
@@ -4,6 +4,8 @@ import { getBackendSrv, config } from '@grafana/runtime';
import { UserOrg } from 'app/types';
import { useAsync } from 'react-use';
import { Button, HorizontalGroup } from '@grafana/ui';
import { setUserOrganization } from './state/actions';
import { connect, ConnectedProps } from 'react-redux';
const navModel = {
main: {
@@ -19,17 +21,23 @@ const navModel = {
const getUserOrgs = async () => {
return await getBackendSrv().get('/api/user/orgs');
};
const setUserOrg = async (org: UserOrg) => {
return await getBackendSrv()
.post('/api/user/using/' + org.orgId)
.then(() => {
window.location.href = config.appSubUrl + '/';
});
const mapDispatchToProps = {
setUserOrganization,
};
export const SelectOrgPage: FC = () => {
const connector = connect(null, mapDispatchToProps);
type Props = ConnectedProps<typeof connector>;
export const SelectOrgPage: FC<Props> = ({ setUserOrganization }) => {
const [orgs, setOrgs] = useState<UserOrg[]>();
const setUserOrg = async (org: UserOrg) => {
setUserOrganization(org.orgId);
window.location.href = config.appSubUrl + '/';
};
useAsync(async () => {
setOrgs(await getUserOrgs());
}, []);
@@ -55,4 +63,4 @@ export const SelectOrgPage: FC = () => {
);
};
export default SelectOrgPage;
export default connector(SelectOrgPage);
+24 -1
View File
@@ -1,4 +1,4 @@
import { updateOrganization } from './actions';
import { updateOrganization, setUserOrganization } from './actions';
import { updateConfigurationSubtitle } from 'app/core/actions';
import { thunkTester } from 'test/core/thunk/thunkTester';
@@ -38,3 +38,26 @@ describe('updateOrganization', () => {
});
});
});
describe('setUserOrganization', () => {
describe('when setUserOrganization thunk is dispatched', () => {
const postMock = jest.fn().mockResolvedValue({ id: 1, name: 'New Org Name' });
const backendSrvMock: any = {
post: postMock,
};
const orgId = 1;
it('then it should dispatch updateConfigurationSubtitle', async () => {
const { initialState } = setup();
const dispatchedActions = await thunkTester(initialState)
.givenThunk(setUserOrganization)
.whenThunkIsDispatched(orgId, { getBackendSrv: () => backendSrvMock });
expect(dispatchedActions[0].type).toEqual(updateConfigurationSubtitle.type);
expect(dispatchedActions[0].payload).toEqual(initialState.organization.organization.name);
});
});
});
+24
View File
@@ -2,6 +2,7 @@ import { ThunkResult } from 'app/types';
import { getBackendSrv } from '@grafana/runtime';
import { organizationLoaded } from './reducers';
import { updateConfigurationSubtitle } from 'app/core/actions';
import { getConfig } from 'app/core/config';
type OrganizationDependencies = { getBackendSrv: typeof getBackendSrv };
@@ -28,3 +29,26 @@ export function updateOrganization(
dispatch(loadOrganization(dependencies));
};
}
export function setUserOrganization(
orgId: number,
dependencies: OrganizationDependencies = { getBackendSrv: getBackendSrv }
): ThunkResult<any> {
return async (dispatch) => {
const organizationResponse = await dependencies.getBackendSrv().post('/api/user/using/' + orgId);
dispatch(updateConfigurationSubtitle(organizationResponse.name));
};
}
export function createOrganization(
newOrg: { name: string },
dependencies: OrganizationDependencies = { getBackendSrv: getBackendSrv }
): ThunkResult<any> {
return async (dispatch) => {
const result = await dependencies.getBackendSrv().post('/api/orgs/', newOrg);
dispatch(setUserOrganization(result.orgId));
window.location.href = getConfig().appSubUrl + '/org';
};
}