Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 63 additions & 16 deletions src/elements/content-sidebar/ActivitySidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import AddTaskButton from './AddTaskButton';
import API from '../../api';
import messages from '../common/messages';
import SidebarContent from './SidebarContent';
import { getParsedFileActivitiesResponse } from '../../api/Feed';
import { WithAnnotatorContextProps, withAnnotatorContext } from '../common/annotator-context';
import { EVENT_DATA_READY, EVENT_JS_READY } from '../common/logger/constants';
import { getBadUserError } from '../../utils/error';
Expand All @@ -39,6 +40,11 @@ import {
FEED_ITEM_TYPE_COMMENT,
FEED_ITEM_TYPE_TASK,
FEED_ITEM_TYPE_VERSION,
FILE_ACTIVITY_TYPE_ANNOTATION,
FILE_ACTIVITY_TYPE_APP_ACTIVITY,
FILE_ACTIVITY_TYPE_COMMENT,
FILE_ACTIVITY_TYPE_TASK,
FILE_ACTIVITY_TYPE_VERSION,
ORIGIN_ACTIVITY_SIDEBAR,
SIDEBAR_VIEW_ACTIVITY,
TASK_COMPLETION_RULE_ALL,
Expand Down Expand Up @@ -1108,26 +1114,61 @@ class ActivitySidebar extends React.PureComponent<Props, State> {

handleItemsFiltered = (status?: ActivityFilterItemType) => {
const { onFilterChange } = this.props;

this.setState({ feedItemsStatusFilter: status });
this.setState({ feedItemsStatusFilter: status }, this.getFilteredFeedItems);
onFilterChange(status);
};

getFilters = (status?: ActivityFilterItemType) => {
let filters = [
FILE_ACTIVITY_TYPE_ANNOTATION,
FILE_ACTIVITY_TYPE_APP_ACTIVITY,
FILE_ACTIVITY_TYPE_COMMENT,
FILE_ACTIVITY_TYPE_TASK,
FILE_ACTIVITY_TYPE_VERSION,
];

if (status === ACTIVITY_FILTER_OPTION_UNRESOLVED || status === ACTIVITY_FILTER_OPTION_RESOLVED) {
filters = [FILE_ACTIVITY_TYPE_ANNOTATION, FILE_ACTIVITY_TYPE_COMMENT, FILE_ACTIVITY_TYPE_VERSION];
}

if (status === FILE_ACTIVITY_TYPE_TASK) {
filters = [FILE_ACTIVITY_TYPE_TASK, FILE_ACTIVITY_TYPE_VERSION];
}

return filters;
};

getFilteredFeedItems = (): FeedItems | typeof undefined => {
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
const { api, file } = this.props;
const { permissions = {} } = file;
const feedAPI = api.getFeedAPI(false);
feedAPI.file = file;

// Clear the feedItems before fetching new data to trigger loading indicator
this.setState({ feedItems: undefined });

// Get the file activity filters
const filters = this.getFilters(feedItemsStatusFilter);

feedAPI.fetchFileActivities(permissions, filters, true).then(items => {
const parsedFeedItems = getParsedFileActivitiesResponse(items);

if (!feedItems || !feedItemsStatusFilter || feedItemsStatusFilter === ACTIVITY_FILTER_OPTION_ALL) {
return this.setState({ feedItems: parsedFeedItems });
}

// 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. 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.
const filteredActivity = parsedFeedItems.filter(
item =>
item.status === feedItemsStatusFilter ||
item.type === FEED_ITEM_TYPE_VERSION ||
item.type === feedItemsStatusFilter,
);

return this.setState({ feedItems: filteredActivity });
});
};

Expand Down Expand Up @@ -1234,7 +1275,13 @@ class ActivitySidebar extends React.PureComponent<Props, State> {
getUserProfileUrl,
onTaskView,
} = this.props;
const { activityFeedError, approverSelectorContacts, contactsLoaded, mentionSelectorContacts } = this.state;
const {
activityFeedError,
approverSelectorContacts,
contactsLoaded,
feedItems,
mentionSelectorContacts,
} = this.state;
const isNewThreadedRepliesEnabled = isFeatureEnabled(features, 'activityFeed.newThreadedReplies.enabled');
const shouldUseUAA = isFeatureEnabled(features, 'activityFeed.uaaIntegration.enabled');

Expand All @@ -1253,7 +1300,7 @@ class ActivitySidebar extends React.PureComponent<Props, State> {
approverSelectorContacts={approverSelectorContacts}
currentUser={currentUser}
currentUserError={currentUserError}
feedItems={this.getFilteredFeedItems()}
feedItems={feedItems}
file={file}
getApproverWithQuery={this.getApprover}
getAvatarUrl={this.getAvatarUrl}
Expand Down
89 changes: 62 additions & 27 deletions src/elements/content-sidebar/__tests__/ActivitySidebar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import cloneDeep from 'lodash/cloneDeep';
import { ActivitySidebarComponent, activityFeedInlineError } from '../ActivitySidebar';
import ActivitySidebarFilter from '../ActivitySidebarFilter';
import { filterableActivityFeedItems, formattedReplies } from '../fixtures';
import { FEED_ITEM_TYPE_COMMENT } from '../../../constants';
import {
FEED_ITEM_TYPE_COMMENT,
FILE_ACTIVITY_TYPE_ANNOTATION,
FILE_ACTIVITY_TYPE_APP_ACTIVITY,
FILE_ACTIVITY_TYPE_COMMENT,
FILE_ACTIVITY_TYPE_TASK,
FILE_ACTIVITY_TYPE_VERSION,
} from '../../../constants';

jest.mock('lodash/debounce', () => jest.fn(i => i));
jest.mock('lodash/uniqueId', () => () => 'uniqueId');

// const mockReplace = jest.fn();
// jest.mock('lodash/uniqueId', () => () => 'uniqueId');

const userError = 'Bad box user!';

describe('elements/content-sidebar/ActivitySidebar', () => {
Expand All @@ -26,6 +30,7 @@ describe('elements/content-sidebar/ActivitySidebar', () => {
deleteTaskNew: jest.fn(),
deleteThreadedComment: jest.fn(),
feedItems: jest.fn(),
fetchFileActivities: jest.fn(),
fetchReplies: jest.fn(),
fetchThreadedComment: jest.fn(),
updateAnnotation: jest.fn(),
Expand Down Expand Up @@ -1500,35 +1505,58 @@ describe('elements/content-sidebar/ActivitySidebar', () => {
});
});

describe('getFilters()', () => {
test.each`
status | expected
${undefined} | ${[FILE_ACTIVITY_TYPE_ANNOTATION, FILE_ACTIVITY_TYPE_APP_ACTIVITY, FILE_ACTIVITY_TYPE_COMMENT, FILE_ACTIVITY_TYPE_TASK, FILE_ACTIVITY_TYPE_VERSION]}
${'open'} | ${[FILE_ACTIVITY_TYPE_ANNOTATION, FILE_ACTIVITY_TYPE_COMMENT, FILE_ACTIVITY_TYPE_VERSION]}
${'resolved'} | ${[FILE_ACTIVITY_TYPE_ANNOTATION, FILE_ACTIVITY_TYPE_COMMENT, FILE_ACTIVITY_TYPE_VERSION]}
${'task'} | ${[FILE_ACTIVITY_TYPE_TASK, FILE_ACTIVITY_TYPE_VERSION]}
`('should return the correct filters when status equals to $status', ({ status, expected }) => {
const wrapper = getWrapper();
const instance = wrapper.instance();

instance.setState({
feedItemsStatusFilter: status,
});

expect(instance.getFilters(status)).toMatchObject(expected);
});
});

describe('getFilteredFeedItems()', () => {
const {
annotationOpen: expectedAnnotationOpen,
annotationResolved: expectedAnnotationResolved,
commentOpen: expectedCommentOpen,
commentResolved: expectedCommentResolved,
taskItem: expectedTaskItem,
versionItem: expectedVersionItem,
annotationOpen,
annotationResolved,
commentOpen,
commentResolved,
taskItem,
versionItem,
} = filterableActivityFeedItems;

const expectedAnnotationOpen = cloneDeep(annotationOpen.source.annotation);
const expectedAnnotationResolved = cloneDeep(annotationResolved.source.annotation);
const expectedCommentOpen = cloneDeep(commentOpen.source.comment);
const expectedCommentResolved = cloneDeep(commentResolved.source.comment);
const expectedTaskItem = { assigned_to: cloneDeep(taskItem.source.task.assigned_to) };
const expectedVersionItem = cloneDeep(versionItem.source.versions);

test.each`
status | expected
${undefined} | ${[expectedAnnotationOpen, expectedAnnotationResolved, expectedCommentOpen, expectedCommentResolved, expectedTaskItem, expectedVersionItem]}
${'open'} | ${[expectedAnnotationOpen, expectedCommentOpen, expectedVersionItem]}
${'resolved'} | ${[expectedAnnotationResolved, expectedCommentResolved, expectedVersionItem]}
${'task'} | ${[expectedTaskItem, expectedVersionItem]}
status | entries | expected
${undefined} | ${[annotationOpen, annotationResolved, commentOpen, commentResolved, taskItem, versionItem]} | ${[expectedVersionItem, expectedTaskItem, expectedCommentResolved, expectedCommentOpen, expectedAnnotationResolved, expectedAnnotationOpen]}
${'open'} | ${[annotationOpen, annotationResolved, commentOpen, commentResolved, versionItem]} | ${[expectedVersionItem, expectedCommentOpen, expectedAnnotationOpen]}
${'resolved'} | ${[annotationOpen, annotationResolved, commentOpen, commentResolved, versionItem]} | ${[expectedVersionItem, expectedCommentResolved, expectedAnnotationResolved]}
${'task'} | ${[taskItem, versionItem]} | ${[expectedVersionItem, expectedTaskItem]}
`(
'should filter feed items of type "comment" or "annotation" based on status equal to $status',
({ status, expected }) => {
const {
annotationOpen,
annotationResolved,
commentOpen,
commentResolved,
taskItem,
versionItem,
} = cloneDeep(filterableActivityFeedItems);
'should correctly filter feed items when status is equal to $status',
async ({ status, entries, expected }) => {
const wrapper = getWrapper();
const instance = wrapper.instance();

api.getFeedAPI().fetchFileActivities = jest
.fn()
.mockImplementationOnce(() => Promise.resolve({ entries }));

instance.setState({
feedItems: [
annotationOpen,
Expand All @@ -1542,7 +1570,10 @@ describe('elements/content-sidebar/ActivitySidebar', () => {
instance.setState({
feedItemsStatusFilter: status,
});
expect(instance.getFilteredFeedItems()).toMatchObject(expected);

await instance.getFilteredFeedItems();

expect(instance.state.feedItems).toMatchObject(expected);
},
);
});
Expand All @@ -1561,8 +1592,12 @@ describe('elements/content-sidebar/ActivitySidebar', () => {
const wrapper = getWrapper({ onFilterChange: mockOnFilterChange });
const instance = wrapper.instance();
instance.setState = jest.fn();
instance.getFilteredFeedItems = jest.fn();
instance.handleItemsFiltered(status);
expect(instance.setState).toBeCalledWith({ feedItemsStatusFilter: expected });
expect(instance.setState).toBeCalledWith(
{ feedItemsStatusFilter: expected },
instance.getFilteredFeedItems,
);
expect(mockOnFilterChange).toBeCalledWith(expected);
},
);
Expand Down
Loading