Merged in release/2025-03-14 (pull request #2214)

Release/2025 03 14
This commit is contained in:
Dave Richer
2025-03-14 15:29:10 +00:00
13 changed files with 46 additions and 26 deletions

View File

@@ -48,8 +48,6 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const scenarioNotificationsOn = client?.getTreatment("Realtime_Notifications_UI") === "on";
useEffect(() => { useEffect(() => {
if (!navigator.onLine) { if (!navigator.onLine) {
setOnline(false); setOnline(false);
@@ -203,12 +201,7 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
path="/manage/*" path="/manage/*"
element={ element={
<ErrorBoundary> <ErrorBoundary>
<SocketProvider <SocketProvider bodyshop={bodyshop} navigate={navigate} currentUser={currentUser}>
bodyshop={bodyshop}
navigate={navigate}
currentUser={currentUser}
scenarioNotificationsOn={scenarioNotificationsOn}
>
<PrivateRoute isAuthorized={currentUser.authorized} /> <PrivateRoute isAuthorized={currentUser.authorized} />
</SocketProvider> </SocketProvider>
</ErrorBoundary> </ErrorBoundary>
@@ -220,12 +213,7 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
path="/tech/*" path="/tech/*"
element={ element={
<ErrorBoundary> <ErrorBoundary>
<SocketProvider <SocketProvider bodyshop={bodyshop} navigate={navigate} currentUser={currentUser}>
bodyshop={bodyshop}
navigate={navigate}
currentUser={currentUser}
scenarioNotificationsOn={scenarioNotificationsOn}
>
<PrivateRoute isAuthorized={currentUser.authorized} /> <PrivateRoute isAuthorized={currentUser.authorized} />
</SocketProvider> </SocketProvider>
</ErrorBoundary> </ErrorBoundary>

View File

@@ -14,6 +14,7 @@ import {
} from "../../graphql/notifications.queries.js"; } from "../../graphql/notifications.queries.js";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
const SocketContext = createContext(null); const SocketContext = createContext(null);
@@ -25,11 +26,10 @@ const INITIAL_NOTIFICATIONS = 10;
* @param bodyshop * @param bodyshop
* @param navigate * @param navigate
* @param currentUser * @param currentUser
* @param scenarioNotificationsOn
* @returns {JSX.Element} * @returns {JSX.Element}
* @constructor * @constructor
*/ */
const SocketProvider = ({ children, bodyshop, navigate, currentUser, scenarioNotificationsOn }) => { const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
const socketRef = useRef(null); const socketRef = useRef(null);
const [clientId, setClientId] = useState(null); const [clientId, setClientId] = useState(null);
const [isConnected, setIsConnected] = useState(false); const [isConnected, setIsConnected] = useState(false);
@@ -37,6 +37,14 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser, scenarioNot
const userAssociationId = bodyshop?.associations?.[0]?.id; const userAssociationId = bodyshop?.associations?.[0]?.id;
const { t } = useTranslation(); const { t } = useTranslation();
const {
treatments: { Realtime_Notifications_UI }
} = useSplitTreatments({
attributes: {},
names: ["Realtime_Notifications_UI"],
splitKey: bodyshop?.imexshopid
});
const [markNotificationRead] = useMutation(MARK_NOTIFICATION_READ, { const [markNotificationRead] = useMutation(MARK_NOTIFICATION_READ, {
update: (cache, { data: { update_notifications } }) => { update: (cache, { data: { update_notifications } }) => {
const timestamp = new Date().toISOString(); const timestamp = new Date().toISOString();
@@ -209,7 +217,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser, scenarioNot
const handleNotification = (data) => { const handleNotification = (data) => {
// Scenario Notifications have been disabled, bail. // Scenario Notifications have been disabled, bail.
if (!scenarioNotificationsOn) { if (Realtime_Notifications_UI?.treatment !== "on") {
return; return;
} }
@@ -329,7 +337,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser, scenarioNot
const handleSyncNotificationRead = ({ notificationId, timestamp }) => { const handleSyncNotificationRead = ({ notificationId, timestamp }) => {
// Scenario Notifications have been disabled, bail. // Scenario Notifications have been disabled, bail.
if (!scenarioNotificationsOn) { if (Realtime_Notifications_UI?.treatment !== "on") {
return; return;
} }
@@ -371,7 +379,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser, scenarioNot
const handleSyncAllNotificationsRead = ({ timestamp }) => { const handleSyncAllNotificationsRead = ({ timestamp }) => {
// Scenario Notifications have been disabled, bail. // Scenario Notifications have been disabled, bail.
if (!scenarioNotificationsOn) { if (Realtime_Notifications_UI?.treatment !== "on") {
return; return;
} }
@@ -462,7 +470,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser, scenarioNot
markAllNotificationsRead, markAllNotificationsRead,
navigate, navigate,
currentUser, currentUser,
scenarioNotificationsOn, Realtime_Notifications_UI,
t t
]); ]);
@@ -474,7 +482,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser, scenarioNot
isConnected, isConnected,
markNotificationRead, markNotificationRead,
markAllNotificationsRead, markAllNotificationsRead,
scenarioNotificationsOn scenarioNotificationsOn: Realtime_Notifications_UI?.treatment === "on"
}} }}
> >
{children} {children}

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."notificiations_idx_jobs";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "notificiations_idx_jobs" on
"public"."notifications" using btree ("jobid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."notifications_idx_associations";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "notifications_idx_associations" on
"public"."notifications" using btree ("associationid");

View File

@@ -0,0 +1,3 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE INDEX idx_notifications_created_at_not_read ON notifications(created_at desc, read) where read is null;

View File

@@ -0,0 +1 @@
CREATE INDEX idx_notifications_created_at_not_read ON notifications(created_at desc, read) where read is null;

View File

@@ -0,0 +1,3 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE INDEX idx_notifications_associations_not_read ON notifications(associationid, read) where read is null;

View File

@@ -0,0 +1 @@
CREATE INDEX idx_notifications_associations_not_read ON notifications(associationid, read) where read is null;

View File

@@ -20,6 +20,11 @@ const defaultFooter = () => {
const now = () => moment().format("MM/DD/YYYY @ hh:mm a"); const now = () => moment().format("MM/DD/YYYY @ hh:mm a");
/**
* Generate the email template
* @param strings
* @returns {string}
*/
const generateEmailTemplate = (strings) => { const generateEmailTemplate = (strings) => {
return ( return (
` `

View File

@@ -5,6 +5,7 @@ const { InstanceEndpoints } = require("../../utils/instanceMgr");
const { registerCleanupTask } = require("../../utils/cleanupManager"); const { registerCleanupTask } = require("../../utils/cleanupManager");
const getBullMQPrefix = require("../../utils/getBullMQPrefix"); const getBullMQPrefix = require("../../utils/getBullMQPrefix");
const devDebugLogger = require("../../utils/devDebugLogger"); const devDebugLogger = require("../../utils/devDebugLogger");
const moment = require("moment-timezone");
const EMAIL_CONSOLIDATION_DELAY_IN_MINS = (() => { const EMAIL_CONSOLIDATION_DELAY_IN_MINS = (() => {
const envValue = process.env?.EMAIL_CONSOLIDATION_DELAY_IN_MINS; const envValue = process.env?.EMAIL_CONSOLIDATION_DELAY_IN_MINS;
@@ -59,7 +60,7 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
emailAddWorker = new Worker( emailAddWorker = new Worker(
"emailAdd", "emailAdd",
async (job) => { async (job) => {
const { jobId, jobRoNumber, bodyShopName, body, recipients } = job.data; const { jobId, jobRoNumber, bodyShopName, bodyShopTimezone, body, recipients } = job.data;
devDebugLogger(`Adding email notifications for jobId ${jobId}`); devDebugLogger(`Adding email notifications for jobId ${jobId}`);
const redisKeyPrefix = `email:${devKey}:notifications:${jobId}`; const redisKeyPrefix = `email:${devKey}:notifications:${jobId}`;
@@ -72,6 +73,7 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
const detailsKey = `email:${devKey}:recipientDetails:${jobId}:${user}`; const detailsKey = `email:${devKey}:recipientDetails:${jobId}:${user}`;
await pubClient.hsetnx(detailsKey, "firstName", firstName || ""); await pubClient.hsetnx(detailsKey, "firstName", firstName || "");
await pubClient.hsetnx(detailsKey, "lastName", lastName || ""); await pubClient.hsetnx(detailsKey, "lastName", lastName || "");
await pubClient.hsetnx(detailsKey, "bodyShopTimezone", bodyShopTimezone);
await pubClient.expire(detailsKey, NOTIFICATION_EXPIRATION / 1000); await pubClient.expire(detailsKey, NOTIFICATION_EXPIRATION / 1000);
await pubClient.sadd(`email:${devKey}:recipients:${jobId}`, user); await pubClient.sadd(`email:${devKey}:recipients:${jobId}`, user);
devDebugLogger(`Stored message for ${user} under ${userKey}: ${body}`); devDebugLogger(`Stored message for ${user} under ${userKey}: ${body}`);
@@ -82,7 +84,7 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
if (flagSet) { if (flagSet) {
await emailConsolidateQueue.add( await emailConsolidateQueue.add(
"consolidate-emails", "consolidate-emails",
{ jobId, jobRoNumber, bodyShopName }, { jobId, jobRoNumber, bodyShopName, bodyShopTimezone },
{ {
jobId: `consolidate:${jobId}`, jobId: `consolidate:${jobId}`,
delay: EMAIL_CONSOLIDATION_DELAY, delay: EMAIL_CONSOLIDATION_DELAY,
@@ -125,9 +127,11 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
const firstName = details.firstName || "User"; const firstName = details.firstName || "User";
const multipleUpdateString = messages.length > 1 ? "Updates" : "Update"; const multipleUpdateString = messages.length > 1 ? "Updates" : "Update";
const subject = `${multipleUpdateString} for job ${jobRoNumber || "N/A"} at ${bodyShopName}`; const subject = `${multipleUpdateString} for job ${jobRoNumber || "N/A"} at ${bodyShopName}`;
const timezone = moment.tz.zone(details?.bodyShopTimezone) ? details.bodyShopTimezone : "UTC";
const emailBody = generateEmailTemplate({ const emailBody = generateEmailTemplate({
header: `${multipleUpdateString} for Job ${jobRoNumber || "N/A"}`, header: `${multipleUpdateString} for Job ${jobRoNumber || "N/A"}`,
subHeader: `Dear ${firstName},`, subHeader: `Dear ${firstName},`,
dateLine: moment().tz(timezone).format("MM/DD/YYYY hh:mm a"),
body: ` body: `
<p>There have been updates to job ${jobRoNumber || "N/A"} at ${bodyShopName}:</p><br/> <p>There have been updates to job ${jobRoNumber || "N/A"} at ${bodyShopName}:</p><br/>
<ul> <ul>
@@ -226,10 +230,10 @@ const dispatchEmailsToQueue = async ({ emailsToDispatch, logger }) => {
const emailAddQueue = getQueue(); const emailAddQueue = getQueue();
for (const email of emailsToDispatch) { for (const email of emailsToDispatch) {
const { jobId, jobRoNumber, bodyShopName, body, recipients } = email; const { jobId, jobRoNumber, bodyShopName, bodyShopTimezone, body, recipients } = email;
if (!jobId || !jobRoNumber || !bodyShopName || !body || !recipients.length) { if (!jobId || !jobRoNumber || !bodyShopName || !body || !recipients.length) {
logger.logger.warn( devDebugLogger(
`Skipping email dispatch for jobId ${jobId} due to missing data: ` + `Skipping email dispatch for jobId ${jobId} due to missing data: ` +
`jobRoNumber=${jobRoNumber || "N/A"}, bodyShopName=${bodyShopName}, body=${body}, recipients=${recipients.length}` `jobRoNumber=${jobRoNumber || "N/A"}, bodyShopName=${bodyShopName}, body=${body}, recipients=${recipients.length}`
); );
@@ -238,7 +242,7 @@ const dispatchEmailsToQueue = async ({ emailsToDispatch, logger }) => {
await emailAddQueue.add( await emailAddQueue.add(
"add-email-notification", "add-email-notification",
{ jobId, jobRoNumber, bodyShopName, body, recipients }, { jobId, jobRoNumber, bodyShopName, bodyShopTimezone, body, recipients },
{ jobId: `${jobId}:${Date.now()}` } { jobId: `${jobId}:${Date.now()}` }
); );
devDebugLogger(`Added email notification to queue for jobId ${jobId} with ${recipients.length} recipients`); devDebugLogger(`Added email notification to queue for jobId ${jobId} with ${recipients.length} recipients`);

View File

@@ -28,6 +28,7 @@ const buildNotification = (data, key, body, variables = {}) => {
jobId: data.jobId, jobId: data.jobId,
jobRoNumber: data.jobRoNumber, jobRoNumber: data.jobRoNumber,
bodyShopName: data.bodyShopName, bodyShopName: data.bodyShopName,
bodyShopTimezone: data.bodyShopTimezone,
body, body,
recipients: [] recipients: []
}, },