close
Skip to content

Commit 76e4010

Browse files
authored
feat(threaded-replies): Update activity filter to support more options (#3280)
* feat(threaded-replies): Update activity filter to support more options * feat(threaded-replies): Fix test * feat(threaded-replies): Arrange props alphabetically * feat(threaded-replies): Update to differentiate status and options * feat(threaded-replies): Add unit tests for feature * feat(threaded-replies): More clearly name filter options * feat(threaded-replies): Update message descriptions * feat(threaded-replies): Update similar tests to use each operator * feat(threaded-replies): Remove passing non-existant prop * feat(threaded-replies): Context based filter options Updated activity sidebar to use the context of what types of comments and tasks it has to determine what filter options to allow * feat(threaded-replies): Updates to sidebar filter logic * feat(threaded-replies): Address naming and filtering * feat(threaded-replies): Extract only comments into function * feat(threaded-replies): Move array to variable
1 parent 006e33c commit 76e4010

8 files changed

Lines changed: 324 additions & 74 deletions

File tree

‎i18n/en-US.properties‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,16 @@ be.activitySidebar.activityFeed.replyInThread = Reply in thread
2020
be.activitySidebar.activityFeed.showReplies = See {repliesToLoadCount, plural, one {# reply} other {# replies}}
2121
# Text to show when a task no longer exists
2222
be.activitySidebar.activityFeed.taskMissingError = This task no longer exists
23-
# One of the dropdown options to select in order to filter comments
24-
be.activitySidebarFilter.status.all = All Comments
25-
# One of the dropdown options to select in order to filter comments
23+
# Dropdown option for filtering all activity from activity list
24+
be.activitySidebarFilter.status.allActivity = All Activity
25+
# Dropdown option for filtering all comments from comments list
26+
be.activitySidebarFilter.status.allComments = All Comments
27+
# Dropdown option for filtering unresolved comments from activity or comments list
2628
be.activitySidebarFilter.status.open = Unresolved Comments
29+
# Dropdown option for filtering resolved comments from activity or comments list
30+
be.activitySidebarFilter.status.resolved = Resolved Comments
31+
# Dropdown option for filtering tasks from activity list
32+
be.activitySidebarFilter.status.tasks = Tasks
2733
# Label for add action
2834
be.add = Add
2935
# Text to display when app is disabled by applied access policy

‎src/common/types/feed.js‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// @flow strict
22
import type { MessageDescriptor } from 'react-intl';
33
import {
4+
ACTIVITY_FILTER_OPTION_ALL,
5+
ACTIVITY_FILTER_OPTION_RESOLVED,
6+
ACTIVITY_FILTER_OPTION_TASKS,
7+
ACTIVITY_FILTER_OPTION_UNRESOLVED,
48
COMMENT_STATUS_OPEN,
59
COMMENT_STATUS_RESOLVED,
610
FEED_ITEM_TYPE_ANNOTATION,
@@ -150,8 +154,22 @@ type ActionItemError = {
150154
title: MessageDescriptor,
151155
};
152156

157+
type ActivityFilterOption =
158+
| typeof ACTIVITY_FILTER_OPTION_ALL
159+
| typeof ACTIVITY_FILTER_OPTION_UNRESOLVED
160+
| typeof ACTIVITY_FILTER_OPTION_RESOLVED
161+
| typeof ACTIVITY_FILTER_OPTION_TASKS;
162+
163+
type ActivityFilterItemType =
164+
| typeof ACTIVITY_FILTER_OPTION_ALL
165+
| typeof COMMENT_STATUS_OPEN
166+
| typeof COMMENT_STATUS_RESOLVED
167+
| typeof FEED_ITEM_TYPE_TASK;
168+
153169
export type {
154170
ActionItemError,
171+
ActivityFilterItemType,
172+
ActivityFilterOption,
155173
ActivityTemplateItem,
156174
Annotation,
157175
AnnotationPermission,

‎src/constants.js‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,12 @@ export const TASK_MAX_GROUP_ASSIGNEES: 250 = 250;
463463
export const COMMENT_STATUS_OPEN: 'open' = 'open';
464464
export const COMMENT_STATUS_RESOLVED: 'resolved' = 'resolved';
465465

466+
/* ------------------ Activity Filter Options ------------------ */
467+
export const ACTIVITY_FILTER_OPTION_ALL: 'all' = 'all';
468+
export const ACTIVITY_FILTER_OPTION_RESOLVED: 'resolved' = 'resolved';
469+
export const ACTIVITY_FILTER_OPTION_TASKS: 'tasks' = 'tasks';
470+
export const ACTIVITY_FILTER_OPTION_UNRESOLVED: 'open' = 'open';
471+
466472
/* ----------------- Theme ---------------------------*/
467473
export const THEME_VERY_DARK = 'vDark';
468474
export const THEME_DARK = 'dark';

‎src/elements/content-sidebar/ActivitySidebar.js‎

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ import { withLogger } from '../common/logger';
2929
import { withRouterAndRef } from '../common/routing';
3030
import ActivitySidebarFilter from './ActivitySidebarFilter';
3131
import {
32+
ACTIVITY_FILTER_OPTION_ALL,
33+
ACTIVITY_FILTER_OPTION_RESOLVED,
34+
ACTIVITY_FILTER_OPTION_TASKS,
35+
ACTIVITY_FILTER_OPTION_UNRESOLVED,
3236
DEFAULT_COLLAB_DEBOUNCE,
3337
ERROR_CODE_FETCH_ACTIVITY,
3438
FEED_ITEM_TYPE_ANNOTATION,
@@ -49,6 +53,8 @@ import type {
4953
import type {
5054
Annotation,
5155
AnnotationPermission,
56+
ActivityFilterItemType,
57+
ActivityFilterOption,
5258
BoxCommentPermission,
5359
Comment,
5460
CommentFeedItemType,
@@ -93,7 +99,7 @@ type PropsWithoutContext = {
9399
hasSidebarInitialized?: boolean,
94100
isDisabled: boolean,
95101
onAnnotationSelect: Function,
96-
onFilterChange: (status?: FeedItemStatus) => void,
102+
onFilterChange: (status?: ActivityFilterItemType) => void,
97103
onVersionChange: Function,
98104
onVersionHistoryClick?: Function,
99105
translations?: Translations,
@@ -111,7 +117,7 @@ type State = {
111117
approverSelectorContacts: SelectorItems<UserMini | GroupMini>,
112118
contactsLoaded?: boolean,
113119
feedItems?: FeedItems,
114-
feedItemsStatusFilter?: FeedItemStatus,
120+
feedItemsStatusFilter?: ActivityFilterItemType,
115121
mentionSelectorContacts?: SelectorItems<UserMini>,
116122
};
117123

@@ -1090,7 +1096,7 @@ class ActivitySidebar extends React.PureComponent<Props, State> {
10901096
onAnnotationSelect(annotation);
10911097
};
10921098

1093-
handleItemsFiltered = (status?: FeedItemStatus) => {
1099+
handleItemsFiltered = (status?: ActivityFilterItemType) => {
10941100
const { onFilterChange } = this.props;
10951101

10961102
this.setState({ feedItemsStatusFilter: status });
@@ -1099,11 +1105,19 @@ class ActivitySidebar extends React.PureComponent<Props, State> {
10991105

11001106
getFilteredFeedItems = (): FeedItems | typeof undefined => {
11011107
const { feedItems, feedItemsStatusFilter } = this.state;
1102-
if (!feedItems || !feedItemsStatusFilter) {
1108+
if (!feedItems || !feedItemsStatusFilter || feedItemsStatusFilter === ACTIVITY_FILTER_OPTION_ALL) {
11031109
return feedItems;
11041110
}
1111+
// Filter is completed on two properties (status and type) because filtering on comments (resolved vs. unresolved)
1112+
// requires looking at item status to see if it is open or resolved. To filter all tasks, we need to look at the
1113+
// item type. Item type is also used to keep versions in the feed. Task also has a status but it's status will be
1114+
// "NOT_STARTED" or "COMPLETED" so it will not conflict with comment's status.
11051115
return feedItems.filter(item => {
1106-
return item.status === feedItemsStatusFilter || item.type === FEED_ITEM_TYPE_VERSION;
1116+
return (
1117+
item.status === feedItemsStatusFilter ||
1118+
item.type === FEED_ITEM_TYPE_VERSION ||
1119+
item.type === feedItemsStatusFilter
1120+
);
11071121
});
11081122
};
11091123

@@ -1145,17 +1159,32 @@ class ActivitySidebar extends React.PureComponent<Props, State> {
11451159
};
11461160

11471161
renderActivitySidebarFilter = () => {
1148-
const { features } = this.props;
1162+
const { features, hasTasks } = this.props;
11491163
const { feedItemsStatusFilter } = this.state;
11501164
const shouldShowActivityFeedFilter = isFeatureEnabled(features, 'activityFeed.filter.enabled');
1165+
const shouldShowAdditionalFilterOptions = isFeatureEnabled(features, 'activityFeed.newThreadedReplies.enabled');
11511166

11521167
if (!shouldShowActivityFeedFilter) {
11531168
return null;
11541169
}
1170+
1171+
const activityFilterOptions: ActivityFilterOption[] = [
1172+
ACTIVITY_FILTER_OPTION_ALL,
1173+
ACTIVITY_FILTER_OPTION_UNRESOLVED,
1174+
];
1175+
if (shouldShowAdditionalFilterOptions) {
1176+
// Determine which filter options to show based on what activity types are available in current context
1177+
activityFilterOptions.push(ACTIVITY_FILTER_OPTION_RESOLVED);
1178+
if (hasTasks) {
1179+
activityFilterOptions.push(ACTIVITY_FILTER_OPTION_TASKS);
1180+
}
1181+
}
1182+
11551183
return (
11561184
<ActivitySidebarFilter
1157-
feedItemStatus={feedItemsStatusFilter}
1158-
onFeedItemStatusClick={selectedStatus => {
1185+
activityFilterOptions={activityFilterOptions}
1186+
feedItemType={feedItemsStatusFilter}
1187+
onFeedItemTypeClick={selectedStatus => {
11591188
this.handleItemsFiltered(selectedStatus);
11601189
}}
11611190
/>
@@ -1171,8 +1200,10 @@ class ActivitySidebar extends React.PureComponent<Props, State> {
11711200

11721201
renderTitle = () => {
11731202
const { features } = this.props;
1174-
if (isFeatureEnabled(features, 'activityFeed.filter.enabled')) {
1175-
return undefined;
1203+
const shouldHideTitle = isFeatureEnabled(features, 'activityFeed.filter.enabled');
1204+
1205+
if (shouldHideTitle) {
1206+
return null;
11761207
}
11771208
return <FormattedMessage {...messages.sidebarActivityTitle} />;
11781209
};

‎src/elements/content-sidebar/ActivitySidebarFilter.js‎

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,47 +9,88 @@ import { FormattedMessage } from 'react-intl';
99
import DropdownMenu, { MenuToggle } from '../../components/dropdown-menu';
1010
import PlainButton from '../../components/plain-button';
1111
import messages from './messages';
12-
import { COMMENT_STATUS_OPEN } from '../../constants';
12+
import {
13+
ACTIVITY_FILTER_OPTION_ALL,
14+
ACTIVITY_FILTER_OPTION_RESOLVED,
15+
ACTIVITY_FILTER_OPTION_TASKS,
16+
ACTIVITY_FILTER_OPTION_UNRESOLVED,
17+
COMMENT_STATUS_OPEN,
18+
COMMENT_STATUS_RESOLVED,
19+
FEED_ITEM_TYPE_TASK,
20+
} from '../../constants';
1321
import { Menu, SelectMenuItem } from '../../components/menu';
14-
import type { FeedItemStatus } from '../../common/types/feed';
22+
import type { ActivityFilterItemType, ActivityFilterOption } from '../../common/types/feed';
1523
import './ActivitySidebarFilter.scss';
1624

1725
type ActivitySidebarFilterProps = {
18-
feedItemStatus?: FeedItemStatus,
19-
onFeedItemStatusClick: (status?: FeedItemStatus) => void,
26+
activityFilterOptions: ActivityFilterOption[],
27+
feedItemType?: ActivityFilterItemType,
28+
onFeedItemTypeClick: (status?: ActivityFilterItemType) => void,
2029
};
2130

22-
const ACTIVITY_FEED_ITEM_ALL = 'all';
23-
const ACTIVITY_FEED_ITEM_OPEN = COMMENT_STATUS_OPEN;
24-
25-
const statuses = [ACTIVITY_FEED_ITEM_ALL, ACTIVITY_FEED_ITEM_OPEN];
26-
const statusMap = {
27-
[ACTIVITY_FEED_ITEM_ALL]: { msg: messages.activitySidebarFilterOptionAll, val: undefined },
28-
[ACTIVITY_FEED_ITEM_OPEN]: { msg: messages.activitySidebarFilterOptionOpen, val: COMMENT_STATUS_OPEN },
31+
const filterOptionToStatus = {
32+
[ACTIVITY_FILTER_OPTION_ALL]: ACTIVITY_FILTER_OPTION_ALL,
33+
[ACTIVITY_FILTER_OPTION_UNRESOLVED]: COMMENT_STATUS_OPEN,
34+
[ACTIVITY_FILTER_OPTION_RESOLVED]: COMMENT_STATUS_RESOLVED,
35+
[ACTIVITY_FILTER_OPTION_TASKS]: FEED_ITEM_TYPE_TASK,
2936
};
3037

3138
function ActivitySidebarFilter({
32-
feedItemStatus = ACTIVITY_FEED_ITEM_ALL,
33-
onFeedItemStatusClick,
39+
activityFilterOptions,
40+
feedItemType = ACTIVITY_FILTER_OPTION_ALL,
41+
onFeedItemTypeClick,
3442
}: ActivitySidebarFilterProps) {
43+
const hasOnlyCommentActivity = (options: ActivityFilterOption[]): boolean => {
44+
const commentActivityFilterOptions = [
45+
ACTIVITY_FILTER_OPTION_ALL,
46+
ACTIVITY_FILTER_OPTION_RESOLVED,
47+
ACTIVITY_FILTER_OPTION_UNRESOLVED,
48+
];
49+
return options.every(option => commentActivityFilterOptions.includes(option));
50+
};
51+
52+
// The message for all activty is based on whether only comments are in the activityFilterOptions prop
53+
const allFilterMessage = hasOnlyCommentActivity(activityFilterOptions)
54+
? messages.activitySidebarFilterOptionAllComments
55+
: messages.activitySidebarFilterOptionAllActivity;
56+
57+
const statusMap = {
58+
[ACTIVITY_FILTER_OPTION_ALL]: {
59+
msg: allFilterMessage,
60+
val: ACTIVITY_FILTER_OPTION_ALL,
61+
},
62+
[COMMENT_STATUS_OPEN]: { msg: messages.activitySidebarFilterOptionOpen, val: COMMENT_STATUS_OPEN },
63+
[COMMENT_STATUS_RESOLVED]: {
64+
msg: messages.activitySidebarFilterOptionResolved,
65+
val: COMMENT_STATUS_RESOLVED,
66+
},
67+
[FEED_ITEM_TYPE_TASK]: {
68+
msg: messages.activitySidebarFilterOptionTasks,
69+
val: FEED_ITEM_TYPE_TASK,
70+
},
71+
};
72+
3573
return (
3674
<div className="bcs-ActivitySidebarFilter">
3775
<DropdownMenu className="bcs-ActivitySidebarFilter-dropdownMenu" constrainToWindow>
3876
<PlainButton type="button">
3977
<MenuToggle>
40-
<FormattedMessage {...statusMap[feedItemStatus].msg} />
78+
<FormattedMessage {...statusMap[feedItemType].msg} />
4179
</MenuToggle>
4280
</PlainButton>
4381
<Menu>
44-
{statuses.map(status => (
45-
<SelectMenuItem
46-
key={status}
47-
isSelected={status === feedItemStatus}
48-
onClick={() => onFeedItemStatusClick(statusMap[status].val)}
49-
>
50-
<FormattedMessage {...statusMap[status].msg} />
51-
</SelectMenuItem>
52-
))}
82+
{activityFilterOptions.map(filterOption => {
83+
const status = filterOptionToStatus[filterOption];
84+
return (
85+
<SelectMenuItem
86+
key={status}
87+
isSelected={status === feedItemType}
88+
onClick={() => onFeedItemTypeClick(statusMap[status].val)}
89+
>
90+
<FormattedMessage {...statusMap[status].msg} />
91+
</SelectMenuItem>
92+
);
93+
})}
5394
</Menu>
5495
</DropdownMenu>
5596
</div>

0 commit comments

Comments
 (0)