box-ui-elements
Version:
Box UI Elements
1,145 lines (1,126 loc) • 40 kB
JavaScript
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
/**
*
* @file Activity feed sidebar component
* @author Box
*/
import * as React from 'react';
import classNames from 'classnames';
import debounce from 'lodash/debounce';
import flow from 'lodash/flow';
import getProp from 'lodash/get';
import noop from 'lodash/noop';
import uniqueId from 'lodash/uniqueId';
import { FormattedMessage } from 'react-intl';
import { generatePath } from 'react-router-dom';
import ActivityFeed from './activity-feed';
import AddTaskButton from './AddTaskButton';
import API from '../../api';
import messages from '../common/messages';
import SidebarContent from './SidebarContent';
import { EVENT_DATA_READY, EVENT_JS_READY } from '../common/logger/constants';
import { getBadUserError } from '../../utils/error';
import { mark } from '../../utils/performance';
import { withAnnotatorContext } from '../common/annotator-context';
import { withAPIContext } from '../common/api-context';
import { withErrorBoundary } from '../common/error-boundary';
import { withFeatureConsumer, isFeatureEnabled } from '../common/feature-checking';
import { withLogger } from '../common/logger';
import { withRouterAndRef } from '../common/routing';
import ActivitySidebarFilter from './ActivitySidebarFilter';
import { ViewType, FeedEntryType } from '../common/types/SidebarNavigation';
import { ACTIVITY_FILTER_OPTION_ALL, ACTIVITY_FILTER_OPTION_RESOLVED, ACTIVITY_FILTER_OPTION_TASKS, ACTIVITY_FILTER_OPTION_UNRESOLVED, DEFAULT_COLLAB_DEBOUNCE, ERROR_CODE_FETCH_ACTIVITY, FEED_ITEM_TYPE_ANNOTATION, FEED_ITEM_TYPE_COMMENT, FEED_ITEM_TYPE_TASK, FEED_ITEM_TYPE_VERSION, ORIGIN_ACTIVITY_SIDEBAR, SIDEBAR_VIEW_ACTIVITY, TASK_COMPLETION_RULE_ALL, METRIC_TYPE_UAA_PARITY_METRIC } from '../../constants';
import './ActivitySidebar.scss';
export const activityFeedInlineError = {
inlineError: {
title: messages.errorOccured,
content: messages.activityFeedItemApiError
}
};
const MARK_NAME_DATA_LOADING = `${ORIGIN_ACTIVITY_SIDEBAR}_data_loading`;
const MARK_NAME_DATA_READY = `${ORIGIN_ACTIVITY_SIDEBAR}_${EVENT_DATA_READY}`;
const MARK_NAME_JS_READY = `${ORIGIN_ACTIVITY_SIDEBAR}_${EVENT_JS_READY}`;
mark(MARK_NAME_JS_READY);
class ActivitySidebar extends React.PureComponent {
constructor(props) {
super(props);
// eslint-disable-next-line react/prop-types
_defineProperty(this, "handleAnnotationDelete", ({
id,
permissions
}) => {
const {
api,
emitAnnotationRemoveEvent,
file
} = this.props;
emitAnnotationRemoveEvent(id, true);
api.getFeedAPI(false).deleteAnnotation(file, id, permissions, this.deleteAnnotationSuccess.bind(this, id), this.feedErrorCallback);
this.fetchFeedItems();
});
_defineProperty(this, "handleAnnotationEdit", ({
id,
text,
permissions
}) => {
const {
api,
emitAnnotationUpdateEvent,
file
} = this.props;
emitAnnotationUpdateEvent({
id,
description: {
message: text
}
}, true);
api.getFeedAPI(false).updateAnnotation(file, id, text, undefined, permissions, annotation => {
emitAnnotationUpdateEvent(annotation);
this.feedSuccessCallback();
}, this.feedErrorCallback);
this.fetchFeedItems();
});
_defineProperty(this, "handleAnnotationStatusChange", ({
id,
permissions,
status
}) => {
const {
api,
emitAnnotationUpdateEvent,
file
} = this.props;
emitAnnotationUpdateEvent({
id,
status
}, true);
api.getFeedAPI(false).updateAnnotation(file, id, undefined, status, permissions, annotation => {
emitAnnotationUpdateEvent(annotation);
this.feedSuccessCallback();
}, this.feedErrorCallback);
this.fetchFeedItems();
});
/**
* Success callback for fetching feed items
*/
_defineProperty(this, "feedSuccessCallback", () => {
this.fetchFeedItems();
});
/**
* Error callback for fetching feed items
*
* @param {Error} e - the error which occured
* @param {Error} code - the code for the error
* @param {Object} contextInfo - the context info for the error
*/
_defineProperty(this, "feedErrorCallback", (e, code, contextInfo) => {
this.errorCallback(e, code, contextInfo);
this.fetchFeedItems();
});
_defineProperty(this, "createTask", (message, assignees, taskType, dueAt, completionRule, onSuccess, onError) => {
const {
api,
currentUser,
file
} = this.props;
if (!currentUser) {
throw getBadUserError();
}
const errorCallback = (e, code, contextInfo) => {
if (onError) {
onError(e, code, contextInfo);
}
this.feedErrorCallback(e, code, contextInfo);
};
const successCallback = () => {
if (onSuccess) {
onSuccess();
}
this.feedSuccessCallback();
};
api.getFeedAPI(false).createTaskNew(file, currentUser, message, assignees, taskType, dueAt, completionRule, successCallback, errorCallback);
// need to load the pending item
this.fetchFeedItems();
});
_defineProperty(this, "deleteTask", task => {
const {
file,
api,
onTaskDelete
} = this.props;
api.getFeedAPI(false).deleteTaskNew(file, task, taskId => {
this.feedSuccessCallback();
onTaskDelete(taskId);
}, this.feedErrorCallback);
// need to load the pending item
this.fetchFeedItems();
});
_defineProperty(this, "updateTask", (task, onSuccess, onError) => {
const {
api,
file,
onTaskUpdate
} = this.props;
const errorCallback = (e, code) => {
if (onError) {
onError(e, code);
}
this.feedErrorCallback(e, code);
};
const successCallback = () => {
this.feedSuccessCallback();
if (onSuccess) {
onSuccess();
}
onTaskUpdate();
};
api.getFeedAPI(false).updateTaskNew(file, task, successCallback, errorCallback);
// need to load the pending item
this.fetchFeedItems();
});
_defineProperty(this, "updateTaskAssignment", (taskId, taskAssignmentId, status) => {
const {
api,
currentUser = {},
file,
onTaskAssignmentUpdate
} = this.props;
const successCallback = () => {
this.feedSuccessCallback();
onTaskAssignmentUpdate(taskId, taskAssignmentId, status, currentUser.id);
};
api.getFeedAPI(false).updateTaskCollaborator(file, taskId, taskAssignmentId, status, successCallback, this.feedErrorCallback);
// need to load the pending item
this.fetchFeedItems();
});
/**
* Deletes a comment via the API.
*
* @param {Object} args - A subset of the comment
* @return void
*/
_defineProperty(this, "deleteComment", ({
id,
permissions
}) => {
const {
api,
file,
hasReplies,
onCommentDelete
} = this.props;
const successCallback = comment => {
this.feedSuccessCallback();
onCommentDelete(comment);
};
if (hasReplies) {
api.getFeedAPI(false).deleteThreadedComment(file, id, permissions, successCallback, this.feedErrorCallback);
} else {
api.getFeedAPI(false).deleteComment(file, id, permissions, successCallback, this.feedErrorCallback);
}
// need to load the pending item
this.fetchFeedItems();
});
/**
* Deletes a reply via the API.
*
* @param {Object} args - A subset of the comment
* @return void
*/
_defineProperty(this, "deleteReply", ({
id,
parentId,
permissions
}) => {
const {
api,
emitAnnotationReplyDeleteEvent,
file
} = this.props;
emitAnnotationReplyDeleteEvent(id, parentId, true);
api.getFeedAPI(false).deleteReply(file, id, parentId, permissions, this.deleteReplySuccessCallback.bind(this, id, parentId), this.feedErrorCallback);
// need to load the pending item
this.fetchFeedItems();
});
/**
* Handles a successful deletion of a reply
*
* @private
* @param {string} id - The id of the reply
* @param {string} parentId - The id of the reply's parent item
* @return {void}
*/
_defineProperty(this, "deleteReplySuccessCallback", (id, parentId) => {
const {
emitAnnotationReplyDeleteEvent
} = this.props;
this.feedSuccessCallback();
emitAnnotationReplyDeleteEvent(id, parentId);
});
_defineProperty(this, "updateComment", (id, text, status, hasMention, permissions, onSuccess, onError) => {
const {
api,
file,
hasReplies,
onCommentUpdate
} = this.props;
const errorCallback = (e, code) => {
if (onError) {
onError(e, code);
}
this.feedErrorCallback(e, code);
};
const successCallback = () => {
this.feedSuccessCallback();
if (onSuccess) {
onSuccess();
}
onCommentUpdate();
};
if (hasReplies) {
api.getFeedAPI(false).updateThreadedComment(file, id, text, status, permissions, successCallback, errorCallback);
} else {
api.getFeedAPI(false).updateComment(file, id, text || '', hasMention, permissions, successCallback, errorCallback);
}
// need to load the pending item
this.fetchFeedItems();
});
/**
* Updates a reply
*
* @param {string} id - id of the reply
* @param {string} parentId - id of the parent item
* @param {string} text - the reply updated text
* @param {BoxCommentPermission} permissions - permissions associated with the reply
* @param {Function} onSuccess - the success callback
* @param {Function} onError - the error callback
* @return {void}
*/
_defineProperty(this, "updateReply", (id, parentId, text, permissions, onSuccess, onError) => {
const {
api,
emitAnnotationReplyUpdateEvent,
file
} = this.props;
emitAnnotationReplyUpdateEvent({
id,
tagged_message: text
}, parentId, true);
api.getFeedAPI(false).updateReply(file, id, parentId, text, permissions, this.updateReplySuccessCallback.bind(this, parentId, onSuccess), (error, code) => {
if (onError) {
onError(error, code);
}
this.feedErrorCallback(error, code);
});
// need to load the pending item
this.fetchFeedItems();
});
/**
* Updates replies of a comment or annotation in the Feed
*
* @param {string} id - id of the feed item
* @param {Array<Comment>} replies - replies
* @return {void}
*/
_defineProperty(this, "updateReplies", (id, replies) => {
const {
activeFeedEntryId,
api,
file,
history,
internalSidebarNavigationHandler,
routerDisabled
} = this.props;
const {
feedItems
} = this.state;
if (!feedItems) {
return;
}
const feedAPI = api.getFeedAPI(false);
feedAPI.file = file;
// Detect if replies are being hidden and activeFeedEntryId belongs to a reply
// that is in currently being updated parent, in order to disable active item
if (activeFeedEntryId && replies.length === 1 && feedItems.some(item => item.id === id && item === this.getCommentFeedItemByReplyId(feedItems, activeFeedEntryId))) {
if (routerDisabled && internalSidebarNavigationHandler) {
internalSidebarNavigationHandler({
sidebar: ViewType.ACTIVITY
}, true);
} else {
history.replace(this.getActiveCommentPath());
}
}
feedAPI.updateFeedItem({
replies
}, id);
this.fetchFeedItems();
});
/**
* Handles a successful update of a reply
*
* @private
* @param {string} parentId - The id of the reply's parent item
* @param {Function} onSuccess - the success callback
* @param {Comment} reply - The reply comment object
* @return {void}
*/
_defineProperty(this, "updateReplySuccessCallback", (parentId, onSuccess, reply) => {
const {
emitAnnotationReplyUpdateEvent
} = this.props;
this.feedSuccessCallback();
emitAnnotationReplyUpdateEvent(reply, parentId);
if (onSuccess) {
onSuccess();
}
});
/**
* Posts a new comment to the API
*
* @param {string} text - The comment's text
* @param {boolean} hasMention - The comment's text
* @return {void}
*/
_defineProperty(this, "createComment", (text, hasMention) => {
const {
api,
currentUser,
file,
hasReplies,
onCommentCreate
} = this.props;
if (!currentUser) {
throw getBadUserError();
}
const successCallback = comment => {
onCommentCreate(comment);
this.feedSuccessCallback();
};
if (hasReplies) {
api.getFeedAPI(false).createThreadedComment(file, currentUser, text, successCallback, this.feedErrorCallback);
} else {
api.getFeedAPI(false).createComment(file, currentUser, text, hasMention, successCallback, this.feedErrorCallback);
}
// need to load the pending item
this.fetchFeedItems();
});
/**
* Posts a new reply to the API
*
* @param {string} parentId - The id of the parent item
* @param {CommentFeedItemType} parentType - The type of the parent item
* @param {string} text - The text of reply
* @return {void}
*/
_defineProperty(this, "createReply", (parentId, parentType, text) => {
const {
api,
currentUser,
emitAnnotationReplyCreateEvent,
file
} = this.props;
if (!currentUser) {
throw getBadUserError();
}
const eventRequestId = uniqueId('comment_');
emitAnnotationReplyCreateEvent({
tagged_message: text
}, eventRequestId, parentId, true);
api.getFeedAPI(false).createReply(file, currentUser, parentId, parentType, text, this.createReplySuccessCallback.bind(this, eventRequestId, parentId), this.feedErrorCallback);
// need to load the pending item
this.fetchFeedItems();
});
/**
* Handles a successful creation of a reply
*
* @private
* @param {string} eventRequestId - The id of the parent item
* @param {string} parentId - The id of the reply's parent item
* @param {Comment} reply - The reply comment object
* @return {void}
*/
_defineProperty(this, "createReplySuccessCallback", (eventRequestId, parentId, reply) => {
const {
emitAnnotationReplyCreateEvent
} = this.props;
this.feedSuccessCallback();
emitAnnotationReplyCreateEvent(reply, eventRequestId, parentId);
});
/**
* Deletes an app activity item via the API.
*
* @param {Object} args - A subset of the app activity
* @return void
*/
_defineProperty(this, "deleteAppActivity", ({
id
}) => {
const {
api,
file
} = this.props;
api.getFeedAPI(false).deleteAppActivity(file, id, this.feedSuccessCallback, this.feedErrorCallback);
// need to load the pending item
this.fetchFeedItems();
});
_defineProperty(this, "fetchRepliesForFeedItems", feedItems => {
const {
activeFeedEntryId
} = this.props;
if (!activeFeedEntryId) {
return;
}
this.getActiveFeedEntryData(feedItems).then(({
id,
type
}) => {
if (!id || !type || this.isActiveEntryInFeed(feedItems, activeFeedEntryId) || !this.isItemTypeComment(type)) {
return Promise.resolve(feedItems);
}
const parentType = type === FEED_ITEM_TYPE_COMMENT ? FEED_ITEM_TYPE_COMMENT : FEED_ITEM_TYPE_ANNOTATION;
return this.getFeedItemsWithReplies(feedItems, id, parentType);
}).then(updatedItems => this.fetchFeedItemsSuccessCallback(updatedItems)).catch(error => this.fetchFeedItemsErrorCallback(feedItems, [error]));
});
/**
* Handles a successful feed API fetch
*
* @private
* @param {Array} feedItems - the feed items
* @return {void}
*/
_defineProperty(this, "fetchFeedItemsSuccessCallback", feedItems => {
const {
file: {
id: fileId
},
logger
} = this.props;
mark(MARK_NAME_DATA_READY);
// Only emit metric if has >1 activity feed items (there should always at least be the current version)
if (feedItems.length > 1) {
logger.onDataReadyMetric({
endMarkName: MARK_NAME_DATA_READY,
startMarkName: MARK_NAME_DATA_LOADING
}, fileId);
}
this.setState({
feedItems,
activityFeedError: undefined
});
});
/**
* Logs diff between UAA and v2 API data
*
* @param {{}[]} responseParity array of aggragated responses from UAA and v2
* @param {{}} parsedDataParity parsed data from UAA and v2
* @return {void}
*/
_defineProperty(this, "logAPIParity", parityData => {
const {
logger
} = this.props;
logger.onPreviewMetric({
parityData,
type: METRIC_TYPE_UAA_PARITY_METRIC
});
});
/**
* Handles a failed feed item fetch
*
* @private
* @param {Error} e - API error
* @return {void}
*/
_defineProperty(this, "fetchFeedItemsErrorCallback", (feedItems, errors) => {
const {
onError
} = this.props;
this.setState({
feedItems,
activityFeedError: activityFeedInlineError
});
if (Array.isArray(errors) && errors.length) {
onError(new Error('Fetch feed items error'), ERROR_CODE_FETCH_ACTIVITY, {
showNotification: true,
errors: errors.map(({
code
}) => code)
});
}
});
_defineProperty(this, "getCommentFeedItemWithReplies", (feedItem, replies) => _objectSpread(_objectSpread({}, feedItem), {}, {
replies
}));
_defineProperty(this, "getFeedItemsWithReplies", (feedItems, id, type) => {
const {
api,
file
} = this.props;
return new Promise((resolve, reject) => {
if (!id || !type) {
resolve(feedItems);
return;
}
api.getFeedAPI(false).fetchReplies(file, id, type, replies => {
const updatedFeedItems = feedItems.map(item => {
if (item.id === id && this.isItemTypeComment(item.type)) {
if (item.type === FEED_ITEM_TYPE_ANNOTATION) {
return this.getCommentFeedItemWithReplies(item, replies);
}
if (item.type === FEED_ITEM_TYPE_COMMENT) {
return this.getCommentFeedItemWithReplies(item, replies);
}
}
return item;
});
resolve(updatedFeedItems);
}, error => {
reject(error);
});
});
});
/**
* Network error callback
*
* @private
* @param {Error} error - Error object
* @param {Error} code - the code for the error
* @param {Object} contextInfo - the context info for the error
* @return {void}
*/
_defineProperty(this, "errorCallback", (error, code, contextInfo = {}) => {
/* eslint-disable no-console */
console.error(error);
/* eslint-enable no-console */
// eslint-disable-next-line react/prop-types
this.props.onError(error, code, contextInfo);
});
/**
* File approver contacts fetch success callback
*
* @private
* @param {BoxItemCollection} collaborators - Collaborators response data
* @return {void}
*/
_defineProperty(this, "getApproverContactsSuccessCallback", collaborators => {
const {
entries
} = collaborators;
this.setState({
approverSelectorContacts: entries
});
});
/**
* File @mention contacts fetch success callback
*
* @private
* @param {BoxItemCollection} collaborators - Collaborators response data
* @return {void}
*/
_defineProperty(this, "getMentionContactsSuccessCallback", collaborators => {
const {
entries
} = collaborators;
this.setState({
contactsLoaded: false
}, () => this.setState({
contactsLoaded: true,
mentionSelectorContacts: entries
}));
});
/**
* Fetches file @mention's with groups
*
* @private
* @param {string} searchStr - Search string to filter file collaborators by
* @return {void}
*/
_defineProperty(this, "getApprover", debounce(searchStr => {
const {
file,
api
} = this.props;
api.getFileCollaboratorsAPI(false).getCollaboratorsWithQuery(file.id, this.getApproverContactsSuccessCallback, this.errorCallback, searchStr, {
includeGroups: true,
respectHiddenCollabs: true
});
}, DEFAULT_COLLAB_DEBOUNCE));
/**
* Fetches file @mention's
*
* @private
* @param {string} searchStr - Search string to filter file collaborators by
* @return {void}
*/
_defineProperty(this, "getMention", debounce(searchStr => {
const {
file,
api
} = this.props;
api.getFileCollaboratorsAPI(false).getCollaboratorsWithQuery(file.id, this.getMentionContactsSuccessCallback, this.errorCallback, searchStr);
}, DEFAULT_COLLAB_DEBOUNCE));
/**
* Returns feed item based on the item id
*
* @param {FeedItems} feedItems - the feed items
* @param {string} itemId - feed item id
* @return {FeedItem | undefined}
*/
_defineProperty(this, "getFocusableFeedItemById", (feedItems, itemId) => {
if (!itemId) {
return undefined;
}
return feedItems.find(({
id,
type
}) => id === itemId && this.isItemTypeFocusable(type));
});
/**
* Returns parent feed item based on the reply id
*
* @param {FeedItems} feedItems - the feed items
* @param {string} replyId - feed item's reply id
* @return {FeedItem | undefined}
*/
_defineProperty(this, "getCommentFeedItemByReplyId", (feedItems, replyId) => {
if (!replyId) {
return undefined;
}
return feedItems.find(item => {
if (item.type !== FEED_ITEM_TYPE_ANNOTATION && item.type !== FEED_ITEM_TYPE_COMMENT || !item.replies) {
return false;
}
return item.replies.some(({
id
}) => id === replyId);
});
});
/**
* Returns true if item (based on given item id) is found within feed items or its replies and it, or its parent, can be active (focusable)
*
* @param {FeedItems} feedItems - the feed items
* @param {string} itemId - feed item id
* @return {boolean}
*/
_defineProperty(this, "isActiveEntryInFeed", (feedItems, itemId) => !!(this.getFocusableFeedItemById(feedItems, itemId) || this.getCommentFeedItemByReplyId(feedItems, itemId)));
_defineProperty(this, "isItemTypeFocusable", type => type === FEED_ITEM_TYPE_ANNOTATION || type === FEED_ITEM_TYPE_COMMENT || type === FEED_ITEM_TYPE_TASK);
_defineProperty(this, "isItemTypeComment", type => type === FEED_ITEM_TYPE_ANNOTATION || type === FEED_ITEM_TYPE_COMMENT);
/**
* Returns active entry data (id, type) based on the activeFeedEntryId and activeFeedEntryType values
* (it can be existing item or parent if the active entry id belongs to a reply)
*
* @param {FeedItems} feedItems - the feed items
* @return {Promise<{ id: string, type?: FocusableFeedItemType }>}
*/
_defineProperty(this, "getActiveFeedEntryData", feedItems => {
const {
activeFeedEntryId,
activeFeedEntryType,
api,
file
} = this.props;
return new Promise((resolve, reject) => {
if (!activeFeedEntryId || !activeFeedEntryType || !this.isItemTypeFocusable(activeFeedEntryType)) {
resolve({});
return;
}
// Check if the active entry is a first level Feed item
const firstLevelItem = this.getFocusableFeedItemById(feedItems, activeFeedEntryId);
if (firstLevelItem) {
const {
id,
type
} = firstLevelItem;
resolve({
id,
type
});
return;
}
// Check if the active entry is within replies of any first level Feed items
const firstLevelItemWithActiveReply = this.getCommentFeedItemByReplyId(feedItems, activeFeedEntryId);
if (firstLevelItemWithActiveReply) {
const {
id,
type
} = firstLevelItemWithActiveReply;
resolve({
id,
type
});
return;
}
// If the active entry could not be found within feed items, it's most likely a reply that
// is not yet visible in feed and we need to fetch its data in order to find parent
api.getFeedAPI(false).fetchThreadedComment(file, activeFeedEntryId, ({
parent
}) => {
const parentItem = this.getFocusableFeedItemById(feedItems, parent?.id);
const {
id,
type
} = parentItem || {};
resolve({
id,
type
});
}, error => {
if (error.status === 404) {
resolve({});
} else {
reject(error);
}
});
});
});
/**
* Fetches replies (comments) of a comment or annotation
*
* @param {string} id - id of the feed item
* @param {CommentFeedItemType} type - type of the feed item
* @return {void}
*/
_defineProperty(this, "getReplies", (id, type) => {
const {
api,
file
} = this.props;
api.getFeedAPI(false).fetchReplies(file, id, type, this.feedSuccessCallback, this.feedErrorCallback);
// need to load the pending item
this.fetchFeedItems();
});
/**
* Gets the user avatar URL
*
* @param {string} userId the user id
* @param {string} fileId the file id
* @return the user avatar URL string for a given user with access token attached
*/
_defineProperty(this, "getAvatarUrl", async userId => {
const {
file,
api
} = this.props;
return api.getUsersAPI(false).getAvatarUrlWithAccessToken(userId, file.id);
});
_defineProperty(this, "handleAnnotationSelect", annotation => {
const {
file_version,
id: nextActiveAnnotationId
} = annotation;
const {
emitActiveAnnotationChangeEvent,
file,
getAnnotationsMatchPath,
getAnnotationsPath,
history,
internalSidebarNavigation,
internalSidebarNavigationHandler,
location,
onAnnotationSelect,
routerDisabled
} = this.props;
const annotationFileVersionId = getProp(file_version, 'id');
const currentFileVersionId = getProp(file, 'file_version.id');
let selectedFileVersionId = currentFileVersionId;
if (routerDisabled && internalSidebarNavigation) {
selectedFileVersionId = getProp(internalSidebarNavigation, 'fileVersionId', currentFileVersionId);
} else {
const match = getAnnotationsMatchPath(location);
selectedFileVersionId = getProp(match, 'params.fileVersionId', currentFileVersionId);
}
emitActiveAnnotationChangeEvent(nextActiveAnnotationId);
if (annotationFileVersionId && annotationFileVersionId !== selectedFileVersionId) {
if (routerDisabled && internalSidebarNavigationHandler) {
internalSidebarNavigationHandler({
sidebar: ViewType.ACTIVITY,
activeFeedEntryId: nextActiveAnnotationId,
activeFeedEntryType: FeedEntryType.ANNOTATIONS,
fileVersionId: annotationFileVersionId
});
} else {
history.push(getAnnotationsPath(annotationFileVersionId, nextActiveAnnotationId));
}
const deferScrollToOnload = annotation?.target?.location?.type === 'frame';
onAnnotationSelect(annotation, deferScrollToOnload);
} else {
onAnnotationSelect(annotation);
}
});
_defineProperty(this, "handleItemsFiltered", status => {
const {
onFilterChange
} = this.props;
this.setState({
feedItemsStatusFilter: status
});
onFilterChange(status);
});
_defineProperty(this, "getFilteredFeedItems", () => {
const {
feedItems,
feedItemsStatusFilter
} = this.state;
if (!feedItems || !feedItemsStatusFilter || feedItemsStatusFilter === ACTIVITY_FILTER_OPTION_ALL) {
return feedItems;
}
// Filter is completed on two properties (status and type) because filtering on comments (resolved vs. unresolved)
// requires looking at item status to see if it is open or resolved. To filter all tasks, we need to look at the
// item type. Item type is also used to keep versions in the feed. Task also has a status but it's status will be
// "NOT_STARTED" or "COMPLETED" so it will not conflict with comment's status.
return feedItems.filter(item => {
return item.status === feedItemsStatusFilter || item.type === FEED_ITEM_TYPE_VERSION || item.type === feedItemsStatusFilter;
});
});
_defineProperty(this, "onTaskModalClose", () => {
this.setState({
approverSelectorContacts: []
});
});
_defineProperty(this, "renderAddTaskButton", () => {
const {
isDisabled,
hasTasks,
internalSidebarNavigation,
internalSidebarNavigationHandler,
routerDisabled
} = this.props;
const {
approverSelectorContacts
} = this.state;
const {
getApprover,
getAvatarUrl,
createTask,
onTaskModalClose
} = this;
if (!hasTasks) {
return null;
}
return /*#__PURE__*/React.createElement(AddTaskButton, {
internalSidebarNavigation: internalSidebarNavigation,
internalSidebarNavigationHandler: internalSidebarNavigationHandler,
isDisabled: isDisabled,
onTaskModalClose: onTaskModalClose,
routerDisabled: routerDisabled,
taskFormProps: {
approvers: [],
approverSelectorContacts,
completionRule: TASK_COMPLETION_RULE_ALL,
createTask,
getApproverWithQuery: getApprover,
getAvatarUrl,
id: '',
message: ''
}
});
});
_defineProperty(this, "renderActivitySidebarFilter", () => {
const {
features,
hasTasks
} = this.props;
const {
feedItemsStatusFilter
} = this.state;
const shouldShowActivityFeedFilter = isFeatureEnabled(features, 'activityFeed.filter.enabled');
const shouldShowAdditionalFilterOptions = isFeatureEnabled(features, 'activityFeed.newThreadedReplies.enabled');
if (!shouldShowActivityFeedFilter) {
return null;
}
const activityFilterOptions = [ACTIVITY_FILTER_OPTION_ALL, ACTIVITY_FILTER_OPTION_UNRESOLVED];
if (shouldShowAdditionalFilterOptions) {
// Determine which filter options to show based on what activity types are available in current context
activityFilterOptions.push(ACTIVITY_FILTER_OPTION_RESOLVED);
if (hasTasks) {
activityFilterOptions.push(ACTIVITY_FILTER_OPTION_TASKS);
}
}
return /*#__PURE__*/React.createElement(ActivitySidebarFilter, {
activityFilterOptions: activityFilterOptions,
feedItemType: feedItemsStatusFilter,
onFeedItemTypeClick: selectedStatus => {
this.handleItemsFiltered(selectedStatus);
}
});
});
_defineProperty(this, "renderActions", () => /*#__PURE__*/React.createElement(React.Fragment, null, this.renderActivitySidebarFilter(), this.renderAddTaskButton()));
_defineProperty(this, "renderTitle", () => {
const {
features
} = this.props;
const shouldHideTitle = isFeatureEnabled(features, 'activityFeed.filter.enabled');
if (shouldHideTitle) {
return null;
}
return /*#__PURE__*/React.createElement(FormattedMessage, messages.sidebarActivityTitle);
});
const {
logger: _logger
} = this.props;
mark(MARK_NAME_DATA_LOADING);
_logger.onReadyMetric({
endMarkName: MARK_NAME_JS_READY
});
this.state = {};
}
componentDidMount() {
this.fetchFeedItems(true);
}
deleteAnnotationSuccess(id) {
const {
emitAnnotationRemoveEvent
} = this.props;
this.feedSuccessCallback();
emitAnnotationRemoveEvent(id);
}
/**
* Fetches the feed items for the sidebar
*
* @param {boolean} shouldRefreshCache true if the cache should be refreshed
* @param {boolean} shouldDestroy true if the api factory should be destroyed
*/
fetchFeedItems(shouldRefreshCache = false, shouldDestroy = false) {
const {
activeFeedEntryId,
activeFeedEntryType,
api,
file,
features,
hasReplies: shouldShowReplies,
hasTasks: shouldShowTasks,
hasVersions: shouldShowVersions
} = this.props;
const shouldFetchReplies = shouldRefreshCache && shouldShowReplies && activeFeedEntryId && activeFeedEntryType === FEED_ITEM_TYPE_COMMENT;
const shouldShowAppActivity = isFeatureEnabled(features, 'activityFeed.appActivity.enabled');
const shouldShowAnnotations = isFeatureEnabled(features, 'activityFeed.annotations.enabled');
const shouldUseUAA = isFeatureEnabled(features, 'activityFeed.uaaIntegration.enabled');
api.getFeedAPI(shouldDestroy).feedItems(file, shouldRefreshCache, shouldFetchReplies ? this.fetchRepliesForFeedItems : this.fetchFeedItemsSuccessCallback, this.fetchFeedItemsErrorCallback, this.errorCallback, {
shouldShowAnnotations,
shouldShowAppActivity,
shouldShowReplies,
shouldShowTasks,
shouldShowVersions,
shouldUseUAA
}, shouldUseUAA ? this.logAPIParity : undefined);
}
getActiveCommentPath(commentId) {
if (!commentId) {
return '/activity';
}
return generatePath('/:sidebar/comments/:commentId?', {
sidebar: 'activity',
commentId
});
}
refresh(shouldRefreshCache = true) {
this.fetchFeedItems(shouldRefreshCache);
}
render() {
const {
activeFeedEntryId,
activeFeedEntryType,
currentUser,
currentUserError,
elementId,
features,
file,
hasReplies,
hasVersions,
isDisabled = false,
onVersionHistoryClick,
getUserProfileUrl,
onTaskView
} = this.props;
const {
activityFeedError,
approverSelectorContacts,
contactsLoaded,
mentionSelectorContacts
} = this.state;
const isNewThreadedRepliesEnabled = isFeatureEnabled(features, 'activityFeed.newThreadedReplies.enabled');
const shouldUseUAA = isFeatureEnabled(features, 'activityFeed.uaaIntegration.enabled');
return /*#__PURE__*/React.createElement(SidebarContent, {
actions: this.renderActions(),
className: classNames('bcs-activity', {
'bcs-activity--full': hasReplies
}),
elementId: elementId,
sidebarView: SIDEBAR_VIEW_ACTIVITY,
title: this.renderTitle()
}, /*#__PURE__*/React.createElement(ActivityFeed, {
activeFeedEntryId: activeFeedEntryId,
activeFeedEntryType: activeFeedEntryType,
activityFeedError: activityFeedError,
approverSelectorContacts: approverSelectorContacts,
currentUser: currentUser,
currentUserError: currentUserError,
feedItems: this.getFilteredFeedItems(),
file: file,
getApproverWithQuery: this.getApprover,
getAvatarUrl: this.getAvatarUrl,
getMentionWithQuery: this.getMention,
getUserProfileUrl: getUserProfileUrl,
hasNewThreadedReplies: isNewThreadedRepliesEnabled,
hasReplies: hasReplies,
hasVersions: hasVersions,
isDisabled: isDisabled,
mentionSelectorContacts: mentionSelectorContacts,
contactsLoaded: contactsLoaded,
onAnnotationDelete: this.handleAnnotationDelete,
onAnnotationEdit: this.handleAnnotationEdit,
onAnnotationSelect: this.handleAnnotationSelect,
onAnnotationStatusChange: this.handleAnnotationStatusChange,
onAppActivityDelete: this.deleteAppActivity,
onCommentCreate: this.createComment,
onCommentDelete: this.deleteComment,
onCommentUpdate: this.updateComment,
onHideReplies: this.updateReplies,
onReplyCreate: this.createReply,
onReplyDelete: this.deleteReply,
onReplyUpdate: this.updateReply,
onShowReplies: this.getReplies,
onTaskAssignmentUpdate: this.updateTaskAssignment,
onTaskCreate: this.createTask,
onTaskDelete: this.deleteTask,
onTaskModalClose: this.onTaskModalClose,
onTaskUpdate: this.updateTask,
onTaskView: onTaskView,
onVersionHistoryClick: onVersionHistoryClick,
shouldUseUAA: shouldUseUAA
}));
}
}
_defineProperty(ActivitySidebar, "defaultProps", {
annotatorState: {},
emitActiveAnnotationChangeEvent: noop,
emitAnnotationRemoveEvent: noop,
emitAnnotationReplyCreateEvent: noop,
emitAnnotationReplyDeleteEvent: noop,
emitAnnotationReplyUpdateEvent: noop,
emitAnnotationUpdateEvent: noop,
getAnnotationsMatchPath: noop,
getAnnotationsPath: noop,
hasReplies: false,
hasTasks: true,
hasVersions: true,
isDisabled: false,
onAnnotationSelect: noop,
onCommentCreate: noop,
onCommentDelete: noop,
onCommentUpdate: noop,
onFilterChange: noop,
onTaskAssignmentUpdate: noop,
onTaskCreate: noop,
onTaskDelete: noop,
onTaskUpdate: noop,
onVersionChange: noop,
onVersionHistoryClick: noop
});
export { ActivitySidebar as ActivitySidebarComponent };
export default flow([withLogger(ORIGIN_ACTIVITY_SIDEBAR), withErrorBoundary(ORIGIN_ACTIVITY_SIDEBAR), withAPIContext, withFeatureConsumer, withAnnotatorContext, withRouterAndRef])(ActivitySidebar);
//# sourceMappingURL=ActivitySidebar.js.map