Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ASAP-767 Adding Assigned Users #4517

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion apps/crn-frontend/src/network/teams/Compliance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
} from './state';

import { usePagination, usePaginationParams, useSearch } from '../../hooks';
import { useAssignedUsersSuggestions } from '../../shared-state/shared-research';
import { useManuscriptToast } from './useManuscriptToast';

const Compliance: React.FC = () => {
const { currentPage, pageSize } = usePaginationParams();
Expand All @@ -30,7 +32,7 @@ const Compliance: React.FC = () => {
pageSize,
);
const isComplianceReviewer = useIsComplianceReviewer();

const getAssignedUsersSuggestions = useAssignedUsersSuggestions();
const [sort, setSort] = useState<SortCompliance>('team_asc');

const [sortingDirection, setSortingDirection] =
Expand All @@ -43,9 +45,12 @@ const Compliance: React.FC = () => {
) => {
const manuscriptResponse = await updateManuscript(id, manuscript);
result.refresh(manuscriptResponse);
setFormType({ type: 'assigned-users', accent: 'successLarge' });
return manuscriptResponse;
};

const { setFormType } = useManuscriptToast();

return (
<article>
<SearchField
Expand All @@ -65,6 +70,15 @@ const Compliance: React.FC = () => {
numberOfPages={numberOfPages}
renderPageHref={renderPageHref}
onUpdateManuscript={handleUpdateManuscript}
getAssignedUsersSuggestions={(input) =>
getAssignedUsersSuggestions(input).then((authors) =>
authors.map((author) => ({
author,
label: author.displayName,
value: author.id,
})),
)
}
/>
</SearchFrame>
</article>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Toast } from '@asap-hub/react-components';
import React, { createContext, useState } from 'react';

type FormType =
| 'assigned-users'
| 'manuscript'
| 'compliance-report'
| 'quick-check'
Expand Down Expand Up @@ -36,6 +37,7 @@ export const ManuscriptToastProvider = ({
});

const formTypeMapping = {
'assigned-users': 'User(s) assigned to a manuscript successfully.',
manuscript: 'Manuscript submitted successfully.',
'compliance-report': 'Compliance Report submitted successfully.',
'quick-check': 'Replied to quick check successfully.',
Expand Down
3 changes: 3 additions & 0 deletions apps/crn-frontend/src/network/teams/__mocks__/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ export const getManuscripts = jest.fn(
id: 'DA1-000463-002-org-G-1',
lastUpdated: '2020-09-23T20:45:22.000Z',
manuscriptId: 'manuscript-id-1',
title: 'Manuscript 1',
teams: 'Team 1',
assignedUsers: [],
team: {
id: 'team-id-1',
displayName: 'Team 1',
Expand Down
123 changes: 115 additions & 8 deletions apps/crn-frontend/src/network/teams/__tests__/Compliance.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createAlgoliaResponse } from '@asap-hub/algolia';
import { mockConsoleError } from '@asap-hub/dom-test-utils';
import {
createManuscriptResponse,
Expand All @@ -11,21 +12,27 @@ import userEvent from '@testing-library/user-event';
import { Suspense } from 'react';
import { MemoryRouter, Route } from 'react-router-dom';
import { RecoilRoot } from 'recoil';

import { Auth0Provider, WhenReady } from '../../../auth/test-utils';
import { getOpenScienceMembers } from '../../users/api';
import { getManuscripts, updateManuscript } from '../api';
import { manuscriptsState } from '../state';

import Compliance from '../Compliance';
import { ManuscriptToastProvider } from '../ManuscriptToastProvider';
import { manuscriptsState } from '../state';

mockConsoleError();

jest.mock('../api', () => ({
...jest.requireActual('../api'),
getOpenScienceMembers: jest.fn(),
getManuscripts: jest.fn(),
updateManuscript: jest.fn(),
}));

jest.mock('../../users/api', () => ({
...jest.requireActual('../api'),
getOpenScienceMembers: jest.fn(),
}));

const mockGetManuscripts = getManuscripts as jest.MockedFunction<
typeof getManuscripts
>;
Expand All @@ -34,6 +41,10 @@ const mockUpdateManuscript = updateManuscript as jest.MockedFunction<
typeof updateManuscript
>;

const mockGetOpenScienceMembers = getOpenScienceMembers as jest.MockedFunction<
typeof getOpenScienceMembers
>;

const user = createUserResponse({}, 1);
user.role = 'Staff';
user.openScienceTeamMember = true;
Expand All @@ -56,11 +67,13 @@ const renderCompliancePage = async () => {
<Auth0Provider user={user}>
<WhenReady>
<MemoryRouter initialEntries={[{ pathname: '/' }]}>
<Route path="/">
<Frame title={null}>
<Compliance />
</Frame>
</Route>
<ManuscriptToastProvider>
<Route path="/">
<Frame title={null}>
<Compliance />
</Frame>
</Route>
</ManuscriptToastProvider>
</MemoryRouter>
</WhenReady>
</Auth0Provider>
Expand All @@ -75,6 +88,7 @@ const renderCompliancePage = async () => {

beforeEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});

it('renders error message when the request is not a 2XX', async () => {
Expand Down Expand Up @@ -249,3 +263,96 @@ it('manuscripts remain the same when getting previous manuscripts fails', async
}),
).not.toBeInTheDocument();
});

it('fetches assigned users suggestions and displays them properly', async () => {
const mockManuscript: PartialManuscriptResponse = {
...createPartialManuscriptResponse(),
manuscriptId: 'manuscript-id-1',
status: 'Review Compliance Report',
};

mockGetManuscripts.mockResolvedValue({
items: [mockManuscript],
total: 1,
});

const userResponse = createUserResponse();

const algoliaUsersResponse = createAlgoliaResponse<'crn', 'user'>([
{
...userResponse,
firstName: 'Billie',
lastName: 'Eilish',
displayName: 'Billie Eilish',
objectID: userResponse.id,
__meta: { type: 'user' },
_tags: userResponse.tags?.map(({ name }) => name) || [],
},
]);
mockGetOpenScienceMembers.mockResolvedValue({
items: algoliaUsersResponse.hits,
total: algoliaUsersResponse.nbHits,
});

await renderCompliancePage();

expect(screen.queryByText(/Billie Eilish/i)).not.toBeInTheDocument();

userEvent.click(screen.getByTitle(/Add user/i));

userEvent.type(
screen.getByRole('textbox', { name: /Assign User/i }),
'Billie',
);

expect(mockGetOpenScienceMembers).toHaveBeenCalled();

await waitFor(() => {
expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
});

expect(screen.getAllByText(/Billie Eilish/i).length > 0).toBe(true);
});

it('displays success message when assigning users', async () => {
const mockManuscript: PartialManuscriptResponse = {
...createPartialManuscriptResponse(),
manuscriptId: 'manuscript-id-1',
status: 'Review Compliance Report',
assignedUsers: [
{
id: 'user-id-1',
firstName: 'Taylor',
lastName: 'Swift',
avatarUrl: 'https://example.com',
},
],
};

mockGetManuscripts.mockResolvedValue({
items: [mockManuscript],
total: 1,
});

mockUpdateManuscript.mockResolvedValue({
...createManuscriptResponse(),
id: 'manuscript-id-1',
});

mockGetOpenScienceMembers.mockResolvedValue({
items: [],
total: 0,
});

await renderCompliancePage();

userEvent.click(screen.getByLabelText(/Edit Assigned Users/i));

userEvent.click(screen.getByRole('button', { name: 'Assign' }));

await waitFor(() => {
expect(
screen.getByText('User(s) assigned to a manuscript successfully.'),
).toBeInTheDocument();
});
});
1 change: 1 addition & 0 deletions apps/crn-frontend/src/network/teams/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ export const useManuscripts = (
? {
...previousManuscriptItem,
status: updatedManuscriptItem.status,
assignedUsers: updatedManuscriptItem.assignedUsers,
}
: previousManuscriptItem,
),
Expand Down
73 changes: 73 additions & 0 deletions apps/crn-frontend/src/network/users/__tests__/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { GetListOptions } from '@asap-hub/frontend-utils';
import { API_BASE_URL } from '../../../config';
import {
getInstitutions,
getOpenScienceMembers,
getUser,
getUsers,
getUsersAndExternalAuthors,
Expand Down Expand Up @@ -269,6 +270,78 @@ describe('getUsersAndExternalAuthors', () => {
});
});

describe('getOpenScienceMembers', () => {
const search: jest.MockedFunction<Search> = jest.fn();

const algoliaSearchClient = {
search,
} as unknown as AlgoliaSearchClient<'crn'>;

const defaultOptions: GetListOptions = {
searchQuery: '',
pageSize: null,
currentPage: null,
filters: new Set(),
};

beforeEach(() => {
const userResponse = createUserResponse();
const algoliaUsersResponse = createAlgoliaResponse<'crn', 'user'>([
{
...userResponse,
objectID: userResponse.id,
__meta: { type: 'user' },
_tags: userResponse.tags?.map(({ name }) => name) || [],
},
]);

search.mockReset();
search.mockResolvedValue({
...algoliaUsersResponse,
hits: algoliaUsersResponse.hits,
});
});

it('will filter users by openScienceTeamMember:true by default', async () => {
await getOpenScienceMembers(algoliaSearchClient, {
...defaultOptions,
});
expect(search).toHaveBeenCalledWith(
['user'],
'',
expect.objectContaining({
filters: 'openScienceTeamMember:true',
}),
);
});

it('will not default to not specifying page and limits hits per page by default', async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This description is a bit confusing

await getOpenScienceMembers(algoliaSearchClient, {
...defaultOptions,
});
expect(search).toHaveBeenCalledWith(
['user'],
'',
expect.objectContaining({
hitsPerPage: undefined,
page: undefined,
}),
);
});

it('will pass the search query to algolia', async () => {
await getOpenScienceMembers(algoliaSearchClient, {
...defaultOptions,
searchQuery: 'Hello World!',
});
expect(search).toHaveBeenCalledWith(
['user'],
'Hello World!',
expect.objectContaining({}),
);
});
});

describe('getUser', () => {
it('makes an authorized GET request for the user id', async () => {
nock(API_BASE_URL, {
Expand Down
19 changes: 19 additions & 0 deletions apps/crn-frontend/src/network/users/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,25 @@ export const getUsers = async (
};
};

export const getOpenScienceMembers = async (
algoliaClient: AlgoliaClient<'crn'>,
{ searchQuery, currentPage, pageSize }: GetListOptions,
): Promise<ListResponse<UserResponse | ExternalAuthorResponse>> => {
const result = await algoliaClient.search(['user'], searchQuery, {
filters: 'openScienceTeamMember:true',
page: currentPage ?? undefined,
hitsPerPage: pageSize ?? undefined,
restrictSearchableAttributes: ['displayName'],
});

return {
items: result.hits,
total: result.nbHits,
algoliaIndexName: result.index,
algoliaQueryId: result.queryID,
};
};

export const getUsersAndExternalAuthors = async (
algoliaClient: AlgoliaClient<'crn'>,
{ searchQuery, currentPage, pageSize }: GetListOptions,
Expand Down
17 changes: 16 additions & 1 deletion apps/crn-frontend/src/shared-state/shared-research.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import {
createResearchOutput,
updateTeamResearchOutput,
} from '../network/teams/api';
import { getUsersAndExternalAuthors } from '../network/users/api';
import {
getOpenScienceMembers,
getUsersAndExternalAuthors,
} from '../network/users/api';
import {
getResearchTags,
getResearchOutputs,
Expand Down Expand Up @@ -111,6 +114,18 @@ export const useRelatedEventsSuggestions = () => {
);
};

export const useAssignedUsersSuggestions = () => {
const algoliaClient = useAlgolia();

return (searchQuery: string) =>
getOpenScienceMembers(algoliaClient.client, {
searchQuery,
currentPage: null,
pageSize: 100,
filters: new Set(),
}).then(({ items }) => items);
};

export const useAuthorSuggestions = () => {
const algoliaClient = useAlgolia();

Expand Down
Loading