Skip to content

Commit

Permalink
[ASAP-585] - Enable Users to start a Discussion (#4459)
Browse files Browse the repository at this point in the history
* adds discussion field to compliance reports

* adds create new discussion endpoint

* adds create to discussion controller

* handles discussion creation in data providers and routes with validations

* updates graphql query

* creates start compliance report discussion modal

* creates hooks and selectors for discussion creation and team state sync

* implements start discussion on compliance reports

* fixes tests and typechecks

* fixes autogenerated files with missing props

* fixes linting

* fixes broken tests

* fixes typecheck issue

* updates migration description

* renames reply variable to message to avoid confusion

* correct text "start discussion" instead of "start a discussion"

* fixes ui issues

* adds toast when discussion started & fixes compliance report closing when version expanded

* changes QuickCheckReplyModal to be reused for quick checks and compliance discussions

* adds missing tests

* simplifies DiscussionRequest type and reuses it for both discussions and replies

* adds workspace missing tests

* adds missing tests for create discussion route

* adds missing tests for compliance report card

* adds missing hooks and selectors tests

* removes duplicated line
  • Loading branch information
AimeurAmin authored Dec 19, 2024
1 parent 7f3b998 commit 739cc27
Show file tree
Hide file tree
Showing 41 changed files with 1,772 additions and 153 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { Toast } from '@asap-hub/react-components';
import React, { createContext, useState } from 'react';

type FormType = 'manuscript' | 'compliance-report' | 'quick-check' | '';
type FormType =
| 'manuscript'
| 'compliance-report'
| 'quick-check'
| 'compliance-report-discussion'
| '';

type ManuscriptToastContextData = {
setFormType: React.Dispatch<React.SetStateAction<FormType>>;
Expand All @@ -22,6 +27,7 @@ export const ManuscriptToastProvider = ({
manuscript: 'Manuscript submitted successfully.',
'compliance-report': 'Compliance Report submitted successfully.',
'quick-check': 'Replied to quick check successfully.',
'compliance-report-discussion': 'Discussion started successfully.',
};

return (
Expand Down
25 changes: 19 additions & 6 deletions apps/crn-frontend/src/network/teams/Workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@ import {
TeamTool,
TeamResponse,
ManuscriptPutRequest,
DiscussionPatchRequest,
DiscussionRequest,
} from '@asap-hub/model';
import { network, useRouteParams } from '@asap-hub/routing';
import { ToastContext, useCurrentUserCRN } from '@asap-hub/react-context';

import {
useCreateComplianceDiscussion,
useDiscussionById,
useIsComplianceReviewer,
usePatchTeamById,
usePutManuscript,
useReplyToDiscussion,
useVersionById,
} from './state';
import { useEligibilityReason } from './useEligibilityReason';
import { useManuscriptToast } from './useManuscriptToast';
Expand All @@ -37,6 +39,8 @@ const Workspace: React.FC<WorkspaceProps> = ({ team }) => {
const patchTeam = usePatchTeamById(team.id);
const updateManuscript = usePutManuscript();
const replyToDiscussion = useReplyToDiscussion();
const createComplianceDiscussion = useCreateComplianceDiscussion();

const getDiscussion = useDiscussionById;
const toast = useContext(ToastContext);

Expand Down Expand Up @@ -76,14 +80,23 @@ const Workspace: React.FC<WorkspaceProps> = ({ team }) => {
}
}
isComplianceReviewer={isComplianceReviewer}
onReplyToDiscussion={async (
id: string,
patch: DiscussionPatchRequest,
) => {
await replyToDiscussion(id, patch);
onSave={async (id: string, patch: DiscussionRequest) => {
await replyToDiscussion(id, patch as DiscussionRequest);
setFormType('quick-check');
}}
getDiscussion={getDiscussion}
createComplianceDiscussion={async (
complianceReportId: string,
message: string,
) => {
const discussionId = await createComplianceDiscussion(
complianceReportId,
message,
);
setFormType('compliance-report-discussion');
return discussionId;
}}
useVersionById={useVersionById}
/>
</Route>
<Route exact path={path + route.tools.template}>
Expand Down
9 changes: 3 additions & 6 deletions apps/crn-frontend/src/network/teams/__mocks__/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
createTeamResponse,
} from '@asap-hub/fixtures';
import {
DiscussionPatchRequest,
DiscussionRequest,
DiscussionResponse,
ListLabsResponse,
ListTeamResponse,
Expand Down Expand Up @@ -68,12 +68,9 @@ export const getDiscussion = jest.fn(
);

export const updateDiscussion = jest.fn(
async (
id: string,
patch: DiscussionPatchRequest,
): Promise<DiscussionResponse> => {
async (id: string, patch: DiscussionRequest): Promise<DiscussionResponse> => {
const discussion = await getDiscussion(id);
discussion.replies = [createMessage(patch.replyText)];
discussion.replies = [createMessage(patch.text)];
return discussion;
},
);
179 changes: 176 additions & 3 deletions apps/crn-frontend/src/network/teams/__tests__/Workspace.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import {
fireEvent,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TeamResponse } from '@asap-hub/model';
import {
ManuscriptVersion,
TeamManuscript,
TeamResponse,
} from '@asap-hub/model';
import {
createDiscussionResponse,
createManuscriptResponse,
Expand All @@ -31,17 +35,30 @@ import {
updateManuscript,
getDiscussion,
updateDiscussion,
createComplianceDiscussion,
} from '../api';

import Workspace from '../Workspace';
import { ManuscriptToastProvider } from '../ManuscriptToastProvider';
import { useVersionById } from '../state';
import { useManuscriptToast } from '../useManuscriptToast';

jest.setTimeout(60000);
jest.mock('../api', () => ({
patchTeam: jest.fn(),
updateManuscript: jest.fn().mockResolvedValue({}),
getDiscussion: jest.fn(),
updateDiscussion: jest.fn(),
createComplianceDiscussion: jest.fn(),
}));

jest.mock('../state', () => ({
...jest.requireActual('../state'),
useVersionById: jest.fn(),
}));

jest.mock('../useManuscriptToast', () => ({
useManuscriptToast: jest.fn(),
}));

const mockPatchTeam = patchTeam as jest.MockedFunction<typeof patchTeam>;
Expand Down Expand Up @@ -94,6 +111,28 @@ const user = {
],
};

const mockSetVersion = jest.fn();

const version = createManuscriptResponse().versions[0] as ManuscriptVersion;

const mockVersionData = {
...version,
complianceReport: {
...version.complianceReport,
discussionId: 'discussion-id',
},
};

beforeEach(() => {
(useVersionById as jest.Mock).mockImplementation(() => [
mockVersionData,
mockSetVersion,
]);
(useManuscriptToast as jest.Mock).mockImplementation(() => ({
setFormType: jest.fn(),
}));
});

afterEach(jest.resetAllMocks);

describe('Manuscript', () => {
Expand Down Expand Up @@ -390,8 +429,19 @@ describe('manuscript quick check discussion', () => {

it('fetches quick check discussion details', async () => {
enable('DISPLAY_MANUSCRIPTS');
mockGetDiscussion.mockImplementation(
async () => acknowledgedGrantNumberDiscussion,
);

(useVersionById as jest.Mock).mockImplementation(() => [
{
...mockVersionData,
acknowledgedGrantNumberDetails: acknowledgedGrantNumberDiscussion,
acknowledgedGrantNumber: 'No',
},
mockSetVersion,
]);

mockGetDiscussion.mockResolvedValueOnce(acknowledgedGrantNumberDiscussion);
const { getByText, findByTestId, getByLabelText, getByTestId } =
renderWithWrapper(
<Workspace
Expand Down Expand Up @@ -421,6 +471,20 @@ describe('manuscript quick check discussion', () => {

it('replies to a quick check discussion', async () => {
enable('DISPLAY_MANUSCRIPTS');

mockGetDiscussion.mockImplementation(
async () => acknowledgedGrantNumberDiscussion,
);

(useVersionById as jest.Mock).mockImplementation(() => [
{
...mockVersionData,
acknowledgedGrantNumberDetails: acknowledgedGrantNumberDiscussion,
acknowledgedGrantNumber: 'No',
},
mockSetVersion,
]);

mockGetDiscussion.mockResolvedValue(acknowledgedGrantNumberDiscussion);
mockUpdateDiscussion.mockResolvedValue(acknowledgedGrantNumberDiscussion);
const { findByTestId, getByRole, getByTestId, getByLabelText } =
Expand Down Expand Up @@ -465,8 +529,117 @@ describe('manuscript quick check discussion', () => {

expect(mockUpdateDiscussion).toHaveBeenLastCalledWith(
acknowledgedGrantNumberDiscussion.id,
{ replyText: 'new reply' },
{ text: 'new reply' },
expect.anything(),
);
});

it('creates a new discussion to compliance report', async () => {
enable('DISPLAY_MANUSCRIPTS');
(createComplianceDiscussion as jest.Mock).mockResolvedValue({
id: 'discussion-id',
});

const mockManuscript = {
id: `manuscript_1`,
title: `Manuscript 1`,
teamId: 'team-1',
status: 'Waiting for Report',
count: 1,
versions: [
{
...manuscript.versions[0],
complianceReport: {
id: 'compliance-report-id',
url: 'http://example.com/file.pdf',
description: 'A description',
count: 1,
createdDate: '2024-12-10T20:36:54Z',
createdBy: {
displayName: 'John Doe',
email: 'john@doe.com',
firstName: 'John',
lastName: 'Doe',
avatarUrl: 'http://example.com/avatar.jpg',
teams: [
{
id: 'team-1',
name: 'Team 1',
},
],
id: 'user-1',
},
},
} as ManuscriptVersion,
],
};

mockGetDiscussion.mockImplementation(
async () => acknowledgedGrantNumberDiscussion,
);

(useVersionById as jest.Mock).mockImplementation(() => [
{
...mockManuscript.versions[0],
acknowledgedGrantNumberDetails: acknowledgedGrantNumberDiscussion,
acknowledgedGrantNumber: 'No',
},
mockSetVersion,
]);

mockGetDiscussion.mockResolvedValue(acknowledgedGrantNumberDiscussion);
mockUpdateDiscussion.mockResolvedValue(acknowledgedGrantNumberDiscussion);
const { findByTestId, getByText, getByLabelText, getByTestId, findByText } =
renderWithWrapper(
<ManuscriptToastProvider>
<Workspace
team={{
...createTeamResponse(),
manuscripts: [mockManuscript as TeamManuscript],
tools: [],
}}
/>
</ManuscriptToastProvider>,
user,
);

await act(async () => {
userEvent.click(await findByTestId('collapsible-button'));
await waitFor(() => {
expect(getByLabelText('Expand Version')).toBeInTheDocument();
});
userEvent.click(getByLabelText('Expand Version'));
await waitFor(() => {
expect(getByLabelText('Expand Report')).toBeInTheDocument();
});
userEvent.click(getByLabelText('Expand Report'));
});

await waitFor(() => {
expect(getByText(/Start Discussion/i)).toBeInTheDocument();
});

await act(async () => {
userEvent.click(await findByText(/Start Discussion/i));
});

const replyEditor = getByTestId('editor');
await act(async () => {
userEvent.click(replyEditor);
userEvent.tab();
fireEvent.input(replyEditor, { data: 'New discussion message' });
userEvent.tab();
});

expect(await findByText(/Send/i)).toBeInTheDocument();

userEvent.click(await findByText(/Send/i));
await waitFor(() => {
expect(createComplianceDiscussion).toHaveBeenCalledWith(
'compliance-report-id',
'New discussion message',
'Bearer access_token',
);
});
});
});
Loading

0 comments on commit 739cc27

Please sign in to comment.