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.
Files changed (87) hide show
  1. package/dist/explorer.js +16 -16
  2. package/dist/openwith.js +6 -6
  3. package/dist/picker.js +10 -10
  4. package/dist/preview.js +12 -12
  5. package/dist/sharing.js +8 -8
  6. package/dist/sidebar.js +9 -9
  7. package/dist/uploader.js +6 -6
  8. package/es/api/APIFactory.js +27 -0
  9. package/es/api/APIFactory.js.flow +26 -0
  10. package/es/api/APIFactory.js.map +1 -1
  11. package/es/api/Feed.js +225 -52
  12. package/es/api/Feed.js.flow +191 -26
  13. package/es/api/Feed.js.map +1 -1
  14. package/es/api/FileActivities.js +119 -0
  15. package/es/api/FileActivities.js.flow +88 -0
  16. package/es/api/FileActivities.js.map +1 -0
  17. package/es/api/Item.js +21 -18
  18. package/es/api/Item.js.flow +22 -15
  19. package/es/api/Item.js.map +1 -1
  20. package/es/api/fixtures.js +46 -1
  21. package/es/api/fixtures.js.flow +39 -1
  22. package/es/api/fixtures.js.map +1 -1
  23. package/es/common/types/feed.js +1 -1
  24. package/es/common/types/feed.js.flow +38 -0
  25. package/es/common/types/feed.js.map +1 -1
  26. package/es/components/pill-selector-dropdown/PillSelectorDropdown.stories.js +1 -1
  27. package/es/components/pill-selector-dropdown/PillSelectorDropdown.stories.js.flow +1 -1
  28. package/es/components/pill-selector-dropdown/PillSelectorDropdown.stories.js.map +1 -1
  29. package/es/constants.js +7 -0
  30. package/es/constants.js.flow +7 -0
  31. package/es/constants.js.map +1 -1
  32. package/es/elements/content-explorer/ContentExplorer.js +5 -18
  33. package/es/elements/content-explorer/ContentExplorer.js.flow +6 -16
  34. package/es/elements/content-explorer/ContentExplorer.js.map +1 -1
  35. package/es/elements/content-picker/ContentPicker.js +5 -8
  36. package/es/elements/content-picker/ContentPicker.js.flow +6 -9
  37. package/es/elements/content-picker/ContentPicker.js.map +1 -1
  38. package/es/elements/content-sharing/hooks/useSharedLink.js +1 -0
  39. package/es/elements/content-sharing/hooks/useSharedLink.js.flow +1 -0
  40. package/es/elements/content-sharing/hooks/useSharedLink.js.map +1 -1
  41. package/es/elements/content-sharing/types.js.flow +2 -1
  42. package/es/elements/content-sidebar/ActivitySidebar.js +6 -2
  43. package/es/elements/content-sidebar/ActivitySidebar.js.flow +11 -1
  44. package/es/elements/content-sidebar/ActivitySidebar.js.map +1 -1
  45. package/es/elements/content-sidebar/activity-feed/activity-feed/ActiveState.js +2 -0
  46. package/es/elements/content-sidebar/activity-feed/activity-feed/ActiveState.js.flow +3 -0
  47. package/es/elements/content-sidebar/activity-feed/activity-feed/ActiveState.js.map +1 -1
  48. package/es/elements/content-sidebar/activity-feed/activity-feed/ActivityFeed.js +2 -0
  49. package/es/elements/content-sidebar/activity-feed/activity-feed/ActivityFeed.js.flow +3 -0
  50. package/es/elements/content-sidebar/activity-feed/activity-feed/ActivityFeed.js.map +1 -1
  51. package/es/features/unified-share-modal/UnifiedShareForm.js +8 -7
  52. package/es/features/unified-share-modal/UnifiedShareForm.js.flow +10 -8
  53. package/es/features/unified-share-modal/UnifiedShareForm.js.map +1 -1
  54. package/es/features/unified-share-modal/messages.js +1 -1
  55. package/es/features/unified-share-modal/messages.js.flow +2 -1
  56. package/es/features/unified-share-modal/messages.js.map +1 -1
  57. package/es/utils/fields.js +1 -1
  58. package/es/utils/fields.js.flow +1 -1
  59. package/es/utils/fields.js.map +1 -1
  60. package/i18n/en-US.js +1 -1
  61. package/i18n/en-US.properties +1 -1
  62. package/i18n/en-x-pseudo.js +1 -1
  63. package/package.json +1 -1
  64. package/src/api/APIFactory.js +26 -0
  65. package/src/api/Feed.js +191 -26
  66. package/src/api/FileActivities.js +88 -0
  67. package/src/api/Item.js +22 -15
  68. package/src/api/__tests__/Feed.test.js +94 -2
  69. package/src/api/__tests__/FileActivities.test.js +95 -0
  70. package/src/api/__tests__/Item.test.js +14 -12
  71. package/src/api/fixtures.js +39 -1
  72. package/src/common/types/feed.js +38 -0
  73. package/src/components/pill-selector-dropdown/PillSelectorDropdown.stories.js +1 -1
  74. package/src/constants.js +7 -0
  75. package/src/elements/content-explorer/ContentExplorer.js +6 -16
  76. package/src/elements/content-explorer/__tests__/ContentExplorer.test.js +2 -2
  77. package/src/elements/content-picker/ContentPicker.js +6 -9
  78. package/src/elements/content-sharing/hooks/useSharedLink.js +1 -0
  79. package/src/elements/content-sharing/types.js +2 -1
  80. package/src/elements/content-sidebar/ActivitySidebar.js +11 -1
  81. package/src/elements/content-sidebar/__tests__/ActivitySidebar.test.js +9 -4
  82. package/src/elements/content-sidebar/__tests__/__snapshots__/ActivitySidebar.test.js.snap +1 -0
  83. package/src/elements/content-sidebar/activity-feed/activity-feed/ActiveState.js +3 -0
  84. package/src/elements/content-sidebar/activity-feed/activity-feed/ActivityFeed.js +3 -0
  85. package/src/features/unified-share-modal/UnifiedShareForm.js +10 -8
  86. package/src/features/unified-share-modal/messages.js +2 -1
  87. 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
