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 Page from 'app/core/components/Page/Page';
import { getBackendSrv, config } from '@grafana/runtime'; import { config } from '@grafana/runtime';
import { UserOrg } from 'app/types'; import { StoreState, UserOrg } from 'app/types';
import { useAsync } from 'react-use'; import { useEffectOnce } from 'react-use';
import { Button, HorizontalGroup } from '@grafana/ui'; import { Button, HorizontalGroup } from '@grafana/ui';
import { setUserOrganization } from './state/actions'; import { getUserOrganizations, setUserOrganization } from './state/actions';
import { connect, ConnectedProps } from 'react-redux'; import { connect, ConnectedProps } from 'react-redux';
const navModel = { const navModel = {
@ -18,29 +18,31 @@ const navModel = {
}, },
}; };
const getUserOrgs = async () => { const mapStateToProps = (state: StoreState) => {
return await getBackendSrv().get('/api/user/orgs'); return {
userOrgs: state.organization.userOrgs,
};
}; };
const mapDispatchToProps = { const mapDispatchToProps = {
setUserOrganization, setUserOrganization,
getUserOrganizations,
}; };
const connector = connect(null, mapDispatchToProps); const connector = connect(mapStateToProps, mapDispatchToProps);
type Props = ConnectedProps<typeof connector>; type Props = ConnectedProps<typeof connector>;
export const SelectOrgPage: FC<Props> = ({ setUserOrganization }) => { export const SelectOrgPage: FC<Props> = ({ setUserOrganization, getUserOrganizations, userOrgs }) => {
const [orgs, setOrgs] = useState<UserOrg[]>();
const setUserOrg = async (org: UserOrg) => { const setUserOrg = async (org: UserOrg) => {
await setUserOrganization(org.orgId); await setUserOrganization(org.orgId);
window.location.href = config.appSubUrl + '/'; window.location.href = config.appSubUrl + '/';
}; };
useAsync(async () => { useEffectOnce(() => {
setOrgs(await getUserOrgs()); getUserOrganizations();
}, []); });
return ( return (
<Page navModel={navModel}> <Page navModel={navModel}>
<Page.Contents> <Page.Contents>
@ -50,8 +52,8 @@ export const SelectOrgPage: FC<Props> = ({ setUserOrganization }) => {
now. You can change this later at any time. now. You can change this later at any time.
</p> </p>
<HorizontalGroup wrap> <HorizontalGroup wrap>
{orgs && {userOrgs &&
orgs.map((org) => ( userOrgs.map((org) => (
<Button key={org.orgId} icon="signin" onClick={() => setUserOrg(org)}> <Button key={org.orgId} icon="signin" onClick={() => setUserOrg(org)}>
{org.name} {org.name}
</Button> </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 { updateConfigurationSubtitle } from 'app/core/actions';
import { thunkTester } from 'test/core/thunk/thunkTester'; import { thunkTester } from 'test/core/thunk/thunkTester';
import { OrgRole } from 'app/types';
const setup = () => { const setup = () => {
const initialState = { const initialState = {
@ -9,6 +10,7 @@ const setup = () => {
id: 1, id: 1,
name: 'New Org Name', 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 { ThunkResult } from 'app/types';
import { getBackendSrv } from '@grafana/runtime'; import { getBackendSrv } from '@grafana/runtime';
import { organizationLoaded } from './reducers'; import { organizationLoaded, userOrganizationsLoaded } from './reducers';
import { updateConfigurationSubtitle } from 'app/core/actions'; import { updateConfigurationSubtitle } from 'app/core/actions';
type OrganizationDependencies = { getBackendSrv: typeof getBackendSrv }; type OrganizationDependencies = { getBackendSrv: typeof getBackendSrv };
@ -50,3 +50,14 @@ export function createOrganization(
dispatch(setUserOrganization(result.orgId)); 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 { reducerTester } from '../../../../test/core/redux/reducerTester';
import { OrganizationState } from '../../../types'; import { OrganizationState, OrgRole } from '../../../types';
import { initialState, organizationLoaded, organizationReducer, setOrganizationName } from './reducers'; import {
initialState,
organizationLoaded,
organizationReducer,
userOrganizationsLoaded,
setOrganizationName,
} from './reducers';
describe('organizationReducer', () => { describe('organizationReducer', () => {
describe('when organizationLoaded is dispatched', () => { describe('when organizationLoaded is dispatched', () => {
@ -10,6 +16,7 @@ describe('organizationReducer', () => {
.whenActionIsDispatched(organizationLoaded({ id: 1, name: 'An org' })) .whenActionIsDispatched(organizationLoaded({ id: 1, name: 'An org' }))
.thenStateShouldEqual({ .thenStateShouldEqual({
organization: { id: 1, name: 'An org' }, organization: { id: 1, name: 'An org' },
userOrgs: [],
}); });
}); });
}); });
@ -21,6 +28,23 @@ describe('organizationReducer', () => {
.whenActionIsDispatched(setOrganizationName('New Name')) .whenActionIsDispatched(setOrganizationName('New Name'))
.thenStateShouldEqual({ .thenStateShouldEqual({
organization: { id: 1, name: 'New Name' }, 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 { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { Organization, OrganizationState } from 'app/types'; import { Organization, OrganizationState, UserOrg } from 'app/types';
export const initialState: OrganizationState = { export const initialState: OrganizationState = {
organization: {} as Organization, organization: {} as Organization,
userOrgs: [] as UserOrg[],
}; };
const organizationSlice = createSlice({ const organizationSlice = createSlice({
@ -16,10 +17,13 @@ const organizationSlice = createSlice({
setOrganizationName: (state, action: PayloadAction<string>): OrganizationState => { setOrganizationName: (state, action: PayloadAction<string>): OrganizationState => {
return { ...state, organization: { ...state.organization, name: action.payload } }; 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; export const organizationReducer = organizationSlice.reducer;

View File

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