feature/IO-3096-GlobalNotifications - Checkpoint, Ratify notifications tb table.

This commit is contained in:
Dave Richer
2025-02-24 12:52:10 -05:00
parent a5cf81bd28
commit 0f067fc503
16 changed files with 101 additions and 82 deletions

View File

@@ -4893,46 +4893,78 @@
- name: job
using:
foreign_key_constraint_on: jobid
insert_permissions:
- role: user
permission:
check:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- scenario_meta
- scenario_text
- fcm_text
- created_at
- read
- updated_at
- associationid
- id
- jobid
comment: ""
select_permissions:
- role: user
permission:
columns:
- associationid
- scenario_meta
- scenario_text
- fcm_text
- created_at
- fcm_data
- fcm_message
- fcm_title
- read
- updated_at
- associationid
- id
- jobid
- meta
- read
- ui_translation_meta
- ui_translation_string
- updated_at
filter:
association:
job:
bodyshop:
associations:
_and:
- active:
_eq: true
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
comment: ""
update_permissions:
- role: user
permission:
columns:
- meta
- scenario_meta
- scenario_text
- fcm_text
- created_at
- read
filter:
association:
- updated_at
- associationid
- id
- jobid
filter: {}
check:
job:
bodyshop:
associations:
_and:
- active:
_eq: true
- user:
authid:
_eq: X-Hasura-User-Id
check: null
- active:
_eq: true
comment: ""
- table:
name: owners

View File

@@ -0,0 +1,3 @@
comment on column "public"."notifications"."html_body" is E'Real Time Notifications System';
alter table "public"."notifications" alter column "html_body" drop not null;
alter table "public"."notifications" add column "html_body" text;

View File

@@ -0,0 +1 @@
alter table "public"."notifications" drop column "html_body" cascade;

View File

@@ -0,0 +1,4 @@
comment on column "public"."notifications"."fcm_data" is E'Real Time Notifications System';
alter table "public"."notifications" alter column "fcm_data" set default jsonb_build_object();
alter table "public"."notifications" alter column "fcm_data" drop not null;
alter table "public"."notifications" add column "fcm_data" jsonb;

View File

@@ -0,0 +1 @@
alter table "public"."notifications" drop column "fcm_data" cascade;

View File

@@ -0,0 +1,3 @@
comment on column "public"."notifications"."fcm_message" is E'Real Time Notifications System';
alter table "public"."notifications" alter column "fcm_message" drop not null;
alter table "public"."notifications" add column "fcm_message" text;

View File

@@ -0,0 +1 @@
alter table "public"."notifications" drop column "fcm_message" cascade;

View File

@@ -0,0 +1,3 @@
comment on column "public"."notifications"."ui_translation_string" is E'Real Time Notifications System';
alter table "public"."notifications" alter column "ui_translation_string" drop not null;
alter table "public"."notifications" add column "ui_translation_string" text;

View File

@@ -0,0 +1 @@
alter table "public"."notifications" drop column "ui_translation_string" cascade;

View File

@@ -0,0 +1 @@
alter table "public"."notifications" rename column "fcm_text" to "fcm_title";

View File

@@ -0,0 +1 @@
alter table "public"."notifications" rename column "fcm_title" to "fcm_text";

View File

@@ -0,0 +1 @@
alter table "public"."notifications" rename column "scenario_text" to "ui_translation_meta";

View File

@@ -0,0 +1 @@
alter table "public"."notifications" rename column "ui_translation_meta" to "scenario_text";

View File

@@ -0,0 +1 @@
alter table "public"."notifications" rename column "scenario_meta" to "meta";

View File

@@ -0,0 +1 @@
alter table "public"."notifications" rename column "meta" to "scenario_meta";

View File

