Skip to content

Commit 4aea80d

Browse files
committed
issue #1962 - showing 5 newest notifications per source in dropdown, and 10 in notification center and mobile notifications view
1 parent 92801f4 commit 4aea80d

File tree

6 files changed

+150
-166
lines changed

6 files changed

+150
-166
lines changed

src/components/NotificationsDropdown/NotificationsDropdownContainer.jsx

Lines changed: 114 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import { Link } from 'react-router-dom'
88
import { connect } from 'react-redux'
99
import _ from 'lodash'
1010
import { getNotifications, visitNotifications, toggleNotificationSeen, markAllNotificationsRead,
11-
toggleNotificationRead, toggleBundledNotificationRead, viewOlderNotifications, toggleNotificationsDropdownMobile } from '../../routes/notifications/actions'
12-
import { splitNotificationsBySources, filterReadNotifications, limitQuantityInSources, filterOldNotifications } from '../../routes/notifications/helpers/notifications'
11+
toggleNotificationRead, toggleBundledNotificationRead, viewOlderNotifications,
12+
toggleNotificationsDropdownMobile, hideOlderNotifications } from '../../routes/notifications/actions'
13+
import { splitNotificationsBySources, filterReadNotifications, limitQuantityInSources } from '../../routes/notifications/helpers/notifications'
1314
import NotificationsSection from '../NotificationsSection/NotificationsSection'
1415
import NotificationsEmpty from '../NotificationsEmpty/NotificationsEmpty'
1516
import NotificationsDropdownHeader from '../NotificationsDropdownHeader/NotificationsDropdownHeader'
@@ -18,21 +19,11 @@ import NotificationsMobilePage from './NotificationsMobilePage'
1819
import NotificationsReadAll from './NotificationsReadAll'
1920
import ScrollLock from 'react-scroll-lock-component'
2021
import MediaQuery from 'react-responsive'
21-
import { NOTIFICATIONS_DROPDOWN_PER_SOURCE, NOTIFICATIONS_DROPDOWN_MAX_TOTAL, REFRESH_NOTIFICATIONS_INTERVAL, SCREEN_BREAKPOINT_MD } from '../../config/constants'
22+
import { NOTIFICATIONS_DROPDOWN_PER_SOURCE, NOTIFICATIONS_NEW_PER_SOURCE, REFRESH_NOTIFICATIONS_INTERVAL, SCREEN_BREAKPOINT_MD } from '../../config/constants'
2223
import './NotificationsDropdown.scss'
2324

