Skip to content

Commit

Permalink
feat(uaa-integration): Integrate UAA into ActivitySidebar (#3316)
Browse files Browse the repository at this point in the history
* feat(threaded-replies): Use UAA

* feat(threaded-replies): Fix endpoint call, fix task item

* feat(threaded-replies): Fix tests and remove unused params

* feat(uaa-integration): Fix lint issues, address PR comments

* feat(uaa-integration): Add tests, fix linting errors, comments

* feat(uaa-integration): Fix typing and parsing

* feat(uaa-integration): Address PR comments

* feat(uaa-integration): Improve readability, keep versions call

* feat(uaa-integration): Fix flow type issues

* feat(uaa-integration): Address PR comments and add tests

* feat(uaa-integration): Fix test

* feat(uaa-integration): address pr comments

* feat(uaa-integration): address PR comments
  • Loading branch information
JChan106 authored May 31, 2023
1 parent 4a945ce commit b81e976
Show file tree
Hide file tree
Showing 13 changed files with 605 additions and 34 deletions.
26 changes: 26 additions & 0 deletions src/api/APIFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import TaskCollaboratorsAPI from './tasks/TaskCollaborators';
import TaskLinksAPI from './tasks/TaskLinks';
import ThreadedCommentsAPI from './ThreadedComments';
import FileAccessStatsAPI from './FileAccessStats';
import FileActivitiesAPI from './FileActivities';
import MarkerBasedGroupsAPI from './MarkerBasedGroups';
import MarkerBasedUsersAPI from './MarkerBasedUsers';
import GroupsAPI from './Groups';
Expand Down Expand Up @@ -117,6 +118,11 @@ class APIFactory {
*/
fileAccessStatsAPI: FileAccessStatsAPI;

/*
* @property {FileActivitiesAPI}
*/
fileActivitiesAPI: FileActivitiesAPI;

/*
* @property {MarkerBasedGroupsAPI}
*/
Expand Down Expand Up @@ -266,6 +272,11 @@ class APIFactory {
delete this.fileAccessStatsAPI;
}

if (this.fileActivitiesAPI) {
this.fileActivitiesAPI.destroy();
delete this.fileActivitiesAPI;
}

if (this.tasksNewAPI) {
this.tasksNewAPI.destroy();
delete this.tasksNewAPI;
Expand Down Expand Up @@ -596,6 +607,21 @@ class APIFactory {
return this.fileAccessStatsAPI;
}

/**
* API for file access stats
*
* @param {boolean} shouldDestroy - true if the factory should destroy before returning the call
* @return {FileActivitiesAPI} FileActivitiesAPI instance
*/
getFileActivitiesAPI(shouldDestroy: boolean): FileActivitiesAPI {
if (shouldDestroy) {
this.destroy();
}

this.fileActivitiesAPI = new FileActivitiesAPI(this.options);
return this.fileActivitiesAPI;
}

/**
* API for file collaborators
*
Expand Down
217 changes: 191 additions & 26 deletions src/api/Feed.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* @file Helper for activity feed API's
* @author Box
*/
import uniqueId from 'lodash/uniqueId';
import noop from 'lodash/noop';
import uniqueId from 'lodash/uniqueId';
import type { MessageDescriptor } from 'react-intl';
import { getBadItemError, getBadUserError, getMissingItemTextOrStatus, isUserCorrectableError } from '../utils/error';
import commonMessages from '../elements/common/messages';
Expand All @@ -15,6 +15,7 @@ import Base from './Base';
import AnnotationsAPI from './Annotations';
import CommentsAPI from './Comments';
import ThreadedCommentsAPI from './ThreadedComments';
import FileActivitiesAPI from './FileActivities';
import VersionsAPI from './Versions';
import TasksNewAPI from './tasks/TasksNew';
import GroupsAPI from './Groups';
Expand All @@ -28,6 +29,10 @@ import {
FEED_ITEM_TYPE_ANNOTATION,
FEED_ITEM_TYPE_COMMENT,
FEED_ITEM_TYPE_TASK,
FILE_ACTIVITY_TYPE_ANNOTATION,
FILE_ACTIVITY_TYPE_APP_ACTIVITY,
FILE_ACTIVITY_TYPE_COMMENT,
FILE_ACTIVITY_TYPE_TASK,
HTTP_STATUS_CODE_CONFLICT,
IS_ERROR_DISPLAYED,
TASK_NEW_APPROVED,
Expand Down Expand Up @@ -71,6 +76,8 @@ import type {
FeedItem,
FeedItems,
FeedItemStatus,
FileActivity,
FileActivityTypes,
Task,
Tasks,
ThreadedComments as ThreadedCommentsType,
Expand All @@ -95,6 +102,97 @@ const getItemWithPendingReply = <T: { replies?: Array<Comment> }>(item: T, reply
return { replies: [...replies, reply], ...rest };
};

const parseReplies = (replies: Comment[]): Comment[] => {
const parsedReplies = [...replies];

return parsedReplies.map(reply => {
return { ...reply, tagged_message: reply.tagged_message || reply.message || '' };
});
};

export const getParsedFileActivitiesResponse = (response?: { entries: FileActivity[] }) => {
if (!response || !response.entries || !response.entries.length) {
return { entries: [] };
}

const data = response.entries;

const parsedData: Array<Object> = data
.map(item => {
if (!item.source) {
return null;
}

const source = { ...item.source };

switch (item.activity_type) {
case FILE_ACTIVITY_TYPE_TASK: {
const taskItem = { ...source[FILE_ACTIVITY_TYPE_TASK] };
// UAA follows a lowercased enum naming convention, convert to uppercase to align with task api
if (taskItem.assigned_to?.entries) {
const assignedToEntries = taskItem.assigned_to.entries.map(entry => {
const assignedToEntry = { ...entry };

assignedToEntry.role = entry.role.toUpperCase();
assignedToEntry.status = entry.status.toUpperCase();

return assignedToEntry;
});
// $FlowFixMe Using the toUpperCase method makes Flow assume role and status is a string type, which is incompatible with string literal
taskItem.assigned_to.entries = assignedToEntries;
}
if (taskItem.completion_rule) {
taskItem.completion_rule = taskItem.completion_rule.toUpperCase();
}
if (taskItem.status) {
taskItem.status = taskItem.status.toUpperCase();
}
if (taskItem.task_type) {
taskItem.task_type = taskItem.task_type.toUpperCase();
}
// $FlowFixMe File Activities only returns a created_by user, Flow type fix is needed
taskItem.created_by = { target: taskItem.created_by };

return taskItem;
}
case FILE_ACTIVITY_TYPE_COMMENT: {
const commentItem = { ...source[FILE_ACTIVITY_TYPE_COMMENT] };

if (commentItem.replies && commentItem.replies.length) {
const replies = parseReplies(commentItem.replies);

commentItem.replies = replies;
}

commentItem.tagged_message = commentItem.tagged_message || commentItem.message || '';

return commentItem;
}
case FILE_ACTIVITY_TYPE_ANNOTATION: {
const annotationItem = { ...source[FILE_ACTIVITY_TYPE_ANNOTATION] };

if (annotationItem.replies && annotationItem.replies.length) {
const replies = parseReplies(annotationItem.replies);

annotationItem.replies = replies;
}

return annotationItem;
}
case FILE_ACTIVITY_TYPE_APP_ACTIVITY: {
return { ...source[FILE_ACTIVITY_TYPE_APP_ACTIVITY] };
}

default: {
return null;
}
}
})
.filter(item => !!item);

return { entries: parsedData };
};

class Feed extends Base {
/**
* @property {AnnotationsAPI}
Expand Down Expand Up @@ -136,6 +234,11 @@ class Feed extends Base {
*/
threadedCommentsAPI: ThreadedCommentsAPI;

/**
* @property {FileActivitiesAPI}
*/
fileActivitiesAPI: FileActivitiesAPI;

/**
* @property {BoxItem}
*/
Expand Down Expand Up @@ -368,12 +471,14 @@ class Feed extends Base {
shouldShowReplies = false,
shouldShowTasks = true,
shouldShowVersions = true,
shouldUseUAA = false,
}: {
shouldShowAnnotations?: boolean,
shouldShowAppActivity?: boolean,
shouldShowReplies?: boolean,
shouldShowTasks?: boolean,
shouldShowVersions?: boolean,
shouldUseUAA?: boolean,
} = {},
): void {
const { id, permissions = {} } = file;
Expand All @@ -394,38 +499,78 @@ class Feed extends Base {
this.file = file;
this.errors = [];
this.errorCallback = onError;
const annotationsPromise = shouldShowAnnotations
? this.fetchAnnotations(permissions, shouldShowReplies)
: Promise.resolve();

// Using the UAA File Activities endpoint replaces the need for these calls
const annotationsPromise =
!shouldUseUAA && shouldShowAnnotations
? this.fetchAnnotations(permissions, shouldShowReplies)
: Promise.resolve();
const commentsPromise = () => {
if (shouldUseUAA) {
return Promise.resolve();
}

return shouldShowReplies ? this.fetchThreadedComments(permissions) : this.fetchComments(permissions);
};
const tasksPromise = !shouldUseUAA && shouldShowTasks ? this.fetchTasksNew() : Promise.resolve();
const appActivityPromise =
!shouldUseUAA && shouldShowAppActivity ? this.fetchAppActivity(permissions) : Promise.resolve();

const versionsPromise = shouldShowVersions ? this.fetchVersions() : Promise.resolve();
const currentVersionPromise = shouldShowVersions ? this.fetchCurrentVersion() : Promise.resolve();
const commentsPromise = shouldShowReplies
? this.fetchThreadedComments(permissions)
: this.fetchComments(permissions);
const tasksPromise = shouldShowTasks ? this.fetchTasksNew() : Promise.resolve();
const appActivityPromise = shouldShowAppActivity ? this.fetchAppActivity(permissions) : Promise.resolve();

Promise.all([
versionsPromise,
currentVersionPromise,
commentsPromise,
tasksPromise,
appActivityPromise,
annotationsPromise,
]).then(([versions: ?FileVersions, currentVersion: ?BoxItemVersion, ...feedItems]) => {
const versionsWithCurrent = currentVersion
? this.versionsAPI.addCurrentVersion(currentVersion, versions, this.file)
: undefined;
const sortedFeedItems = sortFeedItems(versionsWithCurrent, ...feedItems);
const fileActivitiesPromise = shouldUseUAA
? this.fetchFileActivities(permissions, [
FILE_ACTIVITY_TYPE_ANNOTATION,
FILE_ACTIVITY_TYPE_APP_ACTIVITY,
FILE_ACTIVITY_TYPE_COMMENT,
FILE_ACTIVITY_TYPE_TASK,
])
: Promise.resolve();

const handleFeedItems = (feedItems: FeedItems) => {
if (!this.isDestroyed()) {
this.setCachedItems(id, sortedFeedItems);
this.setCachedItems(id, feedItems);
if (this.errors.length) {
errorCallback(sortedFeedItems, this.errors);
errorCallback(feedItems, this.errors);
} else {
successCallback(sortedFeedItems);
successCallback(feedItems);
}
}
});
};

if (shouldUseUAA) {
Promise.all([versionsPromise, currentVersionPromise, fileActivitiesPromise]).then(
([versions: ?FileVersions, currentVersion: ?BoxItemVersion, ...feedItems]) => {
if (!feedItems || !feedItems.length) {
return;
}

const fileActivitiesResponse = feedItems[0];
const versionsWithCurrent = currentVersion
? this.versionsAPI.addCurrentVersion(currentVersion, versions, this.file)
: undefined;
const parsedFeedItems = getParsedFileActivitiesResponse(fileActivitiesResponse);
// $FlowFixMe Does not need to be sorted once we include versions in the file activities call
const sortedFeedItems = sortFeedItems(versionsWithCurrent, parsedFeedItems);
handleFeedItems(sortedFeedItems);
},
);
} else {
Promise.all([
versionsPromise,
currentVersionPromise,
commentsPromise(),
tasksPromise,
appActivityPromise,
annotationsPromise,
]).then(([versions: ?FileVersions, currentVersion: ?BoxItemVersion, ...feedItems]) => {
const versionsWithCurrent = currentVersion
? this.versionsAPI.addCurrentVersion(currentVersion, versions, this.file)
: undefined;
const sortedFeedItems = sortFeedItems(versionsWithCurrent, ...feedItems);
handleFeedItems(sortedFeedItems);
});
}
}

fetchAnnotations(permissions: BoxItemPermission, shouldFetchReplies?: boolean): Promise<?Annotations> {
Expand Down Expand Up @@ -525,6 +670,26 @@ class Feed extends Base {
});
}

/**
* Fetches the file activities for a file
*
* @param {BoxItemPermission} permissions - the file permissions
* @param {FileActivityTypes[]} activityTypes - the activity types to filter by
* @return {Promise} - the file comments
*/
fetchFileActivities(permissions: BoxItemPermission, activityTypes: FileActivityTypes[]): Promise<Object> {
this.fileActivitiesAPI = new FileActivitiesAPI(this.options);
return new Promise(resolve => {
this.fileActivitiesAPI.getActivities({
errorCallback: this.fetchFeedItemErrorCallback.bind(this, resolve),
fileID: this.file.id,
permissions,
successCallback: resolve,
activityTypes,
});
});
}

/**
* Fetches replies (comments) of a comment or annotation
*
Expand Down
Loading

0 comments on commit b81e976

Please sign in to comment.