- const annotationsPromise = shouldShowAnnotations
398
- ? this.fetchAnnotations(permissions, shouldShowReplies)
399
- : Promise.resolve();
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 commentsPromise = shouldShowReplies
403
- ? this.fetchThreadedComments(permissions)
404
- : this.fetchComments(permissions);
405
- const tasksPromise = shouldShowTasks ? this.fetchTasksNew() : Promise.resolve();
406
- const appActivityPromise = shouldShowAppActivity ? this.fetchAppActivity(permissions) : Promise.resolve();
407
-
408
- Promise.all([
409
- versionsPromise,
410
- currentVersionPromise,
411
- commentsPromise,
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, sortedFeedItems);
532
+ this.setCachedItems(id, feedItems);
422
533
  if (this.errors.length) {
423
- errorCallback(sortedFeedItems, this.errors);
534
+ errorCallback(feedItems, this.errors);
424
535
  } else {
425
- successCallback(sortedFeedItems);
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 {Object} item - Item to delete
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 {Object} item - Item to rename
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 update request
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} canSkipSetShareAccess - skip the check for can_set_share_access when creating a new shared link
294
+ * @param {boolean|void} canSkipSetShareAccessPermission - skip `can_set_share_access` permission check
294
295
  * @throws {Error}
295
296
  * @return {void}
296
297
  */
297
- validateRequest(itemID: ?string, itemPermissions: ?BoxItemPermission, canSkipSetShareAccess: boolean = false) {
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 || (!canSkipSetShareAccess && !can_set_share_access)) {
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 {Object} item - Item to share
314
- * @param {string} access - Shared access level
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: ?string, // if "access" is undefined, the backend will set the default access level for the shared link
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
- // if we use the default access level, we don't need permission to set the access level
338
- const canSkipSetShareAccess = access === undefined;
339
- this.validateRequest(id, permissions, canSkipSetShareAccess);
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.validateRequest(id, permissions);
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 { task as mockTask, threadedComments as mockThreadedComments, threadedCommentsFormatted } from '../fixtures';
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
  });