Skip to content

Commit

Permalink
Merge pull request #2629 from aneelac22/RHCLOUD-25965
Browse files Browse the repository at this point in the history
Hook notifications drawer to chrome-services websockets
  • Loading branch information
florkbr authored Sep 21, 2023
2 parents 96387ce + e6f45e8 commit 01360d2
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/components/Header/HeaderTests/Tools.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('Tools', () => {
await act(async () => {
container = render(
<MemoryRouter>
<Provider store={createStore((state = { chrome: { user: {} } }) => state)}>
<Provider store={createStore((state = { chrome: { user: {}, notifications: { data: [] } } }) => state)}>
<Tools onClick={mockClick} />
</Provider>
</MemoryRouter>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Header/Tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const Tools = () => {
});
const { xs } = useWindowWidth();
const user = useSelector(({ chrome: { user } }: ReduxState) => user!);
const unreadNotifications = useSelector(({ chrome: { notifications } }: ReduxState) => notifications?.data?.filter((isRead) => !isRead) || []);
const unreadNotifications = useSelector(({ chrome: { notifications } }: ReduxState) => notifications.data.some((item) => !item.read));
const isDrawerExpanded = useSelector(({ chrome: { notifications } }: ReduxState) => notifications?.isExpanded);
const dispatch = useDispatch();
const libjwt = useContext(LibtJWTContext);
Expand Down Expand Up @@ -243,7 +243,7 @@ const Tools = () => {
<ToolbarItem className="pf-v5-u-mr-0 pf-v5-u-ml-sm">
<NotificationBadge
className="chr-c-notification-badge"
variant={unreadNotifications.length === 0 ? 'read' : 'unread'}
variant={unreadNotifications ? 'unread' : 'read'}
onClick={() => dispatch(toggleNotificationsDrawer())}
aria-label="Notifications"
isExpanded={isDrawerExpanded}
Expand Down
1 change: 1 addition & 0 deletions src/components/RootApp/ScalprumRoot.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ describe('ScalprumRoot', () => {
user: { username: 'foo', first_name: 'foo', last_name: 'foo', is_org_admin: false, is_internal: false },
},
},
notifications: { data: [] },
activeApp: 'some-app',
activeLocation: 'some-location',
appId: 'app-id',
Expand Down
16 changes: 15 additions & 1 deletion src/components/RootApp/ScalprumRoot.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { Suspense, lazy, memo, useCallback, useContext, useEffect, useMemo, useRef } from 'react';
import axios from 'axios';
import { ScalprumProvider, ScalprumProviderProps } from '@scalprum/react-core';
import { shallowEqual, useSelector, useStore } from 'react-redux';
import { shallowEqual, useDispatch, useSelector, useStore } from 'react-redux';
import { Route, Routes } from 'react-router-dom';
import { HelpTopic, HelpTopicContext } from '@patternfly/quickstarts';
import isEqual from 'lodash/isEqual';
Expand Down Expand Up @@ -29,6 +30,7 @@ import chromeApiWrapper from './chromeApiWrapper';
import { ITLess } from '../../utils/common';
import InternalChromeContext from '../../utils/internalChromeContext';
import useChromeServiceEvents from '../../hooks/useChromeServiceEvents';
import { populateNotifications } from '../../redux/actions';

const ProductSelection = lazy(() => import('../Stratosphere/ProductSelection'));

Expand All @@ -46,6 +48,7 @@ export type ScalprumRootProps = FooterProps & {
const ScalprumRoot = memo(
({ config, helpTopicsAPI, quickstartsAPI, cookieElement, setCookieElement, ...props }: ScalprumRootProps) => {
const { setFilteredHelpTopics } = useContext(HelpTopicContext);
const dispatch = useDispatch();
const internalFilteredTopics = useRef<HelpTopic[]>([]);
const { analytics } = useContext(SegmentContext);

Expand All @@ -56,6 +59,15 @@ const ScalprumRoot = memo(
// initialize WS event handling
useChromeServiceEvents();

async function getNotifications() {
try {
const notifications = await axios.get('/api/notifications/v1/notifications/drawer');
dispatch(populateNotifications(notifications.data.data));
} catch (error) {
console.error('Unable to get Notifications ', error);
}
}

const { setActiveTopic } = useHelpTopicManager(helpTopicsAPI);

function isStringArray(arr: EnableTopicsArgs): arr is string[] {
Expand Down Expand Up @@ -91,6 +103,8 @@ const ScalprumRoot = memo(
useEffect(() => {
// prepare webpack module sharing scope overrides
updateSharedScope();
// get notifications drawer api
getNotifications();
const unregister = chromeHistory.listen(historyListener);
return () => {
if (typeof unregister === 'function') {
Expand Down
21 changes: 10 additions & 11 deletions src/hooks/useChromeServiceEvents.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useEffect, useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { useFlag } from '@unleash/proxy-client-react';
import { UPDATE_NOTIFICATIONS } from '../redux/action-types';

import { getEncodedToken, setCookie } from '../jwt/jwt';
import { NotificationsPayload } from '../redux/store';

const NOTIFICATION_DRAWER = 'notifications.drawer';
const SAMPLE_EVENT = 'sample.type';
Expand All @@ -14,12 +16,7 @@ type SamplePayload = {
foo: string;
};

type NotificationPayload = {
title: string;
description: string;
};

type Payload = NotificationPayload | SamplePayload;
type Payload = NotificationsPayload | SamplePayload;
interface GenericEvent<T extends Payload = Payload> {
type: EventTypes;
data: T;
Expand All @@ -34,15 +31,17 @@ const useChromeServiceEvents = () => {
const dispatch = useDispatch();
const isNotificationsEnabled = useFlag('platform.chrome.notifications-drawer');

const handlerMap: { [key in EventTypes]: (payload: Payload) => void } = useMemo(
const handlerMap: { [key in EventTypes]: (payload: GenericEvent<Payload>) => void } = useMemo(
() => ({
[NOTIFICATION_DRAWER]: (data: Payload) => dispatch({ type: 'foo', payload: data }),
[SAMPLE_EVENT]: (data: Payload) => console.log('Received sample payload', data),
[NOTIFICATION_DRAWER]: (data: GenericEvent<Payload>) => {
dispatch({ type: UPDATE_NOTIFICATIONS, payload: data });
},
[SAMPLE_EVENT]: (data: GenericEvent<Payload>) => console.log('Received sample payload', data),
}),
[]
);

function handleEvent(type: EventTypes, data: Payload): void {
function handleEvent(type: EventTypes, data: GenericEvent<Payload>): void {
handlerMap[type](data);
}

Expand All @@ -63,7 +62,7 @@ const useChromeServiceEvents = () => {
try {
const payload = JSON.parse(data);
if (isGenericEvent(payload)) {
handleEvent(payload.type, payload.data);
handleEvent(payload.type, payload);
} else {
throw new Error(`Unable to handle event type: ${event.type}. The payload does not have required shape! ${event}`);
}
Expand Down
2 changes: 2 additions & 0 deletions src/redux/action-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ export const CLEAR_QUICKSTARTS = '@@chrome/clear-quickstarts';
export const SET_GATEWAY_ERROR = '@@chrome/set-gateway-error';

export const TOGGLE_NOTIFICATIONS_DRAWER = '@@chrome/toggle-notifications-drawer';
export const POPULATE_NOTIFICATIONS = '@@chrome/populate-notifications';

export const MARK_NOTIFICATION_AS_READ = '@@chrome/mark-notification-as-read';
export const MARK_NOTIFICATION_AS_UNREAD = '@@chrome/mark-notification-as-unread';
export const MARK_ALL_NOTIFICATION_AS_READ = '@@chrome/mark-all-notification-as-read';
export const MARK_ALL_NOTIFICATION_AS_UNREAD = '@@chrome/mark-all-notification-as-unread';
export const UPDATE_NOTIFICATIONS = '@@chrome/update-notifications';
13 changes: 10 additions & 3 deletions src/redux/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getAllSIDs, getAllTags, getAllWorkloads } from '../components/GlobalFil
import type { TagFilterOptions, TagPagination } from '../components/GlobalFilter/tagsApi';
import type { ChromeUser } from '@redhat-cloud-services/types';
import type { ChromeModule, FlagTagsFilter, NavDOMEvent, NavItem, Navigation } from '../@types/types';
import type { AccessRequest } from './store';
import type { AccessRequest, NotificationData, NotificationsPayload } from './store';
import type { QuickStart } from '@patternfly/quickstarts';
import type { ThreeScaleError } from '../utils/responseInterceptors';

Expand Down Expand Up @@ -207,12 +207,14 @@ export const toggleNotificationsDrawer = () => ({
type: actionTypes.TOGGLE_NOTIFICATIONS_DRAWER,
});

export const markNotificationAsRead = (id: number) => ({
export const populateNotifications = (data: NotificationData[]) => ({ type: actionTypes.POPULATE_NOTIFICATIONS, payload: { data } });

export const markNotificationAsRead = (id: string) => ({
type: actionTypes.MARK_NOTIFICATION_AS_READ,
payload: id,
});

export const markNotificationAsUnread = (id: number) => ({
export const markNotificationAsUnread = (id: string) => ({
type: actionTypes.MARK_NOTIFICATION_AS_UNREAD,
payload: id,
});
Expand All @@ -224,3 +226,8 @@ export const markAllNotificationsAsRead = () => ({
export const markAllNotificationsAsUnread = () => ({
type: actionTypes.MARK_ALL_NOTIFICATION_AS_UNREAD,
});

export const updateNotifications = (payload: NotificationsPayload) => ({
type: actionTypes.UPDATE_NOTIFICATIONS,
payload,
});
26 changes: 23 additions & 3 deletions src/redux/chromeReducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { REQUESTS_COUNT, REQUESTS_DATA } from '../utils/consts';
import { ChromeModule, NavItem, Navigation } from '../@types/types';
import { ITLess, generateRoutesList, highlightItems, isBeta, levelArray } from '../utils/common';
import { ThreeScaleError } from '../utils/responseInterceptors';
import { AccessRequest, ChromeState, NotificationData } from './store';
import { AccessRequest, ChromeState, NotificationData, NotificationsPayload } from './store';

export function contextSwitcherBannerReducer(state: ChromeState): ChromeState {
return {
Expand Down Expand Up @@ -344,7 +344,17 @@ export function toggleNotificationsReducer(state: ChromeState) {
};
}

export function markNotificationAsRead(state: ChromeState, { payload }: { payload: number }): ChromeState {
export function populateNotificationsReducer(state: ChromeState, { payload: { data } }: { payload: { data: NotificationData[] } }) {
return {
...state,
notifications: {
...state.notifications,
data,
},
};
}

export function markNotificationAsRead(state: ChromeState, { payload }: { payload: string }): ChromeState {
return {
...state,
notifications: {
Expand All @@ -357,7 +367,7 @@ export function markNotificationAsRead(state: ChromeState, { payload }: { payloa
};
}

export function markNotificationAsUnread(state: ChromeState, { payload }: { payload: number }): ChromeState {
export function markNotificationAsUnread(state: ChromeState, { payload }: { payload: string }): ChromeState {
return {
...state,
notifications: {
Expand Down Expand Up @@ -391,3 +401,13 @@ export function markAllNotificationsAsUnread(state: ChromeState): ChromeState {
},
};
}

export function updateNotificationsReducer(state: ChromeState, { payload }: { payload: NotificationsPayload }) {
return {
...state,
notifications: {
...state.notifications,
data: [...state.notifications.data, payload.data],
},
};
}
6 changes: 6 additions & 0 deletions src/redux/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ import {
onPageAction,
onPageObjectId,
onRegisterModule,
populateNotificationsReducer,
populateQuickstartsReducer,
setGatewayError,
setPendoFeedbackFlag,
toggleDebuggerButton,
toggleDebuggerModal,
toggleFeedbackModal,
toggleNotificationsReducer,
updateNotificationsReducer,
} from './chromeReducers';
import {
globalFilterDefaultState,
Expand Down Expand Up @@ -67,6 +69,7 @@ import {
MARK_NOTIFICATION_AS_READ,
MARK_NOTIFICATION_AS_UNREAD,
MARK_REQUEST_NOTIFICATION_SEEN,
POPULATE_NOTIFICATIONS,
POPULATE_QUICKSTARTS_CATALOG,
REGISTER_MODULE,
SET_GATEWAY_ERROR,
Expand All @@ -78,6 +81,7 @@ import {
TOGGLE_NOTIFICATIONS_DRAWER,
UPDATE_ACCESS_REQUESTS_NOTIFICATIONS,
UPDATE_DOCUMENT_TITLE_REDUCER,
UPDATE_NOTIFICATIONS,
USER_LOGIN,
} from './action-types';
import { ChromeState, GlobalFilterState, ReduxState } from './store';
Expand Down Expand Up @@ -108,10 +112,12 @@ const reducers = {
[SET_GATEWAY_ERROR]: setGatewayError,
[CLEAR_QUICKSTARTS]: clearQuickstartsReducer,
[TOGGLE_NOTIFICATIONS_DRAWER]: toggleNotificationsReducer,
[POPULATE_NOTIFICATIONS]: populateNotificationsReducer,
[MARK_NOTIFICATION_AS_READ]: markNotificationAsRead,
[MARK_NOTIFICATION_AS_UNREAD]: markNotificationAsUnread,
[MARK_ALL_NOTIFICATION_AS_READ]: markAllNotificationsAsRead,
[MARK_ALL_NOTIFICATION_AS_UNREAD]: markAllNotificationsAsUnread,
[UPDATE_NOTIFICATIONS]: updateNotificationsReducer,
};

const globalFilter = {
Expand Down
15 changes: 13 additions & 2 deletions src/redux/store.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type InternalNavigation = {
export type AccessRequest = { request_id: string; created: string; seen: boolean };

export type NotificationData = {
id: number;
id: string;
title: string;
description: string;
read: boolean;
Expand All @@ -26,6 +26,17 @@ export type Notifications = {
count: number;
};

export type NotificationsPayload = {
data: NotificationData;
source: string;
// cloud events sub protocol metadata
datacontenttype: string;
specversion: string;
// a type field used to identify message purpose
type: string;
time: string;
};

export type ChromeState = {
contextSwitcherOpen: boolean;
activeApp?: string;
Expand Down Expand Up @@ -68,7 +79,7 @@ export type ChromeState = {
};
documentTitle?: string;
gatewayError?: ThreeScaleError;
notifications?: Notifications;
notifications: Notifications;
};

export type GlobalFilterWorkloads = {
Expand Down

0 comments on commit 01360d2

Please sign in to comment.