Skip to content

Commit 5205ccb

Browse files
LukshioMariaAga
authored andcommitted
Fixes #38345 - Update notifications to pf5
1 parent 7212002 commit 5205ccb

29 files changed

+758
-749
lines changed

app/assets/stylesheets/patternfly_colors_overrides.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
}
2121
}
2222
}
23+
2324
.pf-v5-c-nav__link {
2425
--pf-v5-c-nav__link--BackgroundColor: #{$nav-pf-vertical-bg-color};
2526
--pf-v5-c-nav__link--m-current--BackgroundColor: #{$nav-pf-vertical-active-bg-color};
@@ -37,4 +38,7 @@
3738
.pf-v5-c-nav__item {
3839
--pf-v5-c-nav__item--before--BorderColor: #{$nav-pf-vertical-active-bg-color};
3940
}
41+
.pf-v5-c-notification-badge {
42+
--pf-v5-c-notification-badge--m-read--m-expanded--after--BackgroundColor: var(--pf-v5-global--palette--blue-500)
43+
}
4044
}

test/integration/notifications_drawer_test.rb

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,30 @@ class NotificationsDrawerIntegrationTest < IntegrationTestWithJavascript
1414
private
1515

1616
def notifications_open_and_close_flow
17-
within(".notifications_container") do
18-
assert page.has_selector?('.fa.fa-bell-o'), "Notifications toggler was expected in the top bar, but was not found"
19-
assert page.has_no_selector?('.drawer-pf'), "Notifications drawer was expected to be closed, but was found opend"
20-
21-
# open notifications drawer
22-
page.find('.fa.fa-bell-o').click
23-
assert page.has_selector?('.drawer-pf'), "Notifications drawer was expected to be opend, but was found closed"
24-
25-
# close notifications drawer by click on the toggler
26-
page.find('.fa.fa-bell-o').click
27-
assert page.has_no_selector?('.drawer-pf'), "Notifications drawer was expected to be closed, but was found opend"
28-
29-
# open notifications drawer
30-
page.find('.fa.fa-bell-o').click
31-
assert page.has_selector?('.drawer-pf'), "Notifications drawer was expected to be opend, but was found closed"
32-
33-
# close notifications drawer by click on close button
34-
page.find('.drawer-pf-notifications').click # to remove the tooltip from the icon
35-
page.find('.drawer-pf-close').click
36-
assert page.has_no_selector?('.drawer-pf'), "Notifications drawer was expected to be closed, but was found opend"
37-
38-
# open notifications drawer
39-
page.find('.fa.fa-bell-o').click
40-
assert page.has_selector?('.drawer-pf'), "Notifications drawer was expected to be opend, but was found closed"
41-
end
42-
43-
# close notifications drawer by click outside
44-
page.find('body').click
45-
assert page.has_no_selector?('.notifications_container .drawer-pf'), "Notifications drawer was expected to be closed, but was found opend"
17+
assert page.has_selector?('#notification-badge'), "Notifications toggler was expected in the top bar, but was not found"
18+
assert page.has_no_selector?('.notifications'), "Notifications drawer was expected to be closed, but was found opend"
19+
20+
# open notifications drawer
21+
page.find('#notification-badge').click
22+
assert page.has_selector?('.notifications'), "Notifications drawer was expected to be opend, but was found closed"
23+
24+
# close notifications drawer by click on the toggler
25+
page.find('#notification-badge').click
26+
assert page.has_no_selector?('.notifications'), "Notifications drawer was expected to be closed, but was found opend"
27+
28+
# open notifications drawer
29+
page.find('#notification-badge').click
30+
assert page.has_selector?('.notifications'), "Notifications drawer was expected to be opend, but was found closed"
31+
32+
# close notifications drawer by click on close button
33+
page.find('button[aria-label="Close"]').click
34+
assert page.has_no_selector?('.notifications button[aria-label="Close"]'), "Notifications drawer was expected to be closed, but was found opend"
35+
36+
# open notifications drawer
37+
page.find('#notification-badge').click
38+
assert page.has_selector?('.notifications'), "Notifications drawer was expected to be opend, but was found closed"
39+
40+
page.find('button[aria-label="Close"]').click
4641
end
4742

4843
def navigate_somewhere_with_turbolinks

webpack/assets/javascripts/react_app/common/__mocks__/I18n.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export const intl = {
1212
locale: 'en',
1313
};
1414

