Skip to content

Commit 4f65525

Browse files
feat(threaded-replies): reply to annotations (#3331)
* feat(threaded-replies): reply to annotations * feat(threaded-replies): reply to annotations * feat(threaded-replies): reply to annotations * feat(threaded-replies): reply to annotations * feat(threaded-replies): reply to annotations * feat(threaded-replies): reply to annotations * feat(threaded-replies): reply to annotations * feat(threaded-replies): reply to annotations * feat(threaded-replies): reply to annotations * feat(threaded-replies): reply to annotations --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 0e934b0 commit 4f65525

23 files changed

Lines changed: 629 additions & 242 deletions

‎i18n/en-US.properties‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ be.contentSidebar.activityFeed.annotationActivityPageItem = Page {number}
164164
be.contentSidebar.activityFeed.annotationActivityVersionLink = Version {number}
165165
# Annotation activity item link shown on annotation activity for previous file version that is unavailable
166166
be.contentSidebar.activityFeed.annotationActivityVersionUnavailable = Version Unavailable
167+
# Title icon indicating that the activity item is an annotation
168+
be.contentSidebar.activityFeed.comment.annotationBadge = Annotation Badge
167169
# Text to show on menu item to delete comment
168170
be.contentSidebar.activityFeed.comment.commentDeleteMenuItem = Delete
169171
# Confirmation prompt text to delete comment

‎src/api/Annotations.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ export default class Annotations extends MarkerBasedApi {
309309
data: { data: { message } },
310310
errorCallback,
311311
successCallback,
312-
url: this.getUrlWithRepliesForId(annotationId),
312+
url: `${this.getUrlWithRepliesForId(annotationId)}?file_id=${fileId}`,
313313
});
314314
}
315315
}

