Skip to content

Commit cfed356

Browse files
feat(annotation-thread): use of event emitter and hooks refactoring (#3184)
* feat(annotation-thread): use of event emitter and hooks refactoring * feat(annotation-thread): use of event emitter and hooks refactoring
1 parent f773aa3 commit cfed356

File tree

9 files changed

+258
-153
lines changed

9 files changed

+258
-153
lines changed

src/elements/content-sidebar/activity-feed/annotation-thread/AnnotationThread.js

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { IntlProvider } from 'react-intl';
1111
import type EventEmitter from 'events';
1212
import AnnotationThreadContent from './AnnotationThreadContent';
1313
import AnnotationThreadCreate from './AnnotationThreadCreate';
14+
import useAnnotationThread from './useAnnotationThread';
1415
import API from '../../../../api/APIFactory';
1516
import { DEFAULT_COLLAB_DEBOUNCE, DEFAULT_HOSTNAME_API } from '../../../../constants';
1617

@@ -59,6 +60,8 @@ const AnnotationThread = ({
5960
target,
6061
token,
6162
}: Props) => {
63+
const [mentionSelectorContacts, setMentionSelectorContacts] = React.useState<SelectorItems<>>([]);
64+
6265
const api = new API({
6366
apiHost,
6467
cache,
@@ -67,7 +70,25 @@ const AnnotationThread = ({
6770
token,
6871
});
6972

70-
const [mentionSelectorContacts, setMentionSelectorContacts] = React.useState([]);
73+
const { id: fileId, permissions = {} } = file;
74+
75+
const {
76+
annotation,
77+
replies,
78+
isLoading,
79+
error,
80+
annotationActions: { handleAnnotationDelete, handleAnnotationEdit, handleAnnotationStatusChange },
81+
annotationEvents: { handleAnnotationCreateEnd: handleAnnotationCreateEndEvent, handleAnnotationCreateStart },
82+
repliesActions: { handleReplyEdit, handleReplyCreate, handleReplyDelete },
83+
} = useAnnotationThread({
84+
api,
85+
annotationId,
86+
currentUser,
87+
fileId,
88+
filePermissions: permissions,
89+
errorCallback: onError,
90+
eventEmitter,
91+
});
7192

7293
const getAvatarUrl = async (userId: string): Promise<?string> =>
7394
api.getUsersAPI(false).getAvatarUrlWithAccessToken(userId, file.id);
@@ -85,6 +106,11 @@ const AnnotationThread = ({
85106
);
86107
}, DEFAULT_COLLAB_DEBOUNCE);
87108

109+
const handleAnnotationCreateEnd = (newAnnotation: Object, requestId: string) => {
110+
handleAnnotationCreateEndEvent(newAnnotation, requestId);
111+
onAnnotationCreate(newAnnotation);
112+
};
113+
88114
return (
89115
<div className={classNames('AnnotationThread', className)} data-testid="annotation-thread">
90116
<IntlProvider locale={language} messages={messages}>
@@ -97,22 +123,28 @@ const AnnotationThread = ({
97123
getMentionWithQuery={getMentionWithQuery}
98124
handleCancel={handleCancel}
99125
mentionSelectorContacts={mentionSelectorContacts}
100-
onAnnotationCreate={onAnnotationCreate}
126+
onAnnotationCreateEnd={handleAnnotationCreateEnd}
127+
onAnnotationCreateStart={handleAnnotationCreateStart}
101128
onError={onError}
102129
target={target}
103130
/>
104131
) : (
105132
<AnnotationThreadContent
106-
api={api}
107-
annotationId={annotationId}
133+
annotation={annotation}
108134
currentUser={currentUser}
109-
eventEmitter={eventEmitter}
110-
file={file}
135+
error={error}
111136
getAvatarUrl={getAvatarUrl}
112137
getMentionWithQuery={getMentionWithQuery}
113138
getUserProfileUrl={getUserProfileUrl}
139+
isLoading={isLoading}
114140
mentionSelectorContacts={mentionSelectorContacts}
115-
onError={onError}
141+
onAnnotationDelete={handleAnnotationDelete}
142+
onAnnotationEdit={handleAnnotationEdit}
143+
onAnnotationStatusChange={handleAnnotationStatusChange}
144+
onReplyCreate={handleReplyCreate}
145+
onReplyDelete={handleReplyDelete}
146+
onReplyEdit={handleReplyEdit}
147+
replies={replies}
116148
/>
117149
)}
118150
</IntlProvider>

src/elements/content-sidebar/activity-feed/annotation-thread/AnnotationThreadContent.js

Lines changed: 51 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,69 @@
11
// @flow
22
import React from 'react';
3-
import type EventEmitter from 'events';
4-
import useAnnotationThread from './useAnnotationThread';
3+
import type { MessageDescriptor } from 'react-intl';
54
import ActivityError from '../common/activity-error';
65
import ActivityThread from '../activity-feed/ActivityThread';
76
import AnnotationActivity from '../annotations';
8-
import API from '../../../../api/APIFactory';
97
import LoadingIndicator from '../../../../components/loading-indicator/LoadingIndicator';
108

11-
import type { BoxItem, SelectorItems, User } from '../../../../common/types/core';
12-
import type { ErrorContextProps } from '../../../../common/types/api';
9+
import type {
10+
Annotation,
11+
AnnotationPermission,
12+
BoxCommentPermission,
13+
Comment,
14+
FeedItemStatus,
15+
} from '../../../../common/types/feed';
16+
import type { SelectorItems, User } from '../../../../common/types/core';
1317
import type { GetProfileUrlCallback } from '../../../common/flowTypes';
1418

1519
import './AnnotationThreadContent.scss';
1620

1721
type Props = {
18-
annotationId: string,
19-
api: API,
22+
annotation?: Annotation,
2023
currentUser: User,
21-
eventEmitter: EventEmitter,
22-
file: BoxItem,
24+
error?: {
25+
message: MessageDescriptor,
26+
title: MessageDescriptor,
27+
},
2328
getAvatarUrl: string => Promise<?string>,
2429
getMentionWithQuery: (searchStr: string) => void,
2530
getUserProfileUrl?: GetProfileUrlCallback,
31+
isLoading: boolean,
2632
mentionSelectorContacts: SelectorItems<>,
27-
} & ErrorContextProps;
33+
onAnnotationDelete?: ({ id: string, permissions: AnnotationPermission }) => any,
34+
onAnnotationEdit?: (id: string, text: string, permissions: AnnotationPermission) => void,
35+
onAnnotationStatusChange?: (id: string, status: FeedItemStatus, permissions: AnnotationPermission) => void,
36+
onReplyCreate?: (text: string) => void,
37+
onReplyDelete?: ({ id: string, permissions: BoxCommentPermission }) => void,
38+
onReplyEdit?: (
39+
id: string,
40+
text: string,
41+
status?: FeedItemStatus,
42+
hasMention?: boolean,
43+
permissions: BoxCommentPermission,
44+
onSuccess: ?Function,
45+
onError: ?Function,
46+
) => void,
47+
replies?: Array<Comment>,
48+
};
2849

2950
const AnnotationThreadContent = ({
30-
annotationId,
31-
api,
51+
annotation,
3252
currentUser,
33-
eventEmitter,
34-
file,
53+
error,
3554
getAvatarUrl,
3655
getMentionWithQuery,
3756
getUserProfileUrl,
57+
isLoading,
3858
mentionSelectorContacts,
39-
onError,
59+
onAnnotationDelete,
60+
onAnnotationEdit,
61+
onAnnotationStatusChange,
62+
onReplyCreate,
63+
onReplyDelete,
64+
onReplyEdit,
65+
replies = [],
4066
}: Props) => {
41-
const { id: fileId, permissions = {} } = file;
42-
43-
const {
44-
annotation,
45-
replies,
46-
isLoading,
47-
error,
48-
annotationActions: { handleAnnotationStatusChange, handleAnnotationDelete, handleAnnotationEdit },
49-
repliesActions: { handleReplyEdit, handleReplyCreate, handleReplyDelete },
50-
} = useAnnotationThread({
51-
api,
52-
annotationId,
53-
currentUser,
54-
eventEmitter,
55-
fileId,
56-
filePermissions: permissions,
57-
errorCallback: onError,
58-
});
59-
6067
return (
6168
<>
6269
{error && <ActivityError {...error} />}
@@ -67,30 +74,30 @@ const AnnotationThreadContent = ({
6774
)}
6875
{annotation && (
6976
<ActivityThread
70-
hasReplies
7177
getAvatarUrl={getAvatarUrl}
7278
getMentionWithQuery={getMentionWithQuery}
7379
getUserProfileUrl={getUserProfileUrl}
80+
hasReplies
7481
isAlwaysExpanded
7582
isRepliesLoading={isLoading}
7683
mentionSelectorContacts={mentionSelectorContacts}
77-
onReplyCreate={handleReplyCreate}
78-
onReplyDelete={handleReplyDelete}
79-
onReplyEdit={handleReplyEdit}
84+
onReplyCreate={onReplyCreate}
85+
onReplyDelete={onReplyDelete}
86+
onReplyEdit={onReplyEdit}
8087
replies={replies}
8188
repliesTotalCount={replies.length}
8289
>
8390
<AnnotationActivity
84-
getAvatarUrl={getAvatarUrl}
8591
currentUser={currentUser}
86-
isCurrentVersion
87-
item={annotation}
92+
getAvatarUrl={getAvatarUrl}
8893
getMentionWithQuery={getMentionWithQuery}
8994
getUserProfileUrl={getUserProfileUrl}
95+
isCurrentVersion
96+
item={annotation}
9097
mentionSelectorContacts={mentionSelectorContacts}
91-
onEdit={handleAnnotationEdit}
92-
onDelete={handleAnnotationDelete}
93-
onStatusChange={handleAnnotationStatusChange}
98+
onDelete={onAnnotationDelete}
99+
onEdit={onAnnotationEdit}
100+
onStatusChange={onAnnotationStatusChange}
94101
/>
95102
</ActivityThread>
96103
)}

src/elements/content-sidebar/activity-feed/annotation-thread/AnnotationThreadCreate.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// @flow
22
import React from 'react';
33
import classNames from 'classnames';
4+
import uniqueId from 'lodash/uniqueId';
45
import API from '../../../../api/APIFactory';
56
import CommentForm from '../comment-form';
67
import { getBadUserError } from '../../../../utils/error';
@@ -19,19 +20,21 @@ type Props = {
1920
getMentionWithQuery: (searchStr: string) => void,
2021
handleCancel: () => void,
2122
mentionSelectorContacts: SelectorItems<>,
22-
onAnnotationCreate: (annotation: Annotation) => void,
23+
onAnnotationCreateEnd: (annotation: Object, requestId: string) => void,
24+
onAnnotationCreateStart: (annotation: Object, requestId: string) => void,
2325
target: Target,
2426
} & ErrorContextProps;
2527

2628
const AnnotationThreadCreate = ({
2729
api,
2830
currentUser,
31+
file,
2932
getAvatarUrl,
3033
getMentionWithQuery,
31-
file,
3234
handleCancel,
3335
mentionSelectorContacts,
34-
onAnnotationCreate,
36+
onAnnotationCreateEnd,
37+
onAnnotationCreateStart,
3538
onError,
3639
target,
3740
}: Props) => {
@@ -44,15 +47,19 @@ const AnnotationThreadCreate = ({
4447
throw getBadUserError();
4548
}
4649

50+
const requestId = uniqueId('annotation_');
51+
4752
const successCallback = (annotation: Annotation) => {
48-
onAnnotationCreate(annotation);
53+
onAnnotationCreateEnd(annotation, requestId);
4954
};
5055

5156
const payload = {
5257
description: { message: text },
5358
target,
5459
};
5560

61+
onAnnotationCreateStart(payload, requestId);
62+
5663
api.getAnnotationsAPI(false).createAnnotation(
5764
id,
5865
file_version.id,

src/elements/content-sidebar/activity-feed/annotation-thread/__tests__/AnnotationThread.test.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,48 @@ import { render } from '@testing-library/react';
33
import React from 'react';
44
import { IntlProvider } from 'react-intl';
55
import AnnotationThread from '../AnnotationThread';
6+
import { annotation as mockAnnotation } from '../../../../../__mocks__/annotations';
67

78
jest.mock('react-intl', () => jest.requireActual('react-intl'));
89
jest.mock('../AnnotationThreadContent', () => () => <div data-testid="annotation-content" />);
910
jest.mock('../AnnotationThreadCreate', () => () => <div data-testid="annotation-create" />);
1011

12+
jest.mock('../useAnnotationThread', () => {
13+
return jest.fn(() => ({
14+
annotation: mockAnnotation,
15+
annotationActions: {
16+
handleDelete: jest.fn(),
17+
handleEdit: jest.fn(),
18+
handleResolve: jest.fn(),
19+
},
20+
annotationEvents: {
21+
handleAnnotationCreateStart: jest.fn(),
22+
handleAnnotationCreateEnd: jest.fn(),
23+
},
24+
error: {},
25+
isLoading: false,
26+
replies: [],
27+
repliesActions: {
28+
handleCreateReply: jest.fn(),
29+
handleEditReply: jest.fn(),
30+
handleDeleteReply: jest.fn(),
31+
},
32+
}));
33+
});
34+
1135
describe('elements/content-sidebar/activity-feed/annotation-thread/AnnotationThread', () => {
36+
const file = {
37+
id: 'fileId',
38+
permissions: {
39+
can_view_annotations: true,
40+
can_annotate: true,
41+
},
42+
};
43+
1244
const IntlWrapper = ({ children }: { children?: React.ReactNode }) => {
1345
return <IntlProvider locale="en">{children}</IntlProvider>;
1446
};
15-
const getWrapper = props => render(<AnnotationThread {...props} />, { wrapper: IntlWrapper });
47+
const getWrapper = props => render(<AnnotationThread file={file} {...props} />, { wrapper: IntlWrapper });
1648

1749
test('should render AnnotationThreadCreate if annotationId is undefined', async () => {
1850
const { queryByTestId } = getWrapper();

0 commit comments

Comments
 (0)