2425
class NotificationsDropdownContainer extends React.Component {
2526

26-
constructor(props) {
27-
super(props)
28-
29-
this.state = {
30-
isViewAll: false
31-
}
32-
33-
this.viewAll = this.viewAll.bind(this)
34-
}
35-
3627
componentDidMount() {
3728
this.props.getNotifications()
3829
this.autoRefreshNotifications = setInterval(() => this.props.getNotifications(), REFRESH_NOTIFICATIONS_INTERVAL)
@@ -42,6 +33,7 @@ class NotificationsDropdownContainer extends React.Component {
4233
clearInterval(this.autoRefreshNotifications)
4334
// hide notifications dropdown for mobile, when this component is unmounted
4435
this.props.toggleNotificationsDropdownMobile(false)
36+
this.props.hideOlderNotifications()
4537
}
4638

4739
componentWillReceiveProps(nextProps) {
@@ -52,13 +44,10 @@ class NotificationsDropdownContainer extends React.Component {
5244
// hide notifications dropdown for mobile,
5345
// when this component persist but URL changed
5446
this.props.toggleNotificationsDropdownMobile(false)
47+
this.props.hideOlderNotifications()
5548
}
5649
}
5750

58-
viewAll() {
59-
this.setState({isViewAll: true})
60-
}
61-
6251
render() {
6352
if (!this.props.initialized) {
6453
return <NotificationsDropdown hasUnread={false} />
@@ -67,10 +56,9 @@ class NotificationsDropdownContainer extends React.Component {
6756
const {lastVisited, sources, notifications, markAllNotificationsRead, toggleNotificationRead, toggleNotificationSeen,
6857
pending, toggleBundledNotificationRead, visitNotifications, oldSourceIds, viewOlderNotifications, isDropdownMobileOpen,
6958
toggleNotificationsDropdownMobile } = this.props
70-
const {isViewAll} = this.state
7159
const getPathname = link => link.split(/[?#]/)[0].replace(/\/?$/, '')
7260

73-
// mark notifications with url mathc current page's url as seen
61+
// mark notifications with url match current page's url as seen
7462
if (!pending) {
7563
const seenNotificationIds = notifications
7664
.filter(({ isRead, seen, goto = '' }) => !isRead && !seen && getPathname(goto) === getPathname(window.location.pathname))
@@ -80,26 +68,9 @@ class NotificationsDropdownContainer extends React.Component {
8068
}
8169

8270
const notReadNotifications = filterReadNotifications(notifications)
83-
const notOldNotifications = filterOldNotifications(notReadNotifications, oldSourceIds)
84-
const allNotificationsBySources = splitNotificationsBySources(sources, notOldNotifications)
85-
let notificationsBySources
86-
87-
if (!isViewAll) {
88-
notificationsBySources = limitQuantityInSources(
89-
allNotificationsBySources,
90-
NOTIFICATIONS_DROPDOWN_PER_SOURCE,
91-
NOTIFICATIONS_DROPDOWN_MAX_TOTAL
92-
)
93-
} else {
94-
notificationsBySources = allNotificationsBySources
95-
}
96-
97-
const hiddenByLimitCount = _.sumBy(allNotificationsBySources, 'notifications.length') - _.sumBy(notificationsBySources, 'notifications.length')
71+
const allNotificationsBySources = splitNotificationsBySources(sources, notReadNotifications)
9872

99-
const globalSource = notificationsBySources.length > 0 && notificationsBySources[0].id === 'global' ? notificationsBySources[0] : null
100-
const projectSources = notificationsBySources.length > 1 && globalSource ? notificationsBySources.slice(1) : notificationsBySources
10173
const hasUnread = notReadNotifications.length > 0
102-
const olderNotificationsCount = notReadNotifications.length - notOldNotifications.length
10374
// we have to give Dropdown component some time
10475
// before removing notification item node from the list
10576
// otherwise dropdown thinks we clicked outside and closes dropdown
@@ -142,92 +113,111 @@ class NotificationsDropdownContainer extends React.Component {
142113

143114
return (
144115
<MediaQuery minWidth={SCREEN_BREAKPOINT_MD}>
145-
{(matches) => (matches ? (
146-
<NotificationsDropdown hasUnread={hasUnread} hasNew={hasNew} onToggle={visitNotifications}>
147-
<NotificationsDropdownHeader onMarkAllClick={() => !pending && markAllNotificationsRead()} hasUnread={hasUnread}/>
148-
{!hasUnread ? (
149-
<div className="notifications-dropdown-body">
150-
{notificationsEmpty}
151-
</div>
152-
) : ([
153-
<ScrollLock key="body">
154-
<div className="notifications-dropdown-body">
155-
{globalSource && globalSource.notifications.length &&
156-
<NotificationsSection
157-
{...globalSource}
158-
isGlobal
159-
isSimple
160-
onReadToggleClick={toggleNotificationReadWithDelay}
161-
onLinkClick={markNotificationSeen}
162-
/>
163-
}
164-
{projectSources.filter(source => source.notifications.length > 0).map(source => (
165-
<NotificationsSection
166-
{...source}
167-
key={source.id}
168-
isSimple
169-
onReadToggleClick={toggleNotificationReadWithDelay}
170-
onLinkClick={markNotificationSeen}
171-
/>
172-
))}
173-
</div>
174-
</ScrollLock>,
175-
<NotificationsReadAll key="footer" to="/notifications">
176-
{
177-
olderNotificationsCount > 0 ?
178-
`View ${olderNotificationsCount} older notification${olderNotificationsCount > 1 ? 's' : ''}` :
179-
'View all notifications'
180-
}
181-
</NotificationsReadAll>
182-
])}
183-
</NotificationsDropdown>
184-
) : (
185-
<NotificationsMobilePage
186-
hasUnread={hasUnread}
187-
hasNew={hasNew}
188-
onToggle={() => {
189-
toggleNotificationsDropdownMobile()
190-
visitNotifications()
191-
}}
192-
isOpen={isDropdownMobileOpen}
193-
>
194-
{!hasUnread ? (
195-
notificationsEmpty
196-
) : (
197-
<div>
198-
{globalSource && (globalSource.notifications.length || isViewAll && globalSource.total) &&
199-
<NotificationsSection
200-
{...globalSource}
201-
isGlobal
202-
isSimple
203-
isLoading={globalSource.isLoading}
204-
onReadToggleClick={toggleNotificationReadWithDelay}
205-
onViewOlderClick={isViewAll ? () => viewOlderNotifications(globalSource.id) : null}
206-
onLinkClick={(notificationId) => {
207-
toggleNotificationsDropdownMobile()
208-
markNotificationSeen(notificationId)
209-
}}
210-
/>}
211-
{projectSources.filter(source => source.notifications.length || isViewAll && source.total).map(source => (
212-
<NotificationsSection
213-
{...source}
214-
key={source.id}
215-
isSimple
216-
isLoading={source.isLoading}
217-
onReadToggleClick={toggleNotificationReadWithDelay}
218-
onViewOlderClick={isViewAll ? () => viewOlderNotifications(source.id) : null}
219-
onLinkClick={(notificationId) => {
220-
toggleNotificationsDropdownMobile()
221-
markNotificationSeen(notificationId)
222-
}}
223-
/>
224-
))}
225-
{!isViewAll && (olderNotificationsCount > 0 || hiddenByLimitCount > 0) &&
226-
<NotificationsReadAll onClick={this.viewAll}>Read all notifications</NotificationsReadAll>}
227-
</div>
228-
)}
229-
</NotificationsMobilePage>
230-
))}
116+
{(matches) => {
117+
if (matches) {
118+
const notificationsBySources = limitQuantityInSources(
119+
allNotificationsBySources,
120+
NOTIFICATIONS_DROPDOWN_PER_SOURCE,
121+
oldSourceIds
122+
)
123+
const hiddenByLimitCount = _.sumBy(notificationsBySources, 'total') - _.sumBy(notificationsBySources, 'notifications.length')
124+
const globalSource = notificationsBySources.length > 0 && notificationsBySources[0].id === 'global' ? notificationsBySources[0] : null
125+
const projectSources = notificationsBySources.length > 1 && globalSource ? notificationsBySources.slice(1) : notificationsBySources
126+
127+
return (
128+
<NotificationsDropdown hasUnread={hasUnread} hasNew={hasNew} onToggle={visitNotifications}>
129+
<NotificationsDropdownHeader onMarkAllClick={() => !pending && markAllNotificationsRead()} hasUnread={hasUnread}/>
130+
{!hasUnread ? (
131+
<div className="notifications-dropdown-body">
132+
{notificationsEmpty}
133+
</div>
134+
) : ([
135+
<ScrollLock key="body">
136+
<div className="notifications-dropdown-body">
137+
{globalSource && globalSource.notifications.length &&
138+
<NotificationsSection
139+
{...globalSource}
140+
isGlobal
141+
isSimple
142+
onReadToggleClick={toggleNotificationReadWithDelay}
143+
onLinkClick={markNotificationSeen}
144+
/>
145+
}
146+
{projectSources.filter(source => source.notifications.length > 0).map(source => (
147+
<NotificationsSection
148+
{...source}
149+
key={source.id}
150+
isSimple
151+
onReadToggleClick={toggleNotificationReadWithDelay}
152+
onLinkClick={markNotificationSeen}
153+
/>
154+
))}
155+
</div>
156+
</ScrollLock>,
157+
<NotificationsReadAll key="footer" to="/notifications">
158+
{
159+
hiddenByLimitCount > 0 ?
160+
`View ${hiddenByLimitCount} older notification${hiddenByLimitCount > 1 ? 's' : ''}` :
161+
'View all notifications'
162+
}
163+
</NotificationsReadAll>
164+
])}
165+
</NotificationsDropdown>
166+
)
167+
} else {
168+
const notificationsBySources = limitQuantityInSources(
169+
allNotificationsBySources,
170+
NOTIFICATIONS_NEW_PER_SOURCE,
171+
oldSourceIds
172+
)
173+
const globalSource = notificationsBySources.length > 0 && notificationsBySources[0].id === 'global' ? notificationsBySources[0] : null
174+
const projectSources = notificationsBySources.length > 1 && globalSource ? notificationsBySources.slice(1) : notificationsBySources
175+
176+
return (
177+
<NotificationsMobilePage
178+
hasUnread={hasUnread}
179+
hasNew={hasNew}
180+
onToggle={() => {
181+
toggleNotificationsDropdownMobile()
182+
visitNotifications()
183+
}}
184+
isOpen={isDropdownMobileOpen}
185+
>
186+
{!hasUnread ? (
187+
notificationsEmpty
188+
) : (
189+
<div>
190+
{globalSource && globalSource.notifications.length > 0 &&
191+
<NotificationsSection
192+
{...globalSource}
193+
isGlobal
194+
isSimple
195+
onReadToggleClick={toggleNotificationReadWithDelay}
196+
onViewOlderClick={() => viewOlderNotifications(globalSource.id)}
197+
onLinkClick={(notificationId) => {
198+
toggleNotificationsDropdownMobile()
199+
markNotificationSeen(notificationId)
200+
}}
201+
/>}
202+
{projectSources.filter(source => source.notifications.length).map(source => (
203+
<NotificationsSection
204+
{...source}
205+
key={source.id}
206+
isSimple
207+
onReadToggleClick={toggleNotificationReadWithDelay}
208+
onViewOlderClick={() => viewOlderNotifications(source.id)}
209+
onLinkClick={(notificationId) => {
210+
toggleNotificationsDropdownMobile()
211+
markNotificationSeen(notificationId)
212+
}}
213+
/>
214+
))}
215+
</div>
216+
)}
217+
</NotificationsMobilePage>
218+
)
219+
}
220+
}}
231221
</MediaQuery>
232222
)
233223
}
@@ -243,6 +233,7 @@ const mapDispatchToProps = {
243233
toggleNotificationRead,
244234
toggleBundledNotificationRead,
245235
viewOlderNotifications,
236+
hideOlderNotifications,
246237
toggleNotificationsDropdownMobile
247238
}
248239

src/config/constants.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const MARK_ALL_NOTIFICATIONS_READ = 'MARK_ALL_NOTIFICATIONS_READ'
1414
export const TOGGLE_NOTIFICATION_READ = 'TOGGLE_NOTIFICATION_READ'
1515
export const TOGGLE_NOTIFICATION_SEEN = 'TOGGLE_NOTIFICATION_SEEN'
1616
export const VIEW_OLDER_NOTIFICATIONS_SUCCESS = 'VIEW_OLDER_NOTIFICATIONS_SUCCESS'
17+
export const HIDE_OLDER_NOTIFICATIONS_SUCCESS = 'HIDE_OLDER_NOTIFICATIONS_SUCCESS'
1718
export const NOTIFICATIONS_PENDING = 'NOTIFICATIONS_PENDING'
1819
export const TOGGLE_NOTIFICATIONS_DROPDOWN_MOBILE = 'TOGGLE_NOTIFICATIONS_DROPDOWN_MOBILE'
1920

@@ -379,11 +380,9 @@ export const SORT_OPTIONS = [
379380
export const REFRESH_NOTIFICATIONS_INTERVAL = 1000 * 60 * 1 // 1 minute interval
380381
export const REFRESH_UNREAD_UPDATE_INTERVAL = 1000 * 10 * 1 // 10 second interval
381382
export const NOTIFICATIONS_DROPDOWN_PER_SOURCE = 5
382-
export const NOTIFICATIONS_DROPDOWN_MAX_TOTAL = Infinity
383+
export const NOTIFICATIONS_NEW_PER_SOURCE = 10
383384

384385
export const NOTIFICATIONS_LIMIT = 1000
385-
// old notification time in minutes, a notification is old if its date is later than this time
386-
export const OLD_NOTIFICATION_TIME = 60 * 48 // 2 day2
387386

388387
export const SCROLL_TO_MARGIN = 70 // px - 60px of toolbar height + 10px to make some margin
389388
export const SCROLL_TO_DURATION = 500 // ms

src/routes/notifications/actions/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
MARK_ALL_NOTIFICATIONS_READ,
1010
TOGGLE_NOTIFICATION_READ,
1111
VIEW_OLDER_NOTIFICATIONS_SUCCESS,
12+
HIDE_OLDER_NOTIFICATIONS_SUCCESS,
1213
NOTIFICATIONS_PENDING,
1314
TOGGLE_NOTIFICATIONS_DROPDOWN_MOBILE
1415
} from '../../../config/constants'
@@ -112,6 +113,10 @@ export const viewOlderNotifications = (sourceId) => (dispatch) => dispatch({
112113
payload: sourceId
113114
})
114115

116+
export const hideOlderNotifications = () => (dispatch) => dispatch({
117+
type: HIDE_OLDER_NOTIFICATIONS_SUCCESS
118+
})
119+
115120
export const toggleNotificationsDropdownMobile = (isOpen) => (dispatch) => dispatch({
116121
type: TOGGLE_NOTIFICATIONS_DROPDOWN_MOBILE,
117122
payload: isOpen

src/routes/notifications/containers/NotificationsContainer.jsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ import { connect } from 'react-redux'
88
import { Link } from 'react-router-dom'
99
import Sticky from 'react-stickynode'
1010
import { getNotifications, setNotificationsFilterBy, markAllNotificationsRead,
11-
toggleNotificationRead, viewOlderNotifications, toggleBundledNotificationRead } from '../actions'
11+
toggleNotificationRead, viewOlderNotifications, toggleBundledNotificationRead, hideOlderNotifications } from '../actions'
1212
import FooterV2 from '../../../components/FooterV2/FooterV2'
1313
import NotificationsSection from '../../../components/NotificationsSection/NotificationsSection'
1414
import NotificationsSectionTitle from '../../../components/NotificationsSectionTitle/NotificationsSectionTitle'
1515
import SideFilter from '../../../components/SideFilter/SideFilter'
1616
import NotificationsEmpty from '../../../components/NotificationsEmpty/NotificationsEmpty'
1717
import spinnerWhileLoading from '../../../components/LoadingSpinner'
18-
import { getNotificationsFilters, splitNotificationsBySources, filterReadNotifications, filterOldNotifications } from '../helpers/notifications'
18+
import { getNotificationsFilters, splitNotificationsBySources, filterReadNotifications, limitQuantityInSources } from '../helpers/notifications'
1919
import { requiresAuthentication } from '../../../components/AuthenticatedComponent'
20-
import { REFRESH_NOTIFICATIONS_INTERVAL } from '../../../config/constants'
20+
import { REFRESH_NOTIFICATIONS_INTERVAL, NOTIFICATIONS_NEW_PER_SOURCE } from '../../../config/constants'
2121
import './NotificationsContainer.scss'
2222

2323
class NotificationsContainer extends React.Component {
@@ -29,6 +29,7 @@ class NotificationsContainer extends React.Component {
2929

3030
componentWillUnmount() {
3131
clearInterval(this.autoRefreshNotifications)
32+
this.props.hideOlderNotifications()
3233
}
3334

3435
render() {
@@ -39,8 +40,12 @@ class NotificationsContainer extends React.Component {
3940
markAllNotificationsRead, toggleNotificationRead, viewOlderNotifications,
4041
oldSourceIds, pending, toggleBundledNotificationRead } = this.props
4142
const notReadNotifications = filterReadNotifications(notifications)
42-
const notOldNotifications = filterOldNotifications(notReadNotifications, oldSourceIds)
43-
const notificationsBySources = splitNotificationsBySources(sources, notOldNotifications)
43+
const allNotificationsBySources = splitNotificationsBySources(sources, notReadNotifications)
44+
const notificationsBySources = limitQuantityInSources(
45+
allNotificationsBySources,
46+
NOTIFICATIONS_NEW_PER_SOURCE,
47+
oldSourceIds
48+
)
4449
let globalSource = notificationsBySources.length > 0 && notificationsBySources[0].id === 'global' ? notificationsBySources[0] : null
4550
let projectSources = globalSource ? notificationsBySources.slice(1) : notificationsBySources
4651
if (filterBy) {
@@ -135,6 +140,7 @@ const mapDispatchToProps = {
135140
markAllNotificationsRead,
136141
toggleNotificationRead,
137142
viewOlderNotifications,
143+
hideOlderNotifications,
138144
toggleBundledNotificationRead
139145
}
140146

0 commit comments

Comments
 (0)