Skip to content

Commit

Permalink
feat: avoid to call getRightsRequest on every mount
Browse files Browse the repository at this point in the history
refs: SHELL-128 (#296)
  • Loading branch information
rodleyorosa authored Aug 21, 2023
1 parent 115ac44 commit d9d881a
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 110 deletions.
4 changes: 2 additions & 2 deletions src/mocks/handlers/getRightsRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import { type ResponseResolver, type RestContext, type RestRequest } from 'msw';

import { GetRightsResponse } from '../../../types';

type GetRightsRequestBody = {
export type GetRightsRequestBody = {
GetRightsRequest: {
_jsns: string;
ace: Array<{ right: string }>;
};
};

type GetRightsResponseBody = {
export type GetRightsResponseBody = {
Body: {
GetRightsResponse: GetRightsResponse;
Fault?: { Detail?: { Error?: { Code?: string; Detail?: string } }; Reason?: { Text: string } };
Expand Down
162 changes: 161 additions & 1 deletion src/settings/accounts-settings.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@ import { faker } from '@faker-js/faker';
import { act, screen, waitFor, within } from '@testing-library/react';
import { head, shuffle, tail } from 'lodash';
import { rest } from 'msw';
import { Link, Route, Switch } from 'react-router-dom';

import { AccountsSettings } from './accounts-settings';
import { Account, BatchRequest, CreateIdentityResponse, Identity } from '../../types';
import {
GetRightsRequestBody,
GetRightsResponseBody,
getRightsRequest
} from '../mocks/handlers/getRightsRequest';
import server, { waitForRequest } from '../mocks/server';
import { useAccountStore } from '../store/account';
import { setup } from '../test/utils';
Expand Down Expand Up @@ -1596,7 +1602,6 @@ describe('Account setting', () => {
expect(screen.getByRole('button', { name: /save/i })).toBeEnabled();

await user.click(screen.getByRole('button', { name: /save/i }));

const request = await pendingBatchRequest;
const requestBody = (request?.body as { Body: { BatchRequest: BatchRequest } }).Body;
expect(requestBody.BatchRequest.CreateIdentityRequest).toBeUndefined();
Expand All @@ -1610,5 +1615,160 @@ describe('Account setting', () => {
const successSnackbar = await screen.findByText('Edits saved correctly');
expect(successSnackbar).toBeVisible();
});
test('GetRightRequest is called only once when the user navigates inside account settings', async () => {
const defaultFirstName = faker.person.firstName();
const defaultLastName = faker.person.lastName();
const defaultFullName = faker.person.fullName({
firstName: defaultFirstName,
lastName: defaultLastName
});
const defaultEmail = faker.internet.email({
firstName: defaultFirstName,
lastName: defaultLastName
});
const defaultId = faker.string.uuid();

const identitiesArray: Account['identities']['identity'] = [
{
id: defaultId,
name: 'DEFAULT',
_attrs: {
zimbraPrefIdentityName: defaultFullName,
zimbraPrefReplyToEnabled: 'FALSE',
zimbraPrefFromDisplay: defaultFirstName,
zimbraPrefFromAddress: defaultEmail,
zimbraPrefIdentityId: defaultId,
zimbraPrefFromAddressType: 'sendAs'
}
}
];

const account: Account = {
name: defaultEmail,
rights: { targets: [] },
signatures: { signature: [] },
id: defaultId,
displayName: '',
identities: {
identity: identitiesArray
}
};

useAccountStore.setState((previousState) => ({
...previousState,
account
}));

const requestFn = jest.fn();
server.use(
rest.post<GetRightsRequestBody, never, GetRightsResponseBody>(
'/service/soap/GetRightsRequest',
(req, res, context) => {
requestFn();
return getRightsRequest(req, res, context);
}
)
);

const TestComponent = (): JSX.Element => (
<>
<Switch>
<Route path="/other">
<div>
<span data-testid="text">Other page</span>
<Link to="/settings/accounts">Go to Account Settings</Link>
</div>
</Route>
<Route path="/settings/accounts">
<AccountsSettings />
<Link to="/other">Go to Other page</Link>
</Route>
</Switch>
</>
);

const { user } = setup(<TestComponent />, { initialRouterEntries: [`/settings/accounts`] });

await waitForGetRightsRequest();
expect(requestFn).toHaveBeenCalledTimes(1);
expect(screen.queryByTestId('text')).not.toBeInTheDocument();
expect(screen.getByText(/Accounts List/i)).toBeVisible();
await user.click(screen.getByRole('link', { name: 'Go to Other page' }));
expect(screen.queryByTestId('text')).toBeVisible();
expect(screen.queryByText('sendAs')).not.toBeInTheDocument();
await user.click(screen.getByRole('link', { name: 'Go to Account Settings' }));
expect(screen.getByText('sendAs')).toBeVisible();
expect(requestFn).toHaveBeenCalledTimes(1);
});

test('When rights state is updated, the delegates list will be updated accordingly', async () => {
const defaultFirstName = faker.person.firstName();
const defaultLastName = faker.person.lastName();
const defaultFullName = faker.person.fullName({
firstName: defaultFirstName,
lastName: defaultLastName
});
const defaultEmail = faker.internet.email({
firstName: defaultFirstName,
lastName: defaultLastName
});
const defaultId = faker.string.uuid();

const identitiesArray: Account['identities']['identity'] = [
{
id: defaultId,
name: 'DEFAULT',
_attrs: {
zimbraPrefIdentityName: defaultFullName,
zimbraPrefReplyToEnabled: 'FALSE',
zimbraPrefFromDisplay: defaultFirstName,
zimbraPrefFromAddress: defaultEmail,
zimbraPrefIdentityId: defaultId,
zimbraPrefFromAddressType: 'sendAs'
}
}
];

const account: Account = {
name: defaultEmail,
rights: { targets: [] },
signatures: { signature: [] },
id: defaultId,
displayName: '',
identities: {
identity: identitiesArray
}
};

useAccountStore.setState((previousState) => ({
...previousState,
account
}));

const requestFn = jest.fn();
server.use(
rest.post<GetRightsRequestBody, never, GetRightsResponseBody>(
'/service/soap/GetRightsRequest',
(req, res, context) => {
requestFn();
return getRightsRequest(req, res, context);
}
)
);

setup(<AccountsSettings />);

await waitForGetRightsRequest();
expect(requestFn).toHaveBeenCalledTimes(1);
expect(screen.getByText(/Accounts List/i)).toBeVisible();

act(() => {
useAccountStore.setState((previousState) => ({
...previousState,
rights: []
}));
});
expect(screen.queryByText('sendAs')).not.toBeInTheDocument();
});
});
});
84 changes: 49 additions & 35 deletions src/settings/accounts-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import React, { useCallback, useMemo, useState, useEffect, useRef } from 'react';

import { Container, useSnackbar } from '@zextras/carbonio-design-system';
import { TFunction } from 'i18next';
import { produce } from 'immer';
import { map, find, isEmpty, reduce, findIndex, filter, size } from 'lodash';
import { useTranslation } from 'react-i18next';
Expand Down Expand Up @@ -40,6 +41,7 @@ import {
AccountState
} from '../../types';
import type { ModifyIdentityRequest } from '../../types';
import { AccountACEInfo } from '../../types/network/entities';
import { SHELL_APP_ID } from '../constants';
import { getSoapFetch } from '../network/fetch';
import { useAccountStore, useUserAccount, useUserSettings } from '../store/account';
Expand Down Expand Up @@ -88,6 +90,33 @@ function mapToModifyIdentityRequests(
);
}

function generateDelegateList(ace: AccountACEInfo[] | undefined, t: TFunction): DelegateType[] {
return reduce(
ace,
(accumulator: Array<DelegateType>, item, idx) => {
const index = findIndex(accumulator, { email: item.d });
const translatedRight = t('settings.account.delegates.right', {
context: item.right.toLowerCase(),
defaultValue: item.right
});
if (index === -1) {
accumulator.push({ email: item.d || '', right: translatedRight, id: idx.toString() });
} else {
accumulator[index] = {
email: item.d || '',
right: t('settings.account.delegates.multiple_rights', {
defaultValue: `{{rights.0]}} and {{rights.1}}`,
rights: [translatedRight, accumulator[index].right]
}),
id: idx.toString()
};
}
return accumulator;
},
[]
);
}

export const AccountsSettings = (): JSX.Element => {
const [t] = useTranslation();
const createSnackbar = useSnackbar();
Expand Down Expand Up @@ -346,42 +375,27 @@ export const AccountsSettings = (): JSX.Element => {

const [delegates, setDelegates] = useState<DelegateType[]>([]);

const rights = useAccountStore((state) => state.rights);

useEffect(() => {
getSoapFetch(SHELL_APP_ID)<GetRightsRequest, GetRightsResponse>('GetRights', {
_jsns: 'urn:zimbraAccount',
ace: [{ right: 'sendAs' }, { right: 'sendOnBehalfOf' }]
}).then((value) => {
if (value.ace) {
const { ace } = value;
const result = reduce(
ace,
(accumulator: Array<DelegateType>, item, idx) => {
const index = findIndex(accumulator, { email: item.d });
const translatedRight = t('settings.account.delegates.right', {
context: item.right.toLowerCase(),
defaultValue: item.right
});
if (index === -1) {
accumulator.push({ email: item.d || '', right: translatedRight, id: idx.toString() });
} else {
accumulator.push({
email: item.d || '',
right: t('settings.account.delegates.multiple_rights', {
defaultValue: `{{rights.0]}} and {{rights.1}}`,
rights: [translatedRight, accumulator[index].right]
}),
id: idx.toString()
});
accumulator.splice(index, 1);
}
return accumulator;
},
[]
);
setDelegates(result);
}
});
}, [t]);
if (!rights) {
getSoapFetch(SHELL_APP_ID)<GetRightsRequest, GetRightsResponse>('GetRights', {
_jsns: 'urn:zimbraAccount',
ace: [{ right: 'sendAs' }, { right: 'sendOnBehalfOf' }]
}).then((value) => {
if (value.ace) {
const { ace } = value;
setDelegates(generateDelegateList(ace, t));
}
useAccountStore.setState((state) => ({
...state,
rights: value.ace ?? []
}));
});
} else {
setDelegates(generateDelegateList(rights, t));
}
}, [t, rights]);

const personaSettings = useMemo<JSX.Element | null>(() => {
const identity = identities[selectedIdentityId];
Expand Down
1 change: 1 addition & 0 deletions src/store/account/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { AccountState } from '../../../types';
export const useAccountStore = create<AccountState>()(() => ({
authenticated: false,
account: undefined,
rights: undefined,
version: '',
settings: {
prefs: {},
Expand Down
2 changes: 2 additions & 0 deletions types/account/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DARK_READER_PROP_KEY, DELEGATED_SEND_SAVE_TARGET } from '../../src/cons
import { StringOfLength } from '../../src/utils/typeUtils';
import type { SHELL_APP_ID } from '../exports';
import type { DarkReaderPropValues } from '../misc';
import { AccountACEInfo } from '../network/entities';

export interface ZimletProp {
name: string;
Expand All @@ -31,6 +32,7 @@ export type SoapFetch = <Request, Response>(
export type AccountState = {
authenticated: boolean;
account?: Account;
rights?: Array<AccountACEInfo>;
settings: AccountSettings;
zimbraVersion?: string;
usedQuota: number;
Expand Down
Loading

0 comments on commit d9d881a

Please sign in to comment.