UNPKG

box-ui-elements

Version:
1,145 lines (1,126 loc) • 40 kB
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