‎src/api/__tests__/Annotations.test.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ describe('api/Annotations', () => {
350350
data: { data: { message } },
351351
errorCallback,
352352
successCallback,
353-
url: 'https://api.box.com/2.0/undoc/annotations/67890/replies',
353+
url: 'https://api.box.com/2.0/undoc/annotations/67890/replies?file_id=12345',
354354
});
355355
});
356356
test.each([

‎src/elements/content-sidebar/ActivitySidebar.js‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ import type { Translations } from './flowTypes';
7373
import type { FeatureConfig } from '../common/feature-checking';
7474
import './ActivitySidebar.scss';
7575

76+
import { type OnAnnotationEdit } from './activity-feed/comment/types';
77+
7678
type ExternalProps = {
7779
activeFeedEntryId?: string,
7880
activeFeedEntryType?: FocusableFeedItemType,
@@ -194,7 +196,7 @@ class ActivitySidebar extends React.PureComponent<Props, State> {
194196
this.fetchFeedItems();
195197
};
196198

197-
handleAnnotationEdit = (id: string, text: string, permissions: AnnotationPermission) => {
199+
handleAnnotationEdit: OnAnnotationEdit = ({ id, text, permissions }) => {
198200
const { api, emitAnnotationUpdateEvent, file } = this.props;
199201

200202
emitAnnotationUpdateEvent(

‎src/elements/content-sidebar/__tests__/ActivitySidebar.test.js‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,10 +1407,14 @@ describe('elements/content-sidebar/ActivitySidebar', () => {
14071407
const instance = wrapper.instance();
14081408
instance.fetchFeedItems = jest.fn();
14091409

1410-
wrapper.instance().handleAnnotationEdit('123', 'hello', {
1411-
can_edit: true,
1412-
can_delete: true,
1413-
can_resolve: true,
1410+
wrapper.instance().handleAnnotationEdit({
1411+
id: '123',
1412+
permissions: {
1413+
can_edit: true,
1414+
can_delete: true,
1415+
can_resolve: true,
1416+
},
1417+
text: 'hello',
14141418
});
14151419

14161420
expect(mockEmitAnnotationUpdateEvent).toBeCalledWith(

‎src/elements/content-sidebar/activity-feed/activity-feed/ActiveState.js‎

Lines changed: 89 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ import type { SelectorItems, User } from '../../../../common/types/core';
3535
import type { GetAvatarUrlCallback, GetProfileUrlCallback } from '../../../common/flowTypes';
3636
import type { Translations } from '../../flowTypes';
3737

38+
import { type OnAnnotationEdit, type OnCommentEdit } from '../comment/types';
39+
import AnnotationActivityLinkProvider from './AnnotationActivityLinkProvider';
40+
3841
type Props = {
3942
activeFeedItem: FeedItem,
4043
activeFeedItemRef: { current: null | HTMLElement },
@@ -51,20 +54,12 @@ type Props = {
5154
items: FeedItems,
5255
mentionSelectorContacts?: SelectorItems<>,
5356
onAnnotationDelete?: ({ id: string, permissions: AnnotationPermission }) => void,
54-
onAnnotationEdit?: (id: string, text: string, permissions: AnnotationPermission) => void,
57+
onAnnotationEdit?: OnAnnotationEdit,
5558
onAnnotationSelect?: (annotation: Annotation) => void,
5659
onAnnotationStatusChange?: (id: string, status: FeedItemStatus, permissions: AnnotationPermission) => void,
5760
onAppActivityDelete?: Function,
5861
onCommentDelete?: Function,
59-
onCommentEdit?: (
60-
id: string,
61-
text?: string,
62-
status?: FeedItemStatus,
63-
hasMention: boolean,
64-
permissions: BoxCommentPermission,
65-
onSuccess: ?Function,
66-
onError: ?Function,
67-
) => void,
62+
onCommentEdit?: OnCommentEdit,
6863
onCommentSelect?: (id: string | null) => void,
6964
onHideReplies?: (id: string, replies: Array<CommentType>) => void,
7065
onReplyCreate?: (parentId: string, parentType: CommentFeedItemType, text: string) => void,
@@ -163,6 +158,28 @@ const ActiveState = ({
163158
onReplySelect: onCommentSelectHandler(item.id),
164159
};
165160

161+
const commentAndAnnotationCommonProps = {
162+
...item,
163+
...replyProps,
164+
currentUser,
165+
getAvatarUrl,
166+
getMentionWithQuery,
167+
getUserProfileUrl,
168+
mentionSelectorContacts,
169+
onHideReplies: shownReplies => onHideReplies(item.id, shownReplies),
170+
onSelect: onCommentSelectHandler(item.id),
171+
permissions: {
172+
can_delete: getProp(item.permissions, 'can_delete', false),
173+
can_edit: getProp(item.permissions, 'can_edit', false),
174+
can_reply: getProp(item.permissions, 'can_reply', false),
175+
can_resolve: getProp(item.permissions, 'can_resolve', false),
176+
},
177+
// TODO: legitimate, pre-existing typing issue that was previously undetected
178+
// $FlowFixMe
179+
repliesTotalCount: item.total_reply_count,
180+
translations,
181+
};
182+
166183
switch (item.type) {
167184
case FEED_ITEM_TYPE_COMMENT:
168185
return (
@@ -175,28 +192,15 @@ const ActiveState = ({
175192
ref={refValue}
176193
>
177194
{hasNewThreadedReplies ? (
195+
// TODO: legitimate, pre-existing typing issue that was previously undetected
196+
// Conflict between BoxCommentPermissions and BoxTaskPermissions
197+
// $FlowFixMe
178198
<BaseComment
179-
{...item}
180-
{...replyProps}
181-
currentUser={currentUser}
182-
getAvatarUrl={getAvatarUrl}
183-
getMentionWithQuery={getMentionWithQuery}
184-
getUserProfileUrl={getUserProfileUrl}
185-
mentionSelectorContacts={mentionSelectorContacts}
199+
{...commentAndAnnotationCommonProps}
186200
onDelete={onCommentDelete}
187-
onEdit={onCommentEdit}
201+
onCommentEdit={onCommentEdit}
188202
onReplyCreate={reply => onReplyCreate(item.id, FEED_ITEM_TYPE_COMMENT, reply)}
189-
onSelect={onCommentSelectHandler(item.id)}
190203
onShowReplies={() => onShowReplies(item.id, FEED_ITEM_TYPE_COMMENT)}
191-
onHideReplies={shownReplies => onHideReplies(item.id, shownReplies)}
192-
permissions={{
193-
can_delete: getProp(item.permissions, 'can_delete', false),
194-
can_edit: getProp(item.permissions, 'can_edit', false),
195-
can_reply: getProp(item.permissions, 'can_reply', false),
196-
can_resolve: getProp(item.permissions, 'can_resolve', false),
197-
}}
198-
repliesTotalCount={item.total_reply_count}
199-
translations={translations}
200204
/>
201205
) : (
202206
<ActivityThread
@@ -296,45 +300,70 @@ const ActiveState = ({
296300
key={item.type + item.id}
297301
className="bcs-activity-feed-annotation-activity"
298302
data-testid="annotation-activity"
303+
isHoverable
304+
hasNewThreadedReplies={hasNewThreadedReplies}
299305
isFocused={isFocused}
300306
ref={refValue}
301307
>
302-
<ActivityThread
303-
data-testid="activity-thread"
304-
currentUser={currentUser}
305-
getAvatarUrl={getAvatarUrl}
306-
getMentionWithQuery={getMentionWithQuery}
307-
getUserProfileUrl={getUserProfileUrl}
308-
hasNewThreadedReplies={hasNewThreadedReplies}
309-
hasReplies={hasReplies}
310-
isPending={item.isPending}
311-
isRepliesLoading={item.isRepliesLoading}
312-
mentionSelectorContacts={mentionSelectorContacts}
313-
onHideReplies={onHideRepliesHandler(item.id)}
314-
onReplyCreate={onReplyCreateHandler(item.id, item.type)}
315-
onReplyDelete={onReplyDeleteHandler(item.id)}
316-
onReplyEdit={onReplyUpdateHandler(item.id)}
317-
onReplySelect={onCommentSelectHandler(item.id)}
318-
onShowReplies={onShowRepliesHandler(item.id, item.type)}
319-
repliesTotalCount={item.total_reply_count}
320-
replies={item.replies}
321-
translations={translations}
322-
>
323-
<AnnotationActivity
308+
{hasNewThreadedReplies && onAnnotationSelect ? (
309+
// TODO: legitimate, pre-existing typing issue that was previously undetected
310+
// Conflict between BoxCommentPermissions and BoxTaskPermissions
311+
// $FlowFixMe
312+
<BaseComment
313+
{...commentAndAnnotationCommonProps}
314+
annotationActivityLink={
315+
<AnnotationActivityLinkProvider
316+
item={item}
317+
onSelect={onAnnotationSelect}
318+
isCurrentVersion={currentFileVersionId === itemFileVersionId}
319+
/>
320+
}
321+
onAnnotationEdit={onAnnotationEdit}
322+
onDelete={onAnnotationDelete}
323+
onReplyCreate={reply =>
324+
onReplyCreate(item.id, FEED_ITEM_TYPE_ANNOTATION, reply)
325+
}
326+
onShowReplies={() => onShowReplies(item.id, FEED_ITEM_TYPE_ANNOTATION)}
327+
tagged_message={item.description?.message ?? ''}
328+
/>
329+
) : (
330+
<ActivityThread
331+
data-testid="activity-thread"
324332
currentUser={currentUser}
325333
getAvatarUrl={getAvatarUrl}
326-
getUserProfileUrl={getUserProfileUrl}
327334
getMentionWithQuery={getMentionWithQuery}
328-
hasVersions={hasVersions}
329-
isCurrentVersion={currentFileVersionId === itemFileVersionId}
330-
item={item}
335+
getUserProfileUrl={getUserProfileUrl}
336+
hasNewThreadedReplies={hasNewThreadedReplies}
337+
hasReplies={hasReplies}
338+
isPending={item.isPending}
339+
isRepliesLoading={item.isRepliesLoading}
331340
mentionSelectorContacts={mentionSelectorContacts}
332-
onEdit={onAnnotationEdit}
333-
onDelete={onAnnotationDelete}
334-
onSelect={onAnnotationSelect}
335-
onStatusChange={onAnnotationStatusChange}
336-
/>
337-
</ActivityThread>
341+
onHideReplies={onHideRepliesHandler(item.id)}
342+
onReplyCreate={onReplyCreateHandler(item.id, item.type)}
343+
onReplyDelete={onReplyDeleteHandler(item.id)}
344+
onReplyEdit={onReplyUpdateHandler(item.id)}
345+
onReplySelect={onCommentSelectHandler(item.id)}
346+
onShowReplies={onShowRepliesHandler(item.id, item.type)}
347+
repliesTotalCount={item.total_reply_count}
348+
replies={item.replies}
349+
translations={translations}
350+
>
351+
<AnnotationActivity
352+
currentUser={currentUser}
353+
getAvatarUrl={getAvatarUrl}
354+
getUserProfileUrl={getUserProfileUrl}
355+
getMentionWithQuery={getMentionWithQuery}
356+
hasVersions={hasVersions}
357+
isCurrentVersion={currentFileVersionId === itemFileVersionId}
358+
item={item}
359+
mentionSelectorContacts={mentionSelectorContacts}
360+
onEdit={onAnnotationEdit}
361+
onDelete={onAnnotationDelete}
362+
onSelect={onAnnotationSelect}
363+
onStatusChange={onAnnotationStatusChange}
364+
/>
365+
</ActivityThread>
366+
)}
338367
</ActivityItem>
339368
);
340369
default:

‎src/elements/content-sidebar/activity-feed/activity-feed/ActivityFeed.js‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ import type {
3737
import type { SelectorItems, User, GroupMini, BoxItem } from '../../../../common/types/core';
3838
import type { Errors, GetAvatarUrlCallback, GetProfileUrlCallback } from '../../../common/flowTypes';
3939
import type { Translations } from '../../flowTypes';
40+
import { type OnAnnotationEdit } from '../comment/types';
41+
4042
import './ActivityFeed.scss';
4143

4244
type Props = {
@@ -58,7 +60,7 @@ type Props = {
5860
isDisabled?: boolean,
5961
mentionSelectorContacts?: SelectorItems<User>,
6062
onAnnotationDelete?: ({ id: string, permissions: AnnotationPermission }) => void,
61-
onAnnotationEdit?: (id: string, text: string, permissions: AnnotationPermission) => void,
63+
onAnnotationEdit?: OnAnnotationEdit,
6264
onAnnotationSelect?: (annotation: Annotation) => void,
6365
onAnnotationStatusChange?: (id: string, status: FeedItemStatus, permissions: AnnotationPermission) => void,
6466
onAppActivityDelete?: Function,
@@ -399,7 +401,21 @@ class ActivityFeed extends React.Component<Props, State> {
399401
onAnnotationStatusChange={onAnnotationStatusChange}
400402
onAppActivityDelete={onAppActivityDelete}
401403
onCommentDelete={hasCommentPermission ? onCommentDelete : noop}
402-
onCommentEdit={hasCommentPermission ? onCommentUpdate : noop}
404+
onCommentEdit={
405+
hasCommentPermission && onCommentUpdate
406+
? props => {
407+
onCommentUpdate(
408+
props.id,
409+
props.text,
410+
props.status,
411+
props.hasMention,
412+
props.permissions,
413+
props.onSuccess,
414+
props.onError,
415+
);
416+
}
417+
: noop
418+
}
403419
onCommentSelect={this.setSelectedItem}
404420
onHideReplies={onHideReplies}
405421
onReplyCreate={hasCommentPermission ? onReplyCreate : noop}

‎src/elements/content-sidebar/activity-feed/activity-feed/ActivityThreadReplies.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const ActivityThreadReplies = ({
6161
getUserProfileUrl={getUserProfileUrl}
6262
mentionSelectorContacts={mentionSelectorContacts}
6363
onDelete={onDelete}
64-
onEdit={onEdit}
64+
onCommentEdit={onEdit}
6565
onSelect={onSelect}
6666
permissions={getReplyPermissions(reply)}
6767
translations={translations}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// @flow
2+
import * as React from 'react';
3+
import AnnotationActivityLink from '../annotations/AnnotationActivityLink';
4+
5+
import annotationsMessages from '../annotations/messages';
6+
7+
import type { Annotation } from '../../../../common/types/feed';
8+
9+
type AnnotationActivityLinkProviderProps = {
10+
isCurrentVersion: boolean,
11+
item: Annotation,
12+
onSelect: (annotation: Annotation) => void,
13+
};
14+
15+
const AnnotationActivityLinkProvider = ({ isCurrentVersion, item, onSelect }: AnnotationActivityLinkProviderProps) => {
16+
const { file_version, id, target } = item;
17+
18+
const isFileVersionUnavailable = file_version === null;
19+
20+
const linkMessage = isCurrentVersion
21+
? annotationsMessages.annotationActivityPageItem
22+
: annotationsMessages.annotationActivityVersionLink;
23+
const linkValue = isCurrentVersion ? target?.location.value : file_version?.version_number;
24+
25+
const activityLinkMessage = isFileVersionUnavailable
26+
? annotationsMessages.annotationActivityVersionUnavailable
27+
: { ...linkMessage, values: { number: linkValue } };
28+
29+
const handleSelect = () => onSelect(item);
30+
31+
return (
32+
<AnnotationActivityLink
33+
className="bcs-AnnotationActivity-link"
34+
data-resin-target="annotationLink"
35+
id={id}
36+
isDisabled={isFileVersionUnavailable}
37+
message={activityLinkMessage}
38+
onClick={handleSelect}
39+
/>
40+
);
41+
};
42+
43+
export default AnnotationActivityLinkProvider;

0 commit comments

Comments
 (0)