@@ -20,7 +20,7 @@ const RATE_LIMITER_DURATION = APP_CONSOLIDATION_DELAY * 0.1; // 6 seconds (tenth
let addQueue;
let consolidateQueue;
// GraphQL mutation to insert notifications
// Updated GraphQL mutation to insert notifications with the new schema
const INSERT_NOTIFICATIONS_MUTATION = `
mutation INSERT_NOTIFICATIONS($objects: [notifications_insert_input!]!) {
insert_notifications(objects: $objects) {
@@ -29,61 +29,34 @@ const INSERT_NOTIFICATIONS_MUTATION = `
id
jobid
associationid
ui_translation_string
ui_translation_meta
html_body
scenario_text
fcm_text
scenario_meta
}
}
}
`;
/**
* Builds an HTML unordered list from an array of notification bodies.
* Builds the scenario_text, fcm_text, and scenario_meta for a batch of notifications.
*
* @param {Array<Object>} notifications - Array of notification objects with a 'body' field.
* @returns {string} HTML string representing an unordered list of bodies.
* @param {Array<Object>} notifications - Array of notification objects with 'body' and 'variables'.
* @returns {Object} An object with 'scenario_text', 'fcm_text', and 'scenario_meta'.
*/
const buildHtmlBody = (notifications) => {
const listItems = notifications.map((n) => `<li>${n.body}</li>`).join("");
return `<ul>${listItems}</ul>`;
};
const buildNotificationContent = (notifications) => {
const scenarioText = notifications.map((n) => n.body); // Array of text entries
const fcmText = scenarioText.join(". "); // Concatenated text with period separator
const scenarioMeta = notifications.map((n) => n.variables || {}); // Array of metadata objects
/**
* Determines the key and variables for a batch of notifications.
*
* @param {Array<Object>} notifications - Array of notification objects with 'key' and 'variables'.
* @returns {Object} An object with 'key' and 'variables' properties.
*/
const determineKeyAndVariables = (notifications) => {
if (notifications.length === 1) {
// Single notification: use the original key and variables
return {
key: notifications[0].key,
variables: notifications[0].variables
scenario_text: scenarioText,
fcm_text: fcmText ? `${fcmText}.` : null, // Add trailing period if non-empty, otherwise null
scenario_meta: scenarioMeta
};
} else {
// Multiple notifications: use a generic key and consolidate variables with their original keys
return {
key: "notifications.job.multipleChanges",
variables: {
variables: notifications.map((n) => ({
key: n.key, // Include the original key in each variables object
...n.variables
}))
}
};
}
};
/**
* Initializes the notification queues and workers for adding and consolidating notifications.
*
* @param {Object} options - Configuration options for queue initialization.
* @param {Object} options.pubClient - Redis client instance for queue communication.
* @param {Object} options.logger - Logger instance for logging events and debugging.
* @param {Object} options.redisHelpers - Utility functions for Redis operations.
* @param {Object} options.ioRedis - Socket.io Redis adapter for real-time event emission.
* @returns {Queue} The initialized `addQueue` instance for dispatching notifications.
*/
const loadAppQueue = async ({ pubClient, logger, redisHelpers, ioRedis }) => {
if (!addQueue || !consolidateQueue) {
@@ -195,14 +168,13 @@ const loadAppQueue = async ({ pubClient, logger, redisHelpers, ioRedis }) => {
const employeeId = userRecipients[0]?.employeeId;
for (const [bodyShopId, notifications] of Object.entries(bodyShopData)) {
const { key, variables } = determineKeyAndVariables(notifications);
const htmlBody = buildHtmlBody(notifications);
const { scenario_text, fcm_text, scenario_meta } = buildNotificationContent(notifications);
notificationInserts.push({
jobid: jobId,
associationid: employeeId || null,
ui_translation_string: key,
ui_translation_meta: JSON.stringify(variables),
html_body: htmlBody
scenario_text: JSON.stringify(scenario_text), // JSONB requires stringified input
fcm_text: fcm_text,
scenario_meta: JSON.stringify(scenario_meta) // JSONB requires stringified input
});
notificationIdMap.set(`${user}:${bodyShopId}`, null);
}
@@ -301,9 +273,6 @@ const loadAppQueue = async ({ pubClient, logger, redisHelpers, ioRedis }) => {
/**
* Retrieves the initialized `addQueue` instance.
*
* @returns {Queue} The `addQueue` instance for adding notifications.
* @throws {Error} If `addQueue` is not initialized (i.e., `loadAppQueue` wasnt called).
*/
const getQueue = () => {
if (!addQueue) throw new Error("Add queue not initialized. Ensure loadAppQueue is called during bootstrap.");
@@ -312,11 +281,6 @@ const getQueue = () => {
/**
* Dispatches notifications to the `addQueue` for processing.
*
* @param {Object} options - Options for dispatching notifications.
* @param {Array} options.appsToDispatch - Array of notification objects to dispatch.
* @param {Object} options.logger - Logger instance for logging dispatch events.
* @returns {Promise<void>} Resolves when all notifications are added to the queue.
*/
const dispatchAppsToQueue = async ({ appsToDispatch, logger }) => {
const appQueue = getQueue();