SelectOrgPage: migrate API function calls to Redux (#43133)

* SelectOrgPage: migrate API function calls to Redux

* used a much better approach

* writes test for getUserOrganizations action

* writes test for userOrganizationsLoaded reducer

* change userOrg to plural
This commit is contained in:
Uchechukwu Obasi 2021-12-16 09:57:18 +01:00 committed by GitHub
parent 8206802f19
commit d03aef1951
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 22 deletions

View File

@ -1,10 +1,10 @@
import React, { FC, useState } from 'react';
import React, { FC } from 'react';
import Page from 'app/core/components/Page/Page';
import { getBackendSrv, config } from '@grafana/runtime';
import { UserOrg } from 'app/types';
import { useAsync } from 'react-use';
import { config } from '@grafana/runtime';
import { StoreState, UserOrg } from 'app/types';
import { useEffectOnce } from 'react-use';
import { Button, HorizontalGroup } from '@grafana/ui';
import { setUserOrganization } from './state/actions';
import { getUserOrganizations, setUserOrganization } from './state/actions';
import { connect, ConnectedProps } from 'react-redux';
const navModel = {
@ -18,29 +18,31 @@ const navModel = {
},
};
const getUserOrgs = async () => {
return await getBackendSrv().get('/api/user/orgs');
const mapStateToProps = (state: StoreState) => {
return {
userOrgs: state.organization.userOrgs,
};
};
const mapDispatchToProps = {
setUserOrganization,
getUserOrganizations,
};
const connector = connect(null, mapDispatchToProps);
const connector = connect(mapStateToProps, mapDispatchToProps);
type Props = ConnectedProps<typeof connector>;
export const SelectOrgPage: FC<Props> = ({ setUserOrganization }) => {
const [orgs, setOrgs] = useState<UserOrg[]>();
export const SelectOrgPage: FC<Props> = ({ setUserOrganization, getUserOrganizations, userOrgs }) => {
const setUserOrg = async (org: UserOrg) => {
await setUserOrganization(org.orgId);
window.location.href = config.appSubUrl + '/';
};
useAsync(async () => {
setOrgs(await getUserOrgs());
}, []);
useEffectOnce(() => {
getUserOrganizations();
});
return (
<Page navModel={navModel}>
<Page.Contents>
@ -50,8 +52,8 @@ export const SelectOrgPage: FC<Props> = ({ setUserOrganization }) => {
now. You can change this later at any time.
</p>
<HorizontalGroup wrap>
{orgs &&
orgs.map((org) => (
{userOrgs &&
userOrgs.map((org) => (
<Button key={org.orgId} icon="signin" onClick={() => setUserOrg(org)}>
{org.name}
</Button>

View File

@ -1,6 +1,7 @@
import { updateOrganization, setUserOrganization } from './actions';
import { updateOrganization, setUserOrganization, getUserOrganizations } from './actions';
import { updateConfigurationSubtitle } from 'app/core/actions';
import { thunkTester } from 'test/core/thunk/thunkTester';
import { OrgRole } from 'app/types';
const setup = () => {
const initialState = {
@ -9,6 +10,7 @@ const setup = () => {
id: 1,
name: 'New Org Name',
},
userOrg: [{ orgId: 1, name: 'New Org Name', role: OrgRole.Editor }],
},
};
@ -61,3 +63,22 @@ describe('setUserOrganization', () => {
});
});
});
describe('getUserOrganizations', () => {
describe('when getUserOrganizations thunk is dispatched', () => {
const getMock = jest.fn().mockResolvedValue({ orgId: 1, name: 'New Org Name', role: OrgRole.Editor });
const backendSrvMock: any = {
get: getMock,
};
it('then it should dispatch updateConfigurationSubtitle', async () => {
const { initialState } = setup();
const dispatchedActions = await thunkTester(initialState)
.givenThunk(getUserOrganizations)
.whenThunkIsDispatched({ getBackendSrv: () => backendSrvMock });
expect(dispatchedActions[0].payload).toEqual(initialState.organization.userOrg[0]);
});
});
});

View File

@ -1,6 +1,6 @@
import { ThunkResult } from 'app/types';
import { getBackendSrv } from '@grafana/runtime';
import { organizationLoaded } from './reducers';
import { organizationLoaded, userOrganizationsLoaded } from './reducers';
import { updateConfigurationSubtitle } from 'app/core/actions';
type OrganizationDependencies = { getBackendSrv: typeof getBackendSrv };
@ -50,3 +50,14 @@ export function createOrganization(
dispatch(setUserOrganization(result.orgId));
};
}
export function getUserOrganizations(
dependencies: OrganizationDependencies = { getBackendSrv: getBackendSrv }
): ThunkResult<any> {
return async (dispatch) => {
const result = await dependencies.getBackendSrv().get('/api/user/orgs');
dispatch(userOrganizationsLoaded(result));
return result;
};
}

View File

@ -1,6 +1,12 @@
import { reducerTester } from '../../../../test/core/redux/reducerTester';
import { OrganizationState } from '../../../types';
import { initialState, organizationLoaded, organizationReducer, setOrganizationName } from './reducers';
import { OrganizationState, OrgRole } from '../../../types';
import {
initialState,
organizationLoaded,
organizationReducer,
userOrganizationsLoaded,
setOrganizationName,
} from './reducers';
describe('organizationReducer', () => {
describe('when organizationLoaded is dispatched', () => {
@ -10,6 +16,7 @@ describe('organizationReducer', () => {
.whenActionIsDispatched(organizationLoaded({ id: 1, name: 'An org' }))
.thenStateShouldEqual({
organization: { id: 1, name: 'An org' },
userOrgs: [],
});
});
});
@ -21,6 +28,23 @@ describe('organizationReducer', () => {
.whenActionIsDispatched(setOrganizationName('New Name'))
.thenStateShouldEqual({
organization: { id: 1, name: 'New Name' },
userOrgs: [],
});
});
});
describe('when userOrganizationsLoaded is dispatched', () => {
it('then state should be correct', () => {
reducerTester<OrganizationState>()
.givenReducer(organizationReducer, {
...initialState,
organization: { id: 1, name: 'An org' },
userOrgs: [],
})
.whenActionIsDispatched(userOrganizationsLoaded([{ orgId: 1, name: 'New org', role: OrgRole.Editor }]))
.thenStateShouldEqual({
organization: { id: 1, name: 'An org' },
userOrgs: [{ orgId: 1, name: 'New org', role: OrgRole.Editor }],
});
});
});

View File

@ -1,9 +1,10 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { Organization, OrganizationState } from 'app/types';
import { Organization, OrganizationState, UserOrg } from 'app/types';
export const initialState: OrganizationState = {
organization: {} as Organization,
userOrgs: [] as UserOrg[],
};
const organizationSlice = createSlice({
@ -16,10 +17,13 @@ const organizationSlice = createSlice({
setOrganizationName: (state, action: PayloadAction<string>): OrganizationState => {
return { ...state, organization: { ...state.organization, name: action.payload } };
},
userOrganizationsLoaded: (state, action: PayloadAction<UserOrg[]>): OrganizationState => {
return { ...state, userOrgs: action.payload };
},
},
});
export const { setOrganizationName, organizationLoaded } = organizationSlice.actions;
export const { setOrganizationName, organizationLoaded, userOrganizationsLoaded } = organizationSlice.actions;
export const organizationReducer = organizationSlice.reducer;

View File

@ -1,3 +1,5 @@
import { UserOrg } from 'app/types';
export interface Organization {
name: string;
id: number;
@ -5,4 +7,5 @@ export interface Organization {
export interface OrganizationState {
organization: Organization;
userOrgs: UserOrg[];
}