Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POC] WS browser events #1895

Closed
Closed
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
2 changes: 1 addition & 1 deletion src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<title>
console.redhat.com
</title>
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https: wss: data: 'unsafe-inline' 'unsafe-eval' https://*.redhat.com/ https://www.redhat.com https://*.openshift.com/ https://api.stage.openshift.com/ https://identity.api.openshift.com/ https://www.youtube.com/ https://redhat.sc.omtrdc.net/ https://assets.adobedtm.com https://www.redhat.com https://*.storage.googleapis.com/ https://*.hotjar.com:* https://*.hotjar.io wss://*.hotjar.com;">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https: wss: data: 'unsafe-inline' 'unsafe-eval' ws://localhost:8080 https://*.redhat.com/ https://www.redhat.com https://*.openshift.com/ https://api.stage.openshift.com/ https://identity.api.openshift.com/ https://www.youtube.com/ https://redhat.sc.omtrdc.net/ https://assets.adobedtm.com https://www.redhat.com https://*.storage.googleapis.com/ https://*.hotjar.com:* https://*.hotjar.io wss://*.hotjar.com;">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about wss:? Also, shouldn't there be a wss:///.redhat.com?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't take these seriously we I don't even know where the service will live

<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="x-ua-compatible" content="ie=edge">
Expand Down
9 changes: 9 additions & 0 deletions src/js/App/Header/Tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { Badge, Button, Divider, DropdownItem, ToolbarItem, ToolbarGroup, Switch
import QuestionCircleIcon from '@patternfly/react-icons/dist/js/icons/question-circle-icon';
import CogIcon from '@patternfly/react-icons/dist/js/icons/cog-icon';
import RedhatIcon from '@patternfly/react-icons/dist/js/icons/redhat-icon';
import BellIcon from '@patternfly/react-icons/dist/js/icons/bell-icon';
import UserToggle from './UserToggle';
import ToolbarToggle from './ToolbarToggle';
import HeaderAlert from './HeaderAlert';
import cookie from 'js-cookie';
import { getUrl, getSection, isBeta } from '../../utils';
import { spinUpStore } from '../../redux-config';
import classnames from 'classnames';
import { useDispatch } from 'react-redux';

export const switchRelease = (isBeta, pathname) => {
cookie.set('cs_toggledRelease', 'true');
Expand Down Expand Up @@ -82,6 +84,8 @@ const Tools = () => {
isDemoAcc: false,
});

const dispatch = useDispatch();

useEffect(() => {
window.insights.chrome.auth.getUser().then((user) => {
/* Disable settings/cog icon when a user doesn't have an account number */
Expand Down Expand Up @@ -178,6 +182,11 @@ const Tools = () => {
spaceItems={{ default: 'spaceItemsNone' }}
widget-type="InsightsToolbar"
>
<ToolbarItem>
<Button onClick={() => dispatch({ type: 'toggle-notifications-drawer' })} variant="plain">
<BellIcon />
</Button>
</ToolbarItem>
{isBeta() && (
<ToolbarItem>
<Badge className="chr-c-badge-beta">beta</Badge>
Expand Down
63 changes: 63 additions & 0 deletions src/js/App/Notifications/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { useEffect, useState } from 'react';
import {
Drawer,
DrawerActions,
DrawerCloseButton,
DrawerContent,
DrawerContentBody,
DrawerPanelContent,
NotificationDrawer,
NotificationDrawerBody,
NotificationDrawerGroup,
NotificationDrawerGroupList,
NotificationDrawerHeader,
NotificationDrawerList,
NotificationDrawerListItem,
NotificationDrawerListItemBody,
NotificationDrawerListItemHeader,
} from '@patternfly/react-core';
import { useDispatch, useSelector } from 'react-redux';

const NotificationsContent = () => {
const [isBroadcastExpanded, setBroadcastExpanded] = useState(false);
const notifications = useSelector(({ chrome: { notifications } }) => notifications || []);
const dispatch = useDispatch();
return (
<DrawerPanelContent>
<NotificationDrawer>
<NotificationDrawerHeader customText="Foo bar">
<DrawerActions>
<DrawerCloseButton onClick={() => dispatch({ type: 'toggle-notifications-drawer' })} />
</DrawerActions>
</NotificationDrawerHeader>
<NotificationDrawerBody>
<NotificationDrawerGroupList>
<NotificationDrawerGroup onExpand={(_e, value) => setBroadcastExpanded(value)} isExpanded={isBroadcastExpanded} title="Boradcast">
<NotificationDrawerList isHidden={!isBroadcastExpanded}>
{notifications.map(({ variant = 'info', title, description }, idx) => (
<NotificationDrawerListItem key={idx} variant={variant}>
<NotificationDrawerListItemHeader variant={variant} title={title} />
<NotificationDrawerListItemBody timestamp="Just now">{description}</NotificationDrawerListItemBody>
</NotificationDrawerListItem>
))}
</NotificationDrawerList>
</NotificationDrawerGroup>
</NotificationDrawerGroupList>
</NotificationDrawerBody>
</NotificationDrawer>
</DrawerPanelContent>
);
};

const Notifications = ({ children }) => {
const isNotificationsDrawerOpen = useSelector(({ chrome: { isNotificationsDrawerOpen } }) => isNotificationsDrawerOpen);
return (
<Drawer isExpanded={isNotificationsDrawerOpen}>
<DrawerContent panelContent={<NotificationsContent />}>
<DrawerContentBody>{children}</DrawerContentBody>
</DrawerContent>
</Drawer>
);
};

export default Notifications;
15 changes: 9 additions & 6 deletions src/js/App/RootApp/DefaultLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import '../Sidenav/Navigation/Navigation.scss';
import './DefaultLayout.scss';
import { CROSS_ACCESS_ACCOUNT_NUMBER } from '../../consts';
import { getUrl } from '../../utils';
import Notifications from '../Notifications';

const ShieldedRoot = memo(
({ hideNav, insightsContentRef, isGlobalFilterEnabled, initialized, Sidebar }) => {
Expand Down Expand Up @@ -85,12 +86,14 @@ const ShieldedRoot = memo(
}
sidebar={hideNav ? undefined : <PageSidebar isNavOpen={isNavOpen} id="chr-c-sidebar" nav={Sidebar} />}
>
<div ref={insightsContentRef} className={classnames('chr-render', { 'pf-u-h-100vh': !isGlobalFilterEnabled })}>
{isGlobalFilterEnabled && <GlobalFilter />}
{selectedAccountNumber && <div className="chr-viewing-as">Viewing as Account {selectedAccountNumber}</div>}
<Routes routesProps={{ scopeClass: 'chr-scope__default-layout' }} insightsContentRef={insightsContentRef} />
<main className="pf-c-page__main" id="no-access"></main>
</div>
<Notifications>
<div ref={insightsContentRef} className={classnames('chr-render', { 'pf-u-h-100vh': !isGlobalFilterEnabled })}>
{isGlobalFilterEnabled && <GlobalFilter />}
{selectedAccountNumber && <div className="chr-viewing-as">Viewing as Account {selectedAccountNumber}</div>}
<Routes routesProps={{ scopeClass: 'chr-scope__default-layout' }} insightsContentRef={insightsContentRef} />
<main className="pf-c-page__main" id="no-access"></main>
</div>
</Notifications>
</Page>
);
},
Expand Down
74 changes: 71 additions & 3 deletions src/js/App/RootApp/RootApp.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useCallback, useEffect, useRef } from 'react';
import { Router } from 'react-router-dom';
import { HelpTopicContainer, QuickStartContainer } from '@patternfly/quickstarts';

Expand All @@ -12,11 +12,73 @@ import useQuickstartsStates from '../QuickStart/useQuickstartsStates';
import { populateQuickstartsCatalog } from '../../redux/actions';
import { LazyQuickStartCatalog } from '../QuickStart/LazyQuickStartCatalog';
import useHelpTopicState from '../QuickStart/useHelpTopicState';
import Notifications from '../Notifications';

const useEvents = () => {
console.log('Use events was for some reason triggered');
const events = useRef({});
const conn = useRef();
const activeModule = useSelector(({ chrome: { activeModule } }) => activeModule);
const dispatch = useDispatch();
function handleEvent(payload) {
if (payload.type === 'new-notification') {
dispatch({ type: 'add-notification', payload });
} else if (payload.type === 'invalidate') {
Object.values(events.current)
.flat()
.forEach((cb) => {
cb(payload);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd check if the callback is defined in here

Suggested change
cb(payload);
cb?.(payload);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's checked when a callback is registered. It will not register listener if a callback is not a function

});
}
console.log(events);
}

/**
* Establish connection
*/
useEffect(() => {
const x = new WebSocket('ws://localhost:8080/ws');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather see a proxy config in webpack for websockets.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to do some research on this. I am not sure how the webpack proxy works with WS. Not sure if our implementation will work properly.

x.onmessage = (messageEvent) => {
const { data } = messageEvent;
try {
const payload = JSON.parse(data);
handleEvent(payload);
} catch (error) {
console.error('Unable to parse WS message data: ', error);
}
};
conn.current = x;
}, []);

const registerEvent = useCallback(
(type, app, entity, cb) => {
if (typeof app !== 'string' || typeof type !== 'string' || typeof entity !== 'string' || typeof cb !== 'function') {
throw new Error('Invalid registerEvents parameters');
}
const listener = (data) => {
if (data.type === type && data.app === app && data.entity === entity) {
cb(data);
}
};
if (Array.isArray(events.current[activeModule])) {
events.current[activeModule].push(listener);
} else {
events.current[activeModule] = [listener];
}
},
[activeModule]
);

return registerEvent;
};

const RootApp = (props) => {
const dispatch = useDispatch();

const registerEvent = useEvents();

const { allQuickStartStates, setAllQuickStartStates, activeQuickStartID, setActiveQuickStartID } = useQuickstartsStates();
const { helpTopics, addHelpTopics, disableTopics, enableTopics } = useHelpTopicState();
const dispatch = useDispatch();
const quickStarts = useSelector(
({
chrome: {
Expand Down Expand Up @@ -69,7 +131,13 @@ const RootApp = (props) => {

<QuickStartContainer className="pf-u-h-100vh" {...quickStartProps}>
<HelpTopicContainer helpTopics={helpTopics}>
<ScalprumRoot {...props} helpTopics={helpTopics} quickstartsAPI={quickstartsAPI} helpTopicsAPI={helpTopicsAPI} />
<ScalprumRoot
{...props}
registerEvent={registerEvent}
helpTopics={helpTopics}
quickstartsAPI={quickstartsAPI}
helpTopicsAPI={helpTopicsAPI}
/>
</HelpTopicContainer>
</QuickStartContainer>
</IDPChecker>
Expand Down
6 changes: 4 additions & 2 deletions src/js/App/RootApp/ScalprumRoot.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { lazy, Suspense, useContext, useEffect, useState } from 'react';
import React, { lazy, Suspense, useCallback, useContext, useEffect, useRef, useState } from 'react';
import { ScalprumProvider } from '@scalprum/react-core';
import PropTypes from 'prop-types';
import { useSelector, useDispatch } from 'react-redux';
Expand All @@ -21,7 +21,7 @@ const loaderWrapper = (Component, props = {}) => (
</Suspense>
);

const ScalprumRoot = ({ config, helpTopicsAPI, quickstartsAPI, ...props }) => {
const ScalprumRoot = ({ registerEvent, config, helpTopicsAPI, quickstartsAPI, ...props }) => {
const { setActiveHelpTopicByName, helpTopics, activeHelpTopic } = useContext(HelpTopicContext);
const [activeTopicName, setActiveTopicName] = useState();
const [prevActiveTopic, setPrevActiveTopic] = useState(activeHelpTopic?.name);
Expand Down Expand Up @@ -91,6 +91,7 @@ const ScalprumRoot = ({ config, helpTopicsAPI, quickstartsAPI, ...props }) => {
setActiveTopic('');
},
},
registerEvent,
chromeHistory: history,
},
}}
Expand Down Expand Up @@ -123,6 +124,7 @@ ScalprumRoot.propTypes = {
toggle: PropTypes.func.isRequired,
Catalog: PropTypes.elementType.isRequired,
}).isRequired,
registerEvent: PropTypes.func.isRequired,
};

export default ScalprumRoot;
5 changes: 5 additions & 0 deletions src/js/redux/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
populateQuickstartsReducer,
disableQuickstartsReducer,
documentTitleReducer,
notificationsDrawerReducer,
addNewNotificationReducer,
} from './reducers';
import {
onGetAllTags,
Expand Down Expand Up @@ -80,6 +82,8 @@ const reducers = {
[POPULATE_QUICKSTARTS_CATALOG]: populateQuickstartsReducer,
[DISABLE_QUICKSTARTS]: disableQuickstartsReducer,
[UPDATE_DOCUMENT_TITLE_REDUCER]: documentTitleReducer,
'toggle-notifications-drawer': notificationsDrawerReducer,
'add-notification': addNewNotificationReducer,
Comment on lines +85 to +86
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make these into constants?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely. This is just a draft to demo the features. Do not take anything seriously, only the concept.

};

const globalFilter = {
Expand Down Expand Up @@ -109,6 +113,7 @@ export default function () {
quickstarts: {
quickstarts: {},
},
notifications: [],
},
action
) => applyReducerHash(reducers)(state, action),
Expand Down
14 changes: 14 additions & 0 deletions src/js/redux/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,17 @@ export function documentTitleReducer(state, { payload }) {
documentTitle: payload,
};
}

export function notificationsDrawerReducer(state) {
return {
...state,
isNotificationsDrawerOpen: !state.isNotificationsDrawerOpen,
};
}

export function addNewNotificationReducer(state, { payload }) {
return {
...state,
notifications: [...(state.notifications || []), payload],
};
}