box-ui-elements 17.2.0-beta.2 → 17.2.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/explorer.js +16 -16
- package/dist/openwith.js +6 -6
- package/dist/picker.js +10 -10
- package/dist/preview.js +12 -12
- package/dist/sharing.js +8 -8
- package/dist/sidebar.js +9 -9
- package/dist/uploader.js +6 -6
- package/es/api/APIFactory.js +27 -0
- package/es/api/APIFactory.js.flow +26 -0
- package/es/api/APIFactory.js.map +1 -1
- package/es/api/Feed.js +225 -52
- package/es/api/Feed.js.flow +191 -26
- package/es/api/Feed.js.map +1 -1
- package/es/api/FileActivities.js +119 -0
- package/es/api/FileActivities.js.flow +88 -0
- package/es/api/FileActivities.js.map +1 -0
- package/es/api/Item.js +21 -18
- package/es/api/Item.js.flow +22 -15
- package/es/api/Item.js.map +1 -1
- package/es/api/fixtures.js +46 -1
- package/es/api/fixtures.js.flow +39 -1
- package/es/api/fixtures.js.map +1 -1
- package/es/common/types/feed.js +1 -1
- package/es/common/types/feed.js.flow +38 -0
- package/es/common/types/feed.js.map +1 -1
- package/es/components/pill-selector-dropdown/PillSelectorDropdown.stories.js +1 -1
- package/es/components/pill-selector-dropdown/PillSelectorDropdown.stories.js.flow +1 -1
- package/es/components/pill-selector-dropdown/PillSelectorDropdown.stories.js.map +1 -1
- package/es/constants.js +7 -0
- package/es/constants.js.flow +7 -0
- package/es/constants.js.map +1 -1
- package/es/elements/content-explorer/ContentExplorer.js +5 -18
- package/es/elements/content-explorer/ContentExplorer.js.flow +6 -16
- package/es/elements/content-explorer/ContentExplorer.js.map +1 -1
- package/es/elements/content-picker/ContentPicker.js +5 -8
- package/es/elements/content-picker/ContentPicker.js.flow +6 -9
- package/es/elements/content-picker/ContentPicker.js.map +1 -1
- package/es/elements/content-sharing/hooks/useSharedLink.js +1 -0
- package/es/elements/content-sharing/hooks/useSharedLink.js.flow +1 -0
- package/es/elements/content-sharing/hooks/useSharedLink.js.map +1 -1
- package/es/elements/content-sharing/types.js.flow +2 -1
- package/es/elements/content-sidebar/ActivitySidebar.js +6 -2
- package/es/elements/content-sidebar/ActivitySidebar.js.flow +11 -1
- package/es/elements/content-sidebar/ActivitySidebar.js.map +1 -1
- package/es/elements/content-sidebar/activity-feed/activity-feed/ActiveState.js +2 -0
- package/es/elements/content-sidebar/activity-feed/activity-feed/ActiveState.js.flow +3 -0
- package/es/elements/content-sidebar/activity-feed/activity-feed/ActiveState.js.map +1 -1
- package/es/elements/content-sidebar/activity-feed/activity-feed/ActivityFeed.js +2 -0
- package/es/elements/content-sidebar/activity-feed/activity-feed/ActivityFeed.js.flow +3 -0
- package/es/elements/content-sidebar/activity-feed/activity-feed/ActivityFeed.js.map +1 -1
- package/es/features/unified-share-modal/UnifiedShareForm.js +8 -7
- package/es/features/unified-share-modal/UnifiedShareForm.js.flow +10 -8
- package/es/features/unified-share-modal/UnifiedShareForm.js.map +1 -1
- package/es/features/unified-share-modal/messages.js +1 -1
- package/es/features/unified-share-modal/messages.js.flow +2 -1
- package/es/features/unified-share-modal/messages.js.map +1 -1
- package/es/utils/fields.js +1 -1
- package/es/utils/fields.js.flow +1 -1
- package/es/utils/fields.js.map +1 -1
- package/i18n/en-US.js +1 -1
- package/i18n/en-US.properties +1 -1
- package/i18n/en-x-pseudo.js +1 -1
- package/package.json +1 -1
- package/src/api/APIFactory.js +26 -0
- package/src/api/Feed.js +191 -26
- package/src/api/FileActivities.js +88 -0
- package/src/api/Item.js +22 -15
- package/src/api/__tests__/Feed.test.js +94 -2
- package/src/api/__tests__/FileActivities.test.js +95 -0
- package/src/api/__tests__/Item.test.js +14 -12
- package/src/api/fixtures.js +39 -1
- package/src/common/types/feed.js +38 -0
- package/src/components/pill-selector-dropdown/PillSelectorDropdown.stories.js +1 -1
- package/src/constants.js +7 -0
- package/src/elements/content-explorer/ContentExplorer.js +6 -16
- package/src/elements/content-explorer/__tests__/ContentExplorer.test.js +2 -2
- package/src/elements/content-picker/ContentPicker.js +6 -9
- package/src/elements/content-sharing/hooks/useSharedLink.js +1 -0
- package/src/elements/content-sharing/types.js +2 -1
- package/src/elements/content-sidebar/ActivitySidebar.js +11 -1
- package/src/elements/content-sidebar/__tests__/ActivitySidebar.test.js +9 -4
- package/src/elements/content-sidebar/__tests__/__snapshots__/ActivitySidebar.test.js.snap +1 -0
- package/src/elements/content-sidebar/activity-feed/activity-feed/ActiveState.js +3 -0
- package/src/elements/content-sidebar/activity-feed/activity-feed/ActivityFeed.js +3 -0
- package/src/features/unified-share-modal/UnifiedShareForm.js +10 -8
- package/src/features/unified-share-modal/messages.js +2 -1
- package/src/utils/fields.js +1 -1
package/src/api/Feed.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* @file Helper for activity feed API's
|
|
4
4
|
* @author Box
|
|
5
5
|
*/
|
|
6
|
-
import uniqueId from 'lodash/uniqueId';
|
|
7
6
|
import noop from 'lodash/noop';
|
|
7
|
+
import uniqueId from 'lodash/uniqueId';
|
|
8
8
|
import type { MessageDescriptor } from 'react-intl';
|
|
9
9
|
import { getBadItemError, getBadUserError, getMissingItemTextOrStatus, isUserCorrectableError } from '../utils/error';
|
|
10
10
|
import commonMessages from '../elements/common/messages';
|
|
@@ -15,6 +15,7 @@ import Base from './Base';
|
|
|
15
15
|
import AnnotationsAPI from './Annotations';
|
|
16
16
|
import CommentsAPI from './Comments';
|
|
17
17
|
import ThreadedCommentsAPI from './ThreadedComments';
|
|
18
|
+
import FileActivitiesAPI from './FileActivities';
|
|
18
19
|
import VersionsAPI from './Versions';
|
|
19
20
|
import TasksNewAPI from './tasks/TasksNew';
|
|
20
21
|
import GroupsAPI from './Groups';
|
|
@@ -28,6 +29,10 @@ import {
|
|
|
28
29
|
FEED_ITEM_TYPE_ANNOTATION,
|
|
29
30
|
FEED_ITEM_TYPE_COMMENT,
|
|
30
31
|
FEED_ITEM_TYPE_TASK,
|
|
32
|
+
FILE_ACTIVITY_TYPE_ANNOTATION,
|
|
33
|
+
FILE_ACTIVITY_TYPE_APP_ACTIVITY,
|
|
34
|
+
FILE_ACTIVITY_TYPE_COMMENT,
|
|
35
|
+
FILE_ACTIVITY_TYPE_TASK,
|
|
31
36
|
HTTP_STATUS_CODE_CONFLICT,
|
|
32
37
|
IS_ERROR_DISPLAYED,
|
|
33
38
|
TASK_NEW_APPROVED,
|
|
@@ -71,6 +76,8 @@ import type {
|
|
|
71
76
|
FeedItem,
|
|
72
77
|
FeedItems,
|
|
73
78
|
FeedItemStatus,
|
|
79
|
+
FileActivity,
|
|
80
|
+
FileActivityTypes,
|
|
74
81
|
Task,
|
|
75
82
|
Tasks,
|
|
76
83
|
ThreadedComments as ThreadedCommentsType,
|
|
@@ -95,6 +102,97 @@ const getItemWithPendingReply = <T: { replies?: Array<Comment> }>(item: T, reply
|
|
|
95
102
|
return { replies: [...replies, reply], ...rest };
|
|
96
103
|
};
|
|
97
104
|
|
|
105
|
+
const parseReplies = (replies: Comment[]): Comment[] => {
|
|
106
|
+
const parsedReplies = [...replies];
|
|
107
|
+
|
|
108
|
+
return parsedReplies.map(reply => {
|
|
109
|
+
return { ...reply, tagged_message: reply.tagged_message || reply.message || '' };
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const getParsedFileActivitiesResponse = (response?: { entries: FileActivity[] }) => {
|
|
114
|
+
if (!response || !response.entries || !response.entries.length) {
|
|
115
|
+
return { entries: [] };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const data = response.entries;
|
|
119
|
+
|
|
120
|
+
const parsedData: Array<Object> = data
|
|
121
|
+
.map(item => {
|
|
122
|
+
if (!item.source) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const source = { ...item.source };
|
|
127
|
+
|
|
128
|
+
switch (item.activity_type) {
|
|
129
|
+
case FILE_ACTIVITY_TYPE_TASK: {
|
|
130
|
+
const taskItem = { ...source[FILE_ACTIVITY_TYPE_TASK] };
|
|
131
|
+
// UAA follows a lowercased enum naming convention, convert to uppercase to align with task api
|
|
132
|
+
if (taskItem.assigned_to?.entries) {
|
|
133
|
+
const assignedToEntries = taskItem.assigned_to.entries.map(entry => {
|
|
134
|
+
const assignedToEntry = { ...entry };
|
|
135
|
+
|
|
136
|
+
assignedToEntry.role = entry.role.toUpperCase();
|
|
137
|
+
assignedToEntry.status = entry.status.toUpperCase();
|
|
138
|
+
|
|
139
|
+
return assignedToEntry;
|
|
140
|
+
});
|
|
141
|
+
// $FlowFixMe Using the toUpperCase method makes Flow assume role and status is a string type, which is incompatible with string literal
|
|
142
|
+
taskItem.assigned_to.entries = assignedToEntries;
|
|
143
|
+
}
|
|
144
|
+
if (taskItem.completion_rule) {
|
|
145
|
+
taskItem.completion_rule = taskItem.completion_rule.toUpperCase();
|
|
146
|
+
}
|
|
147
|
+
if (taskItem.status) {
|
|
148
|
+
taskItem.status = taskItem.status.toUpperCase();
|
|
149
|
+
}
|
|
150
|
+
if (taskItem.task_type) {
|
|
151
|
+
taskItem.task_type = taskItem.task_type.toUpperCase();
|
|
152
|
+
}
|
|
153
|
+
// $FlowFixMe File Activities only returns a created_by user, Flow type fix is needed
|
|
154
|
+
taskItem.created_by = { target: taskItem.created_by };
|
|
155
|
+
|
|
156
|
+
return taskItem;
|
|
157
|
+
}
|
|
158
|
+
case FILE_ACTIVITY_TYPE_COMMENT: {
|
|
159
|
+
const commentItem = { ...source[FILE_ACTIVITY_TYPE_COMMENT] };
|
|
160
|
+
|
|
161
|
+
if (commentItem.replies && commentItem.replies.length) {
|
|
162
|
+
const replies = parseReplies(commentItem.replies);
|
|
163
|
+
|
|
164
|
+
commentItem.replies = replies;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
commentItem.tagged_message = commentItem.tagged_message || commentItem.message || '';
|
|
168
|
+
|
|
169
|
+
return commentItem;
|
|
170
|
+
}
|
|
171
|
+
case FILE_ACTIVITY_TYPE_ANNOTATION: {
|
|
172
|
+
const annotationItem = { ...source[FILE_ACTIVITY_TYPE_ANNOTATION] };
|
|
173
|
+
|
|
174
|
+
if (annotationItem.replies && annotationItem.replies.length) {
|
|
175
|
+
const replies = parseReplies(annotationItem.replies);
|
|
176
|
+
|
|
177
|
+
annotationItem.replies = replies;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return annotationItem;
|
|
181
|
+
}
|
|
182
|
+
case FILE_ACTIVITY_TYPE_APP_ACTIVITY: {
|
|
183
|
+
return { ...source[FILE_ACTIVITY_TYPE_APP_ACTIVITY] };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
default: {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
.filter(item => !!item);
|
|
192
|
+
|
|
193
|
+
return { entries: parsedData };
|
|
194
|
+
};
|
|
195
|
+
|
|
98
196
|
class Feed extends Base {
|
|
99
197
|
/**
|
|
100
198
|
* @property {AnnotationsAPI}
|
|
@@ -136,6 +234,11 @@ class Feed extends Base {
|
|
|
136
234
|
*/
|
|
137
235
|
threadedCommentsAPI: ThreadedCommentsAPI;
|
|
138
236
|
|
|
237
|
+
/**
|
|
238
|
+
* @property {FileActivitiesAPI}
|
|
239
|
+
*/
|
|
240
|
+
fileActivitiesAPI: FileActivitiesAPI;
|
|
241
|
+
|
|
139
242
|
/**
|
|
140
243
|
* @property {BoxItem}
|
|
141
244
|
*/
|
|
@@ -368,12 +471,14 @@ class Feed extends Base {
|
|
|
368
471
|
shouldShowReplies = false,
|
|
369
472
|
shouldShowTasks = true,
|
|
370
473
|
shouldShowVersions = true,
|
|
474
|
+
shouldUseUAA = false,
|
|
371
475
|
}: {
|
|
372
476
|
shouldShowAnnotations?: boolean,
|
|
373
477
|
shouldShowAppActivity?: boolean,
|
|
374
478
|
shouldShowReplies?: boolean,
|
|
375
479
|
shouldShowTasks?: boolean,
|
|
376
480
|
shouldShowVersions?: boolean,
|
|
481
|
+
shouldUseUAA?: boolean,
|
|
377
482
|
} = {},
|
|
378
483
|
): void {
|
|
379
484
|
const { id, permissions = {} } = file;
|
|
@@ -394,38 +499,78 @@ class Feed extends Base {
|
|
|
394
499
|
this.file = file;
|
|
395
500
|
this.errors = [];
|
|
396
501
|
this.errorCallback = onError;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
502
|
+
|
|
503
|
+
// Using the UAA File Activities endpoint replaces the need for these calls
|
|
504
|
+
const annotationsPromise =
|
|
505
|
+
!shouldUseUAA && shouldShowAnnotations
|
|
506
|
+
? this.fetchAnnotations(permissions, shouldShowReplies)
|
|
507
|
+
: Promise.resolve();
|
|
508
|
+
const commentsPromise = () => {
|
|
509
|
+
if (shouldUseUAA) {
|
|
510
|
+
return Promise.resolve();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return shouldShowReplies ? this.fetchThreadedComments(permissions) : this.fetchComments(permissions);
|
|
514
|
+
};
|
|
515
|
+
const tasksPromise = !shouldUseUAA && shouldShowTasks ? this.fetchTasksNew() : Promise.resolve();
|
|
516
|
+
const appActivityPromise =
|
|
517
|
+
!shouldUseUAA && shouldShowAppActivity ? this.fetchAppActivity(permissions) : Promise.resolve();
|
|
518
|
+
|
|
400
519
|
const versionsPromise = shouldShowVersions ? this.fetchVersions() : Promise.resolve();
|
|
401
520
|
const currentVersionPromise = shouldShowVersions ? this.fetchCurrentVersion() : Promise.resolve();
|
|
402
|
-
const
|
|
403
|
-
? this.
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
tasksPromise,
|
|
413
|
-
appActivityPromise,
|
|
414
|
-
annotationsPromise,
|
|
415
|
-
]).then(([versions: ?FileVersions, currentVersion: ?BoxItemVersion, ...feedItems]) => {
|
|
416
|
-
const versionsWithCurrent = currentVersion
|
|
417
|
-
? this.versionsAPI.addCurrentVersion(currentVersion, versions, this.file)
|
|
418
|
-
: undefined;
|
|
419
|
-
const sortedFeedItems = sortFeedItems(versionsWithCurrent, ...feedItems);
|
|
521
|
+
const fileActivitiesPromise = shouldUseUAA
|
|
522
|
+
? this.fetchFileActivities(permissions, [
|
|
523
|
+
FILE_ACTIVITY_TYPE_ANNOTATION,
|
|
524
|
+
FILE_ACTIVITY_TYPE_APP_ACTIVITY,
|
|
525
|
+
FILE_ACTIVITY_TYPE_COMMENT,
|
|
526
|
+
FILE_ACTIVITY_TYPE_TASK,
|
|
527
|
+
])
|
|
528
|
+
: Promise.resolve();
|
|
529
|
+
|
|
530
|
+
const handleFeedItems = (feedItems: FeedItems) => {
|
|
420
531
|
if (!this.isDestroyed()) {
|
|
421
|
-
this.setCachedItems(id,
|
|
532
|
+
this.setCachedItems(id, feedItems);
|
|
422
533
|
if (this.errors.length) {
|
|
423
|
-
errorCallback(
|
|
534
|
+
errorCallback(feedItems, this.errors);
|
|
424
535
|
} else {
|
|
425
|
-
successCallback(
|
|
536
|
+
successCallback(feedItems);
|
|
426
537
|
}
|
|
427
538
|
}
|
|
428
|
-
}
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
if (shouldUseUAA) {
|
|
542
|
+
Promise.all([versionsPromise, currentVersionPromise, fileActivitiesPromise]).then(
|
|
543
|
+
([versions: ?FileVersions, currentVersion: ?BoxItemVersion, ...feedItems]) => {
|
|
544
|
+
if (!feedItems || !feedItems.length) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const fileActivitiesResponse = feedItems[0];
|
|
549
|
+
const versionsWithCurrent = currentVersion
|
|
550
|
+
? this.versionsAPI.addCurrentVersion(currentVersion, versions, this.file)
|
|
551
|
+
: undefined;
|
|
552
|
+
const parsedFeedItems = getParsedFileActivitiesResponse(fileActivitiesResponse);
|
|
553
|
+
// $FlowFixMe Does not need to be sorted once we include versions in the file activities call
|
|
554
|
+
const sortedFeedItems = sortFeedItems(versionsWithCurrent, parsedFeedItems);
|
|
555
|
+
handleFeedItems(sortedFeedItems);
|
|
556
|
+
},
|
|
557
|
+
);
|
|
558
|
+
} else {
|
|
559
|
+
Promise.all([
|
|
560
|
+
versionsPromise,
|
|
561
|
+
currentVersionPromise,
|
|
562
|
+
commentsPromise(),
|
|
563
|
+
tasksPromise,
|
|
564
|
+
appActivityPromise,
|
|
565
|
+
annotationsPromise,
|
|
566
|
+
]).then(([versions: ?FileVersions, currentVersion: ?BoxItemVersion, ...feedItems]) => {
|
|
567
|
+
const versionsWithCurrent = currentVersion
|
|
568
|
+
? this.versionsAPI.addCurrentVersion(currentVersion, versions, this.file)
|
|
569
|
+
: undefined;
|
|
570
|
+
const sortedFeedItems = sortFeedItems(versionsWithCurrent, ...feedItems);
|
|
571
|
+
handleFeedItems(sortedFeedItems);
|
|
572
|
+
});
|
|
573
|
+
}
|
|
429
574
|
}
|
|
430
575
|
|
|
431
576
|
fetchAnnotations(permissions: BoxItemPermission, shouldFetchReplies?: boolean): Promise<?Annotations> {
|
|
@@ -525,6 +670,26 @@ class Feed extends Base {
|
|
|
525
670
|
});
|
|
526
671
|
}
|
|
527
672
|
|
|
673
|
+
/**
|
|
674
|
+
* Fetches the file activities for a file
|
|
675
|
+
*
|
|
676
|
+
* @param {BoxItemPermission} permissions - the file permissions
|
|
677
|
+
* @param {FileActivityTypes[]} activityTypes - the activity types to filter by
|
|
678
|
+
* @return {Promise} - the file comments
|
|
679
|
+
*/
|
|
680
|
+
fetchFileActivities(permissions: BoxItemPermission, activityTypes: FileActivityTypes[]): Promise<Object> {
|
|
681
|
+
this.fileActivitiesAPI = new FileActivitiesAPI(this.options);
|
|
682
|
+
return new Promise(resolve => {
|
|
683
|
+
this.fileActivitiesAPI.getActivities({
|
|
684
|
+
errorCallback: this.fetchFeedItemErrorCallback.bind(this, resolve),
|
|
685
|
+
fileID: this.file.id,
|
|
686
|
+
permissions,
|
|
687
|
+
successCallback: resolve,
|
|
688
|
+
activityTypes,
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
528
693
|
/**
|
|
529
694
|
* Fetches replies (comments) of a comment or annotation
|
|
530
695
|
*
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flow
|
|
3
|
+
* @file Helper for the box File Activity API
|
|
4
|
+
* @author Box
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import Base from './Base';
|
|
8
|
+
import { PERMISSION_CAN_COMMENT, PERMISSION_CAN_VIEW_ANNOTATIONS, ERROR_CODE_FETCH_ACTIVITY } from '../constants';
|
|
9
|
+
import type { BoxItemPermission } from '../common/types/core';
|
|
10
|
+
import type { ElementsXhrError } from '../common/types/api';
|
|
11
|
+
import type { FileActivity, FileActivityTypes } from '../common/types/feed';
|
|
12
|
+
|
|
13
|
+
// We only show the latest reply in the UI
|
|
14
|
+
const REPLY_LIMIT = 1;
|
|
15
|
+
|
|
16
|
+
const getFileActivityQueryParams = (fileID: string, activityTypes?: FileActivityTypes[] = []) => {
|
|
17
|
+
const baseEndpoint = `/file_activities?file_id=${fileID}`;
|
|
18
|
+
const hasActivityTypes = !!activityTypes && !!activityTypes.length;
|
|
19
|
+
const enabledRepliesQueryParam = `&enable_replies=true&reply_limit=${REPLY_LIMIT}`;
|
|
20
|
+
const activityTypeQueryParam = hasActivityTypes ? `&activity_types=${activityTypes.join()}` : '';
|
|
21
|
+
|
|
22
|
+
return `${baseEndpoint}${activityTypeQueryParam}${enabledRepliesQueryParam}`;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
class FileActivities extends Base {
|
|
26
|
+
/**
|
|
27
|
+
* API URL for filtered file activities
|
|
28
|
+
*
|
|
29
|
+
* @param {string} [id] - a box file id
|
|
30
|
+
* @param {Array<FileActivityTypes>} activityTypes - optional. Array of File Activity types to filter by, returns all Activity Types if omitted.
|
|
31
|
+
* @return {string} base url for files
|
|
32
|
+
*/
|
|
33
|
+
getFilteredUrl(id: string, activityTypes?: FileActivityTypes[]): string {
|
|
34
|
+
return `${this.getBaseApiUrl()}${getFileActivityQueryParams(id, activityTypes)}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* API for fetching file activities
|
|
39
|
+
*
|
|
40
|
+
* @param {Array<FileActivityTypes>} activityTypes - optional. Array of File Activity types to filter by, returns all Activity Types if omitted.
|
|
41
|
+
* @param {Function} errorCallback - the error callback
|
|
42
|
+
* @param {string} fileId - the file id
|
|
43
|
+
* @param {BoxItemPermission} permissions - the permissions for the file
|
|
44
|
+
* @param {number} repliesCount - number of replies to return, by default all replies are returned
|
|
45
|
+
* @param {Function} successCallback - the success callback
|
|
46
|
+
* @returns {void}
|
|
47
|
+
*/
|
|
48
|
+
getActivities({
|
|
49
|
+
activityTypes,
|
|
50
|
+
errorCallback,
|
|
51
|
+
fileID,
|
|
52
|
+
permissions,
|
|
53
|
+
repliesCount,
|
|
54
|
+
successCallback,
|
|
55
|
+
}: {
|
|
56
|
+
activityTypes: FileActivityTypes[],
|
|
57
|
+
errorCallback: (e: ElementsXhrError, code: string) => void,
|
|
58
|
+
fileID: string,
|
|
59
|
+
permissions: BoxItemPermission,
|
|
60
|
+
repliesCount?: number,
|
|
61
|
+
successCallback: (activity: FileActivity) => void,
|
|
62
|
+
}): void {
|
|
63
|
+
this.errorCode = ERROR_CODE_FETCH_ACTIVITY;
|
|
64
|
+
try {
|
|
65
|
+
if (!fileID) {
|
|
66
|
+
throw new Error('Missing file id!');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.checkApiCallValidity(PERMISSION_CAN_COMMENT, permissions, fileID);
|
|
70
|
+
this.checkApiCallValidity(PERMISSION_CAN_VIEW_ANNOTATIONS, permissions, fileID);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
errorCallback(e, this.errorCode);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.get({
|
|
77
|
+
id: fileID,
|
|
78
|
+
successCallback,
|
|
79
|
+
errorCallback,
|
|
80
|
+
requestData: {
|
|
81
|
+
...(repliesCount ? { replies_count: repliesCount } : null),
|
|
82
|
+
},
|
|
83
|
+
url: this.getFilteredUrl(fileID, activityTypes),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default FileActivities;
|
package/src/api/Item.js
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from '../constants';
|
|
21
21
|
import type { ElementsErrorCallback, RequestData, RequestOptions } from '../common/types/api';
|
|
22
22
|
import type {
|
|
23
|
+
Access,
|
|
23
24
|
BoxItem,
|
|
24
25
|
BoxItemPermission,
|
|
25
26
|
FlattenedBoxItem,
|
|
@@ -165,7 +166,7 @@ class Item extends Base {
|
|
|
165
166
|
/**
|
|
166
167
|
* API to delete an Item
|
|
167
168
|
*
|
|
168
|
-
* @param {
|
|
169
|
+
* @param {BoxItem} item - Item to delete
|
|
169
170
|
* @param {Function} successCallback - Success callback
|
|
170
171
|
* @param {Function} errorCallback - Error callback
|
|
171
172
|
* @return {void}
|
|
@@ -221,7 +222,7 @@ class Item extends Base {
|
|
|
221
222
|
/**
|
|
222
223
|
* API to rename an Item
|
|
223
224
|
*
|
|
224
|
-
* @param {
|
|
225
|
+
* @param {BoxItem} item - Item to rename
|
|
225
226
|
* @param {string} name - Item new name
|
|
226
227
|
* @param {Function} successCallback - Success callback
|
|
227
228
|
* @param {Function} errorCallback - Error callback
|
|
@@ -286,32 +287,38 @@ class Item extends Base {
|
|
|
286
287
|
};
|
|
287
288
|
|
|
288
289
|
/**
|
|
289
|
-
* Validate an item
|
|
290
|
+
* Validate request to update an item shared link
|
|
290
291
|
*
|
|
291
292
|
* @param {string|void} itemID - ID of item to share
|
|
292
293
|
* @param {BoxItemPermission|void} itemPermissions - Permissions for item
|
|
293
|
-
* @param {boolean|void}
|
|
294
|
+
* @param {boolean|void} canSkipSetShareAccessPermission - skip `can_set_share_access` permission check
|
|
294
295
|
* @throws {Error}
|
|
295
296
|
* @return {void}
|
|
296
297
|
*/
|
|
297
|
-
|
|
298
|
+
validateSharedLinkRequest(
|
|
299
|
+
itemID: ?string,
|
|
300
|
+
itemPermissions: ?BoxItemPermission,
|
|
301
|
+
canSkipSetShareAccessPermission: boolean = false,
|
|
302
|
+
) {
|
|
298
303
|
if (!itemID || !itemPermissions) {
|
|
299
304
|
this.errorCode = ERROR_CODE_SHARE_ITEM;
|
|
300
305
|
throw getBadItemError();
|
|
301
306
|
}
|
|
302
307
|
|
|
308
|
+
// It is sometimes necessary to skip `can_set_share_access` permission check
|
|
309
|
+
// e.g. Viewer permission can create shared links but cannot update access level
|
|
303
310
|
const { can_share, can_set_share_access }: BoxItemPermission = itemPermissions;
|
|
304
|
-
if (!can_share || (!
|
|
311
|
+
if (!can_share || (!canSkipSetShareAccessPermission && !can_set_share_access)) {
|
|
305
312
|
this.errorCode = ERROR_CODE_SHARE_ITEM;
|
|
306
313
|
throw getBadPermissionsError();
|
|
307
314
|
}
|
|
308
315
|
}
|
|
309
316
|
|
|
310
317
|
/**
|
|
311
|
-
* API to create, modify (change access) or remove a shared link
|
|
318
|
+
* API to create, modify (change access), or remove a shared link
|
|
312
319
|
*
|
|
313
|
-
* @param {
|
|
314
|
-
* @param {
|
|
320
|
+
* @param {BoxItem} item - Item to share
|
|
321
|
+
* @param {Access} access - Shared access level
|
|
315
322
|
* @param {Function} successCallback - Success callback
|
|
316
323
|
* @param {Function|void} errorCallback - Error callback
|
|
317
324
|
* @param {Array<string>|void} [options.fields] - Optionally include specific fields
|
|
@@ -319,7 +326,7 @@ class Item extends Base {
|
|
|
319
326
|
*/
|
|
320
327
|
async share(
|
|
321
328
|
item: BoxItem,
|
|
322
|
-
access
|
|
329
|
+
access?: Access, // if "access" is undefined, the backend will set the default access level for the shared link
|
|
323
330
|
successCallback: Function,
|
|
324
331
|
errorCallback: ElementsErrorCallback = noop,
|
|
325
332
|
options: RequestOptions = {},
|
|
@@ -329,14 +336,14 @@ class Item extends Base {
|
|
|
329
336
|
}
|
|
330
337
|
|
|
331
338
|
try {
|
|
332
|
-
const { id, permissions }: BoxItem = item;
|
|
339
|
+
const { id, permissions, shared_link: sharedLink }: BoxItem = item;
|
|
333
340
|
this.id = id;
|
|
334
341
|
this.successCallback = successCallback;
|
|
335
342
|
this.errorCallback = errorCallback;
|
|
336
343
|
|
|
337
|
-
//
|
|
338
|
-
const
|
|
339
|
-
this.
|
|
344
|
+
// skip permission check when creating links with default access level
|
|
345
|
+
const canSkipSetShareAccessPermission = !sharedLink && access === undefined;
|
|
346
|
+
this.validateSharedLinkRequest(id, permissions, canSkipSetShareAccessPermission);
|
|
340
347
|
|
|
341
348
|
const { fields } = options;
|
|
342
349
|
const requestData: RequestData = {
|
|
@@ -383,7 +390,7 @@ class Item extends Base {
|
|
|
383
390
|
this.successCallback = successCallback;
|
|
384
391
|
this.errorCallback = errorCallback;
|
|
385
392
|
|
|
386
|
-
this.
|
|
393
|
+
this.validateSharedLinkRequest(id, permissions);
|
|
387
394
|
|
|
388
395
|
const { fields } = options;
|
|
389
396
|
const requestData: RequestData = {
|
|
@@ -9,14 +9,24 @@ import {
|
|
|
9
9
|
FEED_ITEM_TYPE_ANNOTATION,
|
|
10
10
|
FEED_ITEM_TYPE_COMMENT,
|
|
11
11
|
FEED_ITEM_TYPE_VERSION,
|
|
12
|
+
FILE_ACTIVITY_TYPE_ANNOTATION,
|
|
13
|
+
FILE_ACTIVITY_TYPE_APP_ACTIVITY,
|
|
14
|
+
FILE_ACTIVITY_TYPE_COMMENT,
|
|
15
|
+
FILE_ACTIVITY_TYPE_TASK,
|
|
12
16
|
IS_ERROR_DISPLAYED,
|
|
13
17
|
TASK_MAX_GROUP_ASSIGNEES,
|
|
14
18
|
} from '../../constants';
|
|
15
19
|
import AnnotationsAPI from '../Annotations';
|
|
16
|
-
import Feed from '../Feed';
|
|
20
|
+
import Feed, { getParsedFileActivitiesResponse } from '../Feed';
|
|
17
21
|
import ThreadedCommentsAPI from '../ThreadedComments';
|
|
18
22
|
import { annotation as mockAnnotation } from '../../__mocks__/annotations';
|
|
19
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
fileActivities as mockFileActivities,
|
|
25
|
+
task as mockTask,
|
|
26
|
+
threadedComments as mockThreadedComments,
|
|
27
|
+
threadedCommentsFormatted,
|
|
28
|
+
annotationsWithFormattedReplies as mockFormattedAnnotations,
|
|
29
|
+
} from '../fixtures';
|
|
20
30
|
|
|
21
31
|
const mockErrors = [{ code: 'error_code_0' }, { code: 'error_code_1' }];
|
|
22
32
|
|
|
@@ -297,6 +307,14 @@ jest.mock('../AppActivity', () =>
|
|
|
297
307
|
})),
|
|
298
308
|
);
|
|
299
309
|
|
|
310
|
+
jest.mock('../FileActivities', () => {
|
|
311
|
+
return jest.fn().mockImplementation(() => ({
|
|
312
|
+
getActivities: jest.fn().mockReturnValue({
|
|
313
|
+
entries: mockFileActivities,
|
|
314
|
+
}),
|
|
315
|
+
}));
|
|
316
|
+
});
|
|
317
|
+
|
|
300
318
|
describe('api/Feed', () => {
|
|
301
319
|
let feed;
|
|
302
320
|
const comments = {
|
|
@@ -417,6 +435,7 @@ describe('api/Feed', () => {
|
|
|
417
435
|
feed.fetchVersions = jest.fn().mockResolvedValue(versions);
|
|
418
436
|
feed.fetchCurrentVersion = jest.fn().mockResolvedValue(mockCurrentVersion);
|
|
419
437
|
feed.fetchTasksNew = jest.fn().mockResolvedValue(tasks);
|
|
438
|
+
feed.fetchFileActivities = jest.fn().mockResolvedValue({ entries: mockFileActivities });
|
|
420
439
|
feed.fetchComments = jest.fn().mockResolvedValue(comments);
|
|
421
440
|
feed.fetchThreadedComments = jest.fn().mockResolvedValue(threadedComments);
|
|
422
441
|
feed.fetchAppActivity = jest.fn().mockReturnValue(appActivities);
|
|
@@ -585,6 +604,21 @@ describe('api/Feed', () => {
|
|
|
585
604
|
done();
|
|
586
605
|
});
|
|
587
606
|
});
|
|
607
|
+
|
|
608
|
+
test('should use the file activities api if shouldUseUAA is true', done => {
|
|
609
|
+
feed.feedItems(file, false, successCb, errorCb, errorCb, { shouldUseUAA: true });
|
|
610
|
+
setImmediate(() => {
|
|
611
|
+
expect(feed.fetchFileActivities).toBeCalledWith(file.permissions, [
|
|
612
|
+
FILE_ACTIVITY_TYPE_ANNOTATION,
|
|
613
|
+
FILE_ACTIVITY_TYPE_APP_ACTIVITY,
|
|
614
|
+
FILE_ACTIVITY_TYPE_COMMENT,
|
|
615
|
+
FILE_ACTIVITY_TYPE_TASK,
|
|
616
|
+
]);
|
|
617
|
+
expect(feed.fetchComments).not.toBeCalled();
|
|
618
|
+
expect(feed.fetchThreadedComments).not.toBeCalled();
|
|
619
|
+
done();
|
|
620
|
+
});
|
|
621
|
+
});
|
|
588
622
|
});
|
|
589
623
|
|
|
590
624
|
describe('fetchAnnotations()', () => {
|
|
@@ -800,6 +834,37 @@ describe('api/Feed', () => {
|
|
|
800
834
|
expect(feed.tasksNewAPI.getTasksForFile).toBeCalled();
|
|
801
835
|
});
|
|
802
836
|
});
|
|
837
|
+
|
|
838
|
+
describe('fetchFileActivities()', () => {
|
|
839
|
+
beforeEach(() => {
|
|
840
|
+
feed.file = file;
|
|
841
|
+
feed.fetchFeedItemErrorCallback = jest.fn();
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
test('should return a promise and call the file activities api', () => {
|
|
845
|
+
const permissions = { can_edit: true, can_delete: true, can_resolve: true };
|
|
846
|
+
const fileActivityItems = feed.fetchFileActivities(permissions, [
|
|
847
|
+
FILE_ACTIVITY_TYPE_ANNOTATION,
|
|
848
|
+
FILE_ACTIVITY_TYPE_APP_ACTIVITY,
|
|
849
|
+
FILE_ACTIVITY_TYPE_COMMENT,
|
|
850
|
+
FILE_ACTIVITY_TYPE_TASK,
|
|
851
|
+
]);
|
|
852
|
+
expect(fileActivityItems instanceof Promise).toBeTruthy();
|
|
853
|
+
expect(feed.fileActivitiesAPI.getActivities).toBeCalledWith({
|
|
854
|
+
activityTypes: [
|
|
855
|
+
FILE_ACTIVITY_TYPE_ANNOTATION,
|
|
856
|
+
FILE_ACTIVITY_TYPE_APP_ACTIVITY,
|
|
857
|
+
FILE_ACTIVITY_TYPE_COMMENT,
|
|
858
|
+
FILE_ACTIVITY_TYPE_TASK,
|
|
859
|
+
],
|
|
860
|
+
errorCallback: expect.any(Function),
|
|
861
|
+
fileID: feed.file.id,
|
|
862
|
+
permissions,
|
|
863
|
+
successCallback: expect.any(Function),
|
|
864
|
+
});
|
|
865
|
+
expect(fileActivityItems).resolves.toEqual({ entries: mockFileActivities });
|
|
866
|
+
});
|
|
867
|
+
});
|
|
803
868
|
});
|
|
804
869
|
|
|
805
870
|
describe('updateTaskCollaborator()', () => {
|
|
@@ -2235,4 +2300,31 @@ describe('api/Feed', () => {
|
|
|
2235
2300
|
expect(feed.updateFeedItem).toBeCalledWith({ total_reply_count: total_reply_count + 1 }, id);
|
|
2236
2301
|
});
|
|
2237
2302
|
});
|
|
2303
|
+
|
|
2304
|
+
describe('getParsedFileActivitiesResponse()', () => {
|
|
2305
|
+
test.each`
|
|
2306
|
+
response
|
|
2307
|
+
${undefined}
|
|
2308
|
+
${{}}
|
|
2309
|
+
${{ entries: { test: 'invalid' } }}
|
|
2310
|
+
${{ entries: [{ test: 'invalid' }] }}
|
|
2311
|
+
${{ entries: [{ source: { activity: 'invalid' } }] }}
|
|
2312
|
+
`('should return an empty entries array when the response is $response', ({ response }) => {
|
|
2313
|
+
expect(getParsedFileActivitiesResponse(response)).toEqual({ entries: [] });
|
|
2314
|
+
});
|
|
2315
|
+
|
|
2316
|
+
test.each`
|
|
2317
|
+
response
|
|
2318
|
+
${{ entries: mockFileActivities }}
|
|
2319
|
+
${{ entries: [...mockFileActivities, { test: 'filtered out' }] }}
|
|
2320
|
+
`('should return a parsed entries array when response is valid', ({ response }) => {
|
|
2321
|
+
expect(getParsedFileActivitiesResponse(response)).toEqual({
|
|
2322
|
+
entries: [
|
|
2323
|
+
mockFormattedAnnotations[0],
|
|
2324
|
+
threadedCommentsFormatted[0],
|
|
2325
|
+
{ ...mockTask, task_type: 'GENERAL', created_by: { target: mockTask.created_by.target } },
|
|
2326
|
+
],
|
|
2327
|
+
});
|
|
2328
|
+
});
|
|
2329
|
+
});
|
|
2238
2330
|
});
|