15+
export const documentLocale = jest.fn(() => 'en');
16+
1517
const i18n = {
1618
translate,
1719
ngettext,

webpack/assets/javascripts/react_app/components/Layout/Layout.js

Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -35,45 +35,43 @@ const Layout = ({
3535
}
3636
};
3737
return (
38-
<>
39-
<Flex
40-
direction={{ default: 'column' }}
41-
flexWrap={{ default: 'nowrap' }}
42-
spaceItems={{ default: 'spaceItemsNone' }}
43-
style={{ height: '100%' }}
44-
>
45-
<FlexItem>
46-
<InstanceBanner data={data} />
47-
</FlexItem>
48-
<FlexItem grow={{ default: 'grow' }} style={{ minHeight: 0 }}>
49-
<Page
50-
mainContainerId="foreman-main-container"
51-
header={
52-
<Header
53-
data={data}
54-
onNavToggle={onNavToggle}
55-
isLoading={isLoading}
56-
/>
57-
}
58-
id="foreman-page"
59-
sidebar={
60-
<PageSidebar isSidebarOpen={!isCollapsed}>
61-
<PageSidebarBody>
62-
<Navigation
63-
items={items}
64-
navigate={navigate}
65-
navigationActiveItem={navigationActiveItem}
66-
setNavigationActiveItem={setNavigationActiveItem}
67-
/>
68-
</PageSidebarBody>
69-
</PageSidebar>
70-
}
71-
>
72-
{children}
73-
</Page>
74-
</FlexItem>
75-
</Flex>
76-
</>
38+
<Flex
39+
direction={{ default: 'column' }}
40+
flexWrap={{ default: 'nowrap' }}
41+
spaceItems={{ default: 'spaceItemsNone' }}
42+
style={{ height: '100%' }}
43+
>
44+
<FlexItem>
45+
<InstanceBanner data={data} />
46+
</FlexItem>
47+
<FlexItem grow={{ default: 'grow' }} style={{ minHeight: 0 }}>
48+
<Page
49+
mainContainerId="foreman-main-container"
50+
header={
51+
<Header
52+
data={data}
53+
onNavToggle={onNavToggle}
54+
isLoading={isLoading}
55+
/>
56+
}
57+
id="foreman-page"
58+
sidebar={
59+
<PageSidebar isSidebarOpen={!isCollapsed}>
60+
<PageSidebarBody>
61+
<Navigation
62+
items={items}
63+
navigate={navigate}
64+
navigationActiveItem={navigationActiveItem}
65+
setNavigationActiveItem={setNavigationActiveItem}
66+
/>
67+
</PageSidebarBody>
68+
</PageSidebar>
69+
}
70+
>
71+
{children}
72+
</Page>
73+
</FlexItem>
74+
</Flex>
7775
);
7876
};
7977

webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
} from '@patternfly/react-core';
99
import TaxonomySwitcher from '../TaxonomySwitcher/TaxonomySwitcher';
1010
import UserDropdowns from './UserDropdowns';
11-
import NotificationContainer from '../../../notifications';
1211
import ImpersonateIcon from '../ImpersonateIcon';
1312
import {
1413
layoutPropTypes,
@@ -18,6 +17,9 @@ import {
1817
userPropType,
1918
} from '../../LayoutHelper';
2019
import './HeaderToolbar.scss';
20+
import NotificationIcon from '../../../notifications/components/NotificationIcon';
21+
import { NotificationsContextWrapper } from '../../../notifications/NotificationsContext';
22+
import Notifications from '../../../notifications';
2123

2224
const HeaderToolbar = ({
2325
locations,
@@ -37,15 +39,17 @@ const HeaderToolbar = ({
3739
/>
3840
</ToolbarGroup>
3941
<ToolbarGroup align={{ default: 'alignRight' }}>
40-
<ToolbarItem className="notifications_container">
41-
<NotificationContainer data={{ url: notificationUrl }} />
42+
<ToolbarItem>
43+
<NotificationsContextWrapper>
44+
<NotificationIcon />
45+
<Notifications />
46+
</NotificationsContextWrapper>
4247
</ToolbarItem>
4348
{user.impersonated_by && (
4449
<ToolbarItem className="impersonation-item">
4550
<ImpersonateIcon stopImpersonationUrl={stopImpersonationUrl} />
4651
</ToolbarItem>
4752
)}
48-
4953
<ToolbarItem className="header-tool-item-hidden-lg user-nav-item">
5054
<UserDropdowns notificationUrl={notificationUrl} user={user} />
5155
</ToolbarItem>

webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/__snapshots__/HeaderToolbar.test.js.snap

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,11 @@ exports[`HeaderToolbar rendering render HeaderToolbar 1`] = `
5353
}
5454
}
5555
>
56-
<ToolbarItem
57-
className="notifications_container"
58-
>
59-
<Connect(OnClickOutside(notificationContainer))
60-
data={
61-
Object {
62-
"url": "/notification_recipients",
63-
}
64-
}
65-
/>
56+
<ToolbarItem>
57+
<NotificationsContextWrapper>
58+
<NotificationIcon />
59+
<Notifications />
60+
</NotificationsContextWrapper>
6661
</ToolbarItem>
6762
<ToolbarItem
6863
className="impersonation-item"
Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import { getValue, setValue } from '../../common/SessionStorage';
22

3-
export const getIsOpened = () => getValue('isDrawerOpen');
4-
export const setIsOpened = value => setValue('isDrawerOpen', value);
5-
export const getExpandedGroup = () => getValue('expandedGroup');
6-
export const setExpandedGroup = value => setValue('expandedGroup', value);
73
export const getHasUnreadMessages = () => getValue('hasUnreadMessages');
84
export const setHasUnreadMessages = value =>
95
setValue('hasUnreadMessages', value);
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import PropTypes from 'prop-types';
2+
import React, { useEffect, useState, useMemo } from 'react';
3+
import { useDispatch, useSelector } from 'react-redux';
4+
import { NotificationBadgeVariant as BadgeVariant } from '@patternfly/react-core';
5+
import { groupBy } from 'lodash';
6+
import { startNotificationsPolling } from '../../redux/actions/notifications';
7+
import forceSingleton from '../../common/forceSingleton';
8+
9+
export const NotificationsContext = forceSingleton('NotificationsContext', () =>
10+
React.createContext({})
11+
);
12+
13+
export const NotificationsContextWrapper = ({ children }) => {
14+
const dispatch = useDispatch();
15+
const drawerRef = React.useRef(null);
16+
17+
const [isExpanded, setIsExpanded] = useState(false);
18+
const [variant, setVariant] = useState(BadgeVariant.read);
19+
20+
const notificationsState = useSelector(state => state.notifications);
21+
const notifications = groupBy(notificationsState.notifications, n => n.group);
22+
23+
const [expandedNotifications, setExpandedNotifications] = useState([]);
24+
const [expandedKebab, setExpandedKebab] = useState('');
25+
26+
const isGroupExpanded = key => expandedNotifications.includes(key);
27+
const isKebabExpanded = key => expandedKebab === key;
28+
29+
const toggleNotifications = key => {
30+
const otherExpanded = expandedNotifications.filter(
31+
arrKeys => arrKeys !== key
32+
);
33+
return isGroupExpanded(key)
34+
? setExpandedNotifications(otherExpanded)
35+
: setExpandedNotifications([...otherExpanded, key]);
36+
};
37+
38+
const toggleKebab = key => {
39+
isKebabExpanded(key) ? setExpandedKebab('') : setExpandedKebab(key);
40+
};
41+
42+
const closeNotificationsDrawer = () => {
43+
toggleKebab('');
44+
toggleExpanded();
45+
};
46+
47+
useEffect(() => {
48+
dispatch(startNotificationsPolling());
49+
}, [dispatch]);
50+
51+
const countUnreadMessages = useMemo(() => {
52+
if (notificationsState.notifications) {
53+
const count = Object.values(notificationsState.notifications).filter(
54+
n => !n.seen
55+
).length;
56+
57+
setVariant(count > 0 ? BadgeVariant.unread : BadgeVariant.read);
58+
return count;
59+
}
60+
return 0;
61+
}, [notificationsState]);
62+
63+
const toggleExpanded = () => {
64+
setIsExpanded(!isExpanded);
65+
};
66+
67+
return (
68+
<NotificationsContext.Provider
69+
value={{
70+
isExpanded,
71+
setIsExpanded,
72+
toggleExpanded,
73+
variant,
74+
drawerRef,
75+
notifications,
76+
countUnreadMessages,
77+
dispatch,
78+
toggleKebab,
79+
isKebabExpanded,
80+
isGroupExpanded,
81+
closeNotificationsDrawer,
82+
toggleNotifications,
83+
}}
84+
>
85+
{children}
86+
</NotificationsContext.Provider>
87+
);
88+
};
89+
90+
NotificationsContextWrapper.propTypes = {
91+
children: PropTypes.node.isRequired,
92+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { documentLocale } from '../../common/I18n';
2+
3+
export const formatDateTime = datetime => {
4+
const locale = documentLocale();
5+
const options = {
6+
month: 'numeric',
7+
day: 'numeric',
8+
year: 'numeric',
9+
hour: 'numeric',
10+
minute: 'numeric',
11+
second: 'numeric',
12+
hour12: true,
13+
};
14+
try {
15+
return new Date(datetime).toLocaleDateString(locale, options);
16+
} catch {
17+
return new Date(datetime).toLocaleDateString('en', options);
18+
}
19+
};

webpack/assets/javascripts/react_app/components/notifications/ToggleIcon/ToggleIcon.js

Lines changed: 0 additions & 34 deletions
This file was deleted.

0 commit comments

Comments
 (0)