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

Notification #1129

Merged
merged 3 commits into from
Jul 17, 2023
Merged
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
138 changes: 128 additions & 10 deletions ui_src/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import './App.scss';

import { Switch, Route, withRouter } from 'react-router-dom';
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
import { JSONCodec, StringCodec, connect } from 'nats.ws';
import { useMediaQuery } from 'react-responsive';
import { connect } from 'nats.ws';
import { message } from 'antd';
import { useHistory } from 'react-router-dom';
import { message, notification } from 'antd';
import { Redirect } from 'react-router-dom';

import {
LOCAL_STORAGE_ACCOUNT_ID,
Expand All @@ -28,23 +30,27 @@ import {
} from './const/localStorageConsts';
import { CLOUD_URL, ENVIRONMENT, HANDLE_REFRESH_INTERVAL, WS_PREFIX, WS_SERVER_URL_PRODUCTION } from './config';
import { handleRefreshTokenRequest, httpRequest } from './services/http';
import infoNotificationIcon from './assets/images/infoNotificationIcon.svg';
import successIcon from './assets/images/successIcon.svg';
import close from './assets/images/closeNotification.svg';
import StationOverview from './domain/stationOverview';
import errorIcon from './assets/images/errorIcon.svg';
import MessageJourney from './domain/messageJourney';
import { isCloud } from './services/valueConvertor';
import Administration from './domain/administration';
import { ApiEndpoints } from './const/apiEndpoints';
import { isCloud } from './services/valueConvertor';
import warnIcon from './assets/images/warnIcon.svg';
import AppWrapper from './components/appWrapper';
import StationsList from './domain/stationsList';
import SchemaManagment from './domain/schema';
import { useHistory } from 'react-router-dom';
import { Redirect } from 'react-router-dom';
import PrivateRoute from './PrivateRoute';
import AuthService from './services/auth';
import Overview from './domain/overview';
import Loader from './components/loader';
import { Context } from './hooks/store';
import Profile from './domain/profile';
import pathDomains from './router';
import Users from './domain/users';
import { ApiEndpoints } from './const/apiEndpoints';
import AuthService from './services/auth';

let SysLogs = undefined;
let Login = undefined;
Expand All @@ -65,9 +71,14 @@ const App = withRouter((props) => {
const firebase_id_token = urlParams.get('firebase_id_token');
const firebase_organization_id = urlParams.get('firebase_organization_id');
const [cloudLogedIn, setCloudLogedIn] = useState(isCloud() ? false : true);
const [persistedNotifications, setPersistedNotifications] = useState(() => {
const storedNotifications = JSON.parse(localStorage.getItem('persistedNotifications'));
return storedNotifications || [];
});
const [displayedNotifications, setDisplayedNotifications] = useState([]);

const ref = useRef();
ref.current = cloudLogedIn;
const stateRef = useRef([]);
stateRef.current = [cloudLogedIn, persistedNotifications];

const handleLoginWithToken = async () => {
try {
Expand Down Expand Up @@ -133,7 +144,7 @@ const App = withRouter((props) => {
}, [isMobile]);

const handleRefresh = useCallback(async (firstTime) => {
if (window.location.pathname === pathDomains.login || (firebase_id_token !== null && !ref.current)) {
if (window.location.pathname === pathDomains.login || (firebase_id_token !== null && !stateRef.current[0])) {
return;
} else if (localStorage.getItem(LOCAL_STORAGE_TOKEN)) {
const ws_port = localStorage.getItem(LOCAL_STORAGE_WS_PORT);
Expand Down Expand Up @@ -189,8 +200,115 @@ const App = withRouter((props) => {
};
}, [handleRefresh, setAuthCheck]);

useEffect(() => {
const sc = StringCodec();
const jc = JSONCodec();
let sub;
const subscribeToNotifications = async () => {
try {
const rawBrokerName = await state.socket?.request(`$memphis_ws_subs.get_system_messages`, sc.encode('SUB'));
if (rawBrokerName) {
const brokerName = JSON.parse(sc.decode(rawBrokerName?._rdata))['name'];
sub = state.socket?.subscribe(`$memphis_ws_pubs.get_system_messages.${brokerName}`);
listenForUpdates();
}
} catch (err) {
console.error('Error subscribing to overview data:', err);
}
};

const listenForUpdates = async () => {
try {
if (sub) {
for await (const msg of sub) {
let data = jc.decode(msg.data);
const uniqueNewNotifications = data.filter((newNotification) => {
return !stateRef.current[1].some((existingNotification) => existingNotification.id === newNotification.id);
});
setPersistedNotifications((prevPersistedNotifications) => [...prevPersistedNotifications, ...uniqueNewNotifications]);
localStorage.setItem('persistedNotifications', JSON.stringify([...stateRef.current[1], ...uniqueNewNotifications]));
}
}
} catch (err) {
console.error('Error receiving overview data updates:', err);
}
};

isCloud() && subscribeToNotifications();

return () => {
if (sub) {
try {
sub.unsubscribe();
} catch (err) {
console.error('Error unsubscribing from overview data:', err);
}
}
};
}, [state.socket]);

const notificationHandler = (id, type, message, duration) => {
const defaultAntdField = {
className: 'notification-wrapper',
closeIcon: <img src={close} alt="close" />,
message: 'System Message',
onClose: () => {
const updatedNotifications = stateRef.current[1].map((n) => (n.id === id ? { ...n, read: true } : n));
setPersistedNotifications(updatedNotifications);
localStorage.setItem('persistedNotifications', JSON.stringify(updatedNotifications));
}
};
switch (type) {
case 'info':
notification.info({
...defaultAntdField,
icon: <img src={infoNotificationIcon} alt="info" />,
description: message,
duration: duration
});
break;
case 'warning':
notification.warning({
...defaultAntdField,

icon: <img src={warnIcon} alt="warn" />,
description: message,
duration: duration
});
break;
case 'error':
notification.error({
...defaultAntdField,
icon: <img src={errorIcon} alt="error" />,
description: message,
duration: duration
});
break;
case 'success':
notification.success({
...defaultAntdField,
icon: <img src={successIcon} alt="success" />,
description: message,
duration: duration
});
break;
default:
break;
}
};

useEffect(() => {
stateRef.current[1].forEach((notification) => {
if (!displayedNotifications.includes(notification.id) && !notification.read) {
notificationHandler(notification.id, notification.message_type, notification.message_payload, 0);
setDisplayedNotifications((prevDisplayedNotifications) => [...prevDisplayedNotifications, notification.id]);
}
});
}, [stateRef.current[1]]);

return (
<div className="app-container">
{!cloudLogedIn && <Loader />}
<div>
{' '}
{!authCheck &&
Expand Down
15 changes: 15 additions & 0 deletions ui_src/src/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,19 @@ pre {
}
.open {
transform: rotate(0deg);
}

.notification-wrapper{
border-radius: 12px;
border: 1px solid var(--gray-200, #EAECF0);
box-shadow: 0px 4px 6px -2px rgba(16, 24, 40, 0.03), 0px 12px 16px -4px rgba(16, 24, 40, 0.08);
.ant-notification-notice-message{
font-family: 'InterSemiBold';
font-size: 16px;
}
.ant-notification-notice-description{
font-family: 'Inter';
font-size: 14px;
color: #475467;
}
}
3 changes: 3 additions & 0 deletions ui_src/src/assets/images/closeNotification.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions ui_src/src/assets/images/errorIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions ui_src/src/assets/images/infoNotificationIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions ui_src/src/assets/images/successIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions ui_src/src/assets/images/warnIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 7 additions & 7 deletions ui_src/src/components/createStationForm/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,34 +176,34 @@ const CreateStationForm = ({ createStationFormRef, getStartedStateRef, finishUpd
break;
}
setActualPods(replicas);
} catch (error) { }
} catch (error) {}
};

const getAllSchemas = async () => {
try {
const data = await httpRequest('GET', ApiEndpoints.GET_ALL_SCHEMAS);
setSchemas(data);
} catch (error) { }
} catch (error) {}
};

const getIntegration = async () => {
try {
const data = await httpRequest('GET', `${ApiEndpoints.GET_INTEGRATION_DETAILS}?name=s3`);
setIntegrateValue(data);
} catch (error) { }
} catch (error) {}
};

const createStation = async (bodyRequest) => {
try {
getStarted && setLoading(true);
setLoading(true);
const data = await httpRequest('POST', ApiEndpoints.CREATE_STATION, bodyRequest);
if (data) {
if (!getStarted) history.push(`${pathDomains.stations}/${data.name}`);
else finishUpdate(data);
}
} catch (error) {
} finally {
getStarted && setLoading(false);
setLoading(false);
}
};

Expand Down Expand Up @@ -287,7 +287,7 @@ const CreateStationForm = ({ createStationFormRef, getStartedStateRef, finishUpd
</div>
)}
</div>
{!isCloud() &&
{!isCloud() && (
<div className="replicas-container">
<TitleComponent
headerTitle="Replicas"
Expand All @@ -313,7 +313,7 @@ const CreateStationForm = ({ createStationFormRef, getStartedStateRef, finishUpd
</Form.Item>
</div>
</div>
}
)}
<div className="idempotency-type">
<Form.Item name="idempotency">
<div>
Expand Down
6 changes: 3 additions & 3 deletions ui_src/src/domain/overview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,12 @@ function OverView() {
<div className="hostname">
<p>Account ID : </p>
<span>{localStorage.getItem(LOCAL_STORAGE_ACCOUNT_ID)}</span>
<Copy width="12" text={localStorage.getItem(LOCAL_STORAGE_ACCOUNT_ID)} />
<Copy width="12" data={localStorage.getItem(LOCAL_STORAGE_ACCOUNT_ID)} />
</div>
<div className="hostname">
<p>Broker Hostname : </p>
<span>{host}</span>
<Copy width="12" text={host} />
<Copy width="12" data={host} />
</div>
</div>
)}
Expand Down Expand Up @@ -291,7 +291,7 @@ function OverView() {
open={open}
isLoading={creatingProsessd}
>
<CreateStationForm createStationFormRef={createStationRef} handleClick={(e) => setCreatingProsessd(e)} />
<CreateStationForm createStationFormRef={createStationRef} setLoading={(e) => setCreatingProsessd(e)} />
</Modal>
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions ui_src/src/domain/overview/schemaverse/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ const Schemaverse = () => {
</div>
<div className="total-data sum">
<span>
<p className="total-measure">Total Schemas</p>
<p className="total-measure">Total schemas</p>
<p className="total-value">{state?.monitor_data?.schemas_details?.total_schemas}</p>
</span>
<Divider type="vertical" />
<span>
<p className="total-measure">Enforced Schemas</p>
<p className="total-measure">Enforced schemas</p>
<p className="total-value">{state?.monitor_data?.schemas_details?.enforced_schemas}</p>
</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,22 +150,22 @@ const Messages = () => {
};

const handleResend = async () => {
setResendProcced(true);
// setResendProcced(true);
try {
await httpRequest('POST', `${ApiEndpoints.RESEND_POISON_MESSAGE_JOURNEY}`, { poison_message_ids: isCheck, station_name: stationName });
setTimeout(() => {
setResendProcced(false);
message.success({
key: 'memphisSuccessMessage',
content: isCheck.length === 1 ? 'The message was sent successfully' : 'The messages were sent successfully',
duration: 5,
style: { cursor: 'pointer' },
onClick: () => message.destroy('memphisSuccessMessage')
});
setIsCheck([]);
}, 1500);
// setTimeout(() => {
// setResendProcced(false);
// message.success({
// key: 'memphisSuccessMessage',
// content: isCheck.length === 1 ? 'The message was sent successfully' : 'The messages were sent successfully',
// duration: 5,
// style: { cursor: 'pointer' },
// onClick: () => message.destroy('memphisSuccessMessage')
// });
// setIsCheck([]);
// }, 1500);
} catch (error) {
setResendProcced(false);
// setResendProcced(false);
}
};

Expand Down Expand Up @@ -256,28 +256,27 @@ const Messages = () => {
<Button
width="80px"
height="32px"
placeholder="Drop"
placeholder={isCheck.length === 0 ? 'Purge' : `Drop (${isCheck.length})`}
colorType="white"
radiusType="circle"
backgroundColorType="purple"
fontSize="12px"
fontWeight="600"
disabled={isCheck.length === 0}
isLoading={ignoreProcced}
onClick={() => handleDrop()}
onClick={() => (isCheck.length === 0 ? modalPurgeFlip(true) : handleDrop())}
/>
)}
{tabValue === 'Dead-letter' && subTabValue === 'Unacked' && stationState?.stationSocketData?.poison_messages?.length > 0 && (
<Button
width="80px"
height="32px"
placeholder="Resend"
placeholder={isCheck.length === 0 ? 'Resend all' : `Resend (${isCheck.length})`}
colorType="white"
radiusType="circle"
backgroundColorType="purple"
fontSize="12px"
fontWeight="600"
disabled={isCheck.length === 0 || !stationState?.stationMetaData?.is_native}
disabled={!stationState?.stationMetaData?.is_native}
tooltip={!stationState?.stationMetaData?.is_native && 'Supported only by using Memphis SDKs'}
isLoading={resendProcced}
onClick={() => handleResend()}
Expand Down
Loading