diff --git a/DSL/DMapper/hbs/return_open_chats_messages.handlebars b/DSL/DMapper/hbs/return_open_chats_messages.handlebars new file mode 100644 index 000000000..919e03040 --- /dev/null +++ b/DSL/DMapper/hbs/return_open_chats_messages.handlebars @@ -0,0 +1,12 @@ +[ + {{#each data}} + { + "channelId": "{{chatBaseId}}", + "timestamp": "{{getISODate}}", + "payload": { + "type": "message", + "messageId": "{{baseId}}" + } + }{{#unless @last}},{{/unless}} + {{/each}} +] diff --git a/DSL/DMapper/hbs/return_open_chats_notifications.handlebars b/DSL/DMapper/hbs/return_open_chats_notifications.handlebars new file mode 100644 index 000000000..a711aec2d --- /dev/null +++ b/DSL/DMapper/hbs/return_open_chats_notifications.handlebars @@ -0,0 +1,17 @@ +[ + {{#each data}} + { + "index": { + "_index": "notifications" + } + }, + { + "channelId": "{{chatBaseId}}", + "timestamp": "{{getISODate}}", + "payload": { + "type": "message", + "messageId": "{{baseId}}" + } + }{{#unless @last}},{{/unless}} + {{/each}} +] diff --git a/DSL/Resql/insert-message-content-by-customer-support-id.sql b/DSL/Resql/insert-message-content-by-customer-support-id.sql index 8e5f9383c..3f79bab80 100644 --- a/DSL/Resql/insert-message-content-by-customer-support-id.sql +++ b/DSL/Resql/insert-message-content-by-customer-support-id.sql @@ -17,4 +17,5 @@ WHERE chat_base_id IN (SELECT base_id WHERE id IN (SELECT max(id) FROM chat GROUP BY base_id) AND customer_support_id = :customerSupportId AND ended IS null) - AND id IN (SELECT max(id) FROM message GROUP BY chat_base_id); \ No newline at end of file + AND id IN (SELECT max(id) FROM message GROUP BY chat_base_id) +RETURNING id, chat_base_id, base_id; diff --git a/DSL/Ruuter.private/DSL/POST/bots/active.yml b/DSL/Ruuter.private/DSL/POST/bots/active.yml index 3713856ce..22868e489 100644 --- a/DSL/Ruuter.private/DSL/POST/bots/active.yml +++ b/DSL/Ruuter.private/DSL/POST/bots/active.yml @@ -26,20 +26,19 @@ setConfigurationValue: value: ${isActive} result: setConfigurationResult +check_if_bot_is_active: + switch: + - condition: ${isActive === 'true' || isActive === true} + next: return_result + next: insertChatAndMessageWhenBotNotActive + insertChatAndMessageWhenBotNotActive: call: http.post args: url: "[#CHATBOT_RUUTER_PRIVATE_INTERNAL]/internal/chat-and-message-when-bot-is-not-active" result: insertChatAndMessageWhenBotNotActiveResult - -getConfigurationValue: - call: http.post - args: - url: "[#CHATBOT_RESQL]/get-configuration" - body: - key: "is_bot_active" - result: res + next: return_result return_result: - return: ${res.response.body[0]?.value} + return: ${isActive} status: 200 diff --git a/DSL/Ruuter.private/DSL/POST/internal/chat-and-message-when-bot-is-not-active.yml b/DSL/Ruuter.private/DSL/POST/internal/chat-and-message-when-bot-is-not-active.yml index 66519cf8f..fd4e34b6a 100644 --- a/DSL/Ruuter.private/DSL/POST/internal/chat-and-message-when-bot-is-not-active.yml +++ b/DSL/Ruuter.private/DSL/POST/internal/chat-and-message-when-bot-is-not-active.yml @@ -19,23 +19,18 @@ assignBotName: assign: botName: ${bot_name_res.response.body[0].value} -endOldBotChats: +get_organization_base_config: call: http.post args: - url: "[#CHATBOT_RESQL]/insert-chat-ended-by-last-message-datetime" - body: - authorId: ${botName} - authorRole: "buerokratt" - content: "Teie vestlus antakse üle nõustajale." - currentDatetime: ${new Date().toISOString()} - currentStatus: "OPEN" - event: "inactive-chat-ended" - interval: "2 hours" - newStatus: "ENDED" - targetUser: "end-user" - result: end_old_bot_chats_res - -insertMessageContent: + url: "[#CHATBOT_RESQL]/get-organization-base-config" + result: org_base_config_res + next: assign_organization_base_config + +assign_organization_base_config: + assign: + base_config: ${org_base_config_res.response.body[0]} + +notify_chats_and_return_chats_and_messages: call: http.post args: url: "[#CHATBOT_RESQL]/insert-message-content-by-customer-support-id" @@ -43,19 +38,39 @@ insertMessageContent: customerSupportId: ${botName} authorId: ${botName} authorRole: "buerokratt" - content: "Teie vestlus antakse üle nõustajale." - event: "" + content: ${base_config.outsideWorkingHoursMessage} + event: "${base_config.outsideWorkingHoursAskForContacts === 'true' ? 'unavailable_organization_ask_contacts' : 'unavailable_organization'}" created: ${new Date().toISOString()} - result: insert_message_content_res + result: res -insertChatCustomerSupport: +map_open_chats_messages: call: http.post args: - url: "[#CHATBOT_RESQL]/insert-chat-customer-support-by-customer-support-id" + url: "[#CHATBOT_DMAPPER]/hbs/chat-bot/return_open_chats_messages" + headers: + type: json body: - customerSupportId: ${botName} - result: insert_chat_customer_support_res + data: ${res.response.body} + result: open_chat_messages_res + +map_open_chats_notifications: + call: http.post + args: + url: "[#CHATBOT_DMAPPER]/hbs/chat-bot/return_open_chats_notifications" + headers: + type: json + body: + data: ${res.response.body} + result: open_chat_notifications_res + +notify_end_users: + template: messages/notify-all + requestType: templates + body: + chats: ${open_chat_messages_res.response.body} + notifications: ${open_chat_notifications_res.response.body} + result: notification_res return_result: - return: "success" + return: "Success" next: end diff --git a/DSL/Ruuter.private/DSL/TEMPLATES/messages/notify-all.yml b/DSL/Ruuter.private/DSL/TEMPLATES/messages/notify-all.yml new file mode 100644 index 000000000..fa4501854 --- /dev/null +++ b/DSL/Ruuter.private/DSL/TEMPLATES/messages/notify-all.yml @@ -0,0 +1,20 @@ +clear_notifications: + call: http.post + args: + url: "[#CHATBOT_OPENSEARCH]/notifications/_delete_by_query?conflicts=proceed" + body: + query: + terms: + channelId: ${incoming.body.chats.map(chat => chat.channelId)} + result: clear_res + +notify: + call: http.post + args: + url: "[#CHATBOT_NOTIFICATIONS]/bulk-notifications" + body: + operations: ${incoming.body.notifications} + result: notify_res + +return_result: + return: "Notification sent" diff --git a/GUI/src/components/Card/Card.scss b/GUI/src/components/Card/Card.scss index 2d64e5048..4a67007f7 100644 --- a/GUI/src/components/Card/Card.scss +++ b/GUI/src/components/Card/Card.scss @@ -20,6 +20,10 @@ } } + &--scrollable { + overflow-y: auto; + } + &__header, &__body, &__footer { diff --git a/GUI/src/components/Card/index.tsx b/GUI/src/components/Card/index.tsx index 128ba81fb..3bf541f9d 100644 --- a/GUI/src/components/Card/index.tsx +++ b/GUI/src/components/Card/index.tsx @@ -9,6 +9,7 @@ type CardProps = { borderless?: boolean; isHeaderLight?: boolean; isBodyDivided?: boolean; + isScrollable?: boolean; }; const Card: FC> = ({ @@ -17,10 +18,11 @@ const Card: FC> = ({ borderless, isHeaderLight, isBodyDivided, + isScrollable = false, children, }) => { return ( -
+
{header && (
{header} diff --git a/GUI/src/pages/Settings/SettingsWorkingTime/index.tsx b/GUI/src/pages/Settings/SettingsWorkingTime/index.tsx index 472115262..f63e87c5e 100644 --- a/GUI/src/pages/Settings/SettingsWorkingTime/index.tsx +++ b/GUI/src/pages/Settings/SettingsWorkingTime/index.tsx @@ -160,6 +160,7 @@ const SettingsWorkingTime: FC = () => { key={key} isHeaderLight={true} isBodyDivided={true} + isScrollable={true} footer={ diff --git a/notification-server/src/openSearch.js b/notification-server/src/openSearch.js index c91fb5d5b..1d9c54dd6 100644 --- a/notification-server/src/openSearch.js +++ b/notification-server/src/openSearch.js @@ -28,6 +28,10 @@ async function searchNotification({ channelId, connectionId, sender }) { } } +async function sendBulkNotification({ operations }) { + await client.bulk({ body: operations }); +} + async function markAsSent({ _index, _id }, connectionId) { await client.update({ index: _index, @@ -145,4 +149,5 @@ module.exports = { enqueueChatId, dequeueChatId, findChatIdOrder, + sendBulkNotification }; diff --git a/notification-server/src/server.js b/notification-server/src/server.js index 789c21dfb..98e5d233e 100644 --- a/notification-server/src/server.js +++ b/notification-server/src/server.js @@ -6,7 +6,7 @@ const { buildNotificationSearchInterval, buildQueueCounter, } = require("./addOns"); -const { enqueueChatId, dequeueChatId } = require("./openSearch"); +const { enqueueChatId, dequeueChatId, sendBulkNotification } = require("./openSearch"); const { addToTerminationQueue, removeFromTerminationQueue } = require("./terminationQueue"); const helmet = require("helmet"); const cookieParser = require("cookie-parser"); @@ -38,6 +38,15 @@ app.get("/sse/queue/:id", (req, res) => { }); }); +app.post("/bulk-notifications", async (req, res) => { + try { + await sendBulkNotification(req.body); + res.status(200).json({ response: 'sent successfully' }); + } catch { + res.status(500).json({ response: 'error' }); + } +}); + app.post("/enqueue", async (req, res) => { try{ await enqueueChatId(req.body.id);