From 15ba2a1caf1b65836ecff0a82ddcd4e6bbbe9a57 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 3 Dec 2024 09:48:52 -0800 Subject: [PATCH 1/4] feature/IO-3048-Fix-Job-Bug-Messaging - Fix tag weirdness and a vite error Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 37 ++++++++++++++++++- .../chat-message-list.component.jsx | 10 ++--- .../chat-tag-ro/chat-tag-ro.container.jsx | 26 ++++++++----- client/src/utils/GraphQLClient.js | 33 +---------------- 4 files changed, 59 insertions(+), 47 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index a6c7cc33a..41d65427d 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -334,12 +334,47 @@ export const registerMessagingHandlers = ({ socket, client }) => { break; case "tag-added": + // Ensure `job_conversations` is properly formatted + const formattedJobConversations = job_conversations.map((jc) => ({ + __typename: "job_conversations", + jobid: jc.jobid || jc.job?.id, + conversationid: conversationId, + job: jc.job || { + __typename: "jobs", + id: data.selectedJob.id, + ro_number: data.selectedJob.ro_number, + ownr_co_nm: data.selectedJob.ownr_co_nm, + ownr_fn: data.selectedJob.ownr_fn, + ownr_ln: data.selectedJob.ownr_ln + } + })); + client.cache.modify({ id: client.cache.identify({ __typename: "conversations", id: conversationId }), fields: { - job_conversations: (existing = []) => [...existing, ...job_conversations] + job_conversations: (existing = []) => { + // Ensure no duplicates based on `jobid` + const existingIds = new Set( + existing.map( + (jc) => + client.cache.readFragment({ + id: client.cache.identify(jc), + fragment: gql` + fragment JobConversationId on job_conversations { + jobid + } + ` + })?.jobid + ) + ); + + const newItems = formattedJobConversations.filter((jc) => !existingIds.has(jc.jobid)); + + return [...existing, ...newItems]; + } } }); + break; case "tag-removed": diff --git a/client/src/components/chat-messages-list/chat-message-list.component.jsx b/client/src/components/chat-messages-list/chat-message-list.component.jsx index f045cae8c..3f2bba2f0 100644 --- a/client/src/components/chat-messages-list/chat-message-list.component.jsx +++ b/client/src/components/chat-messages-list/chat-message-list.component.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; import { Virtuoso } from "react-virtuoso"; import { renderMessage } from "./renderMessage"; import "./chat-message-list.styles.scss"; @@ -16,7 +16,7 @@ export default function ChatMessageListComponent({ messages }) { loadedImagesRef.current = 0; }; - const preloadImages = (imagePaths, onComplete) => { + const preloadImages = useCallback((imagePaths, onComplete) => { resetImageLoadState(); if (imagePaths.length === 0) { @@ -34,7 +34,7 @@ export default function ChatMessageListComponent({ messages }) { } }; }); - }; + }, []); // Ensure all images are loaded on initial render useEffect(() => { @@ -51,7 +51,7 @@ export default function ChatMessageListComponent({ messages }) { }); } }); - }, [messages]); + }, [messages, preloadImages]); // Handle scrolling when new messages are added useEffect(() => { @@ -69,7 +69,7 @@ export default function ChatMessageListComponent({ messages }) { }); } }); - }, [messages, atBottom]); + }, [messages, atBottom, preloadImages]); return (
diff --git a/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx b/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx index 1bb791b00..63839b263 100644 --- a/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx +++ b/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx @@ -52,20 +52,26 @@ export function ChatTagRoContainer({ conversation, bodyshop }) { // Find the job details from the search data const selectedJob = data?.search_jobs.find((job) => job.id === option.key); if (!selectedJob) return; - const newJobConversation = { - __typename: "job_conversations", - jobid: selectedJob.id, - conversationid: conversation.id, - job: { - __typename: "jobs", - ...selectedJob - } - }; socket.emit("conversation-modified", { conversationId: conversation.id, bodyshopId: bodyshop.id, type: "tag-added", - job_conversations: [newJobConversation] + selectedJob, + job_conversations: [ + { + __typename: "job_conversations", + jobid: selectedJob.id, + conversationid: conversation.id, + job: { + __typename: "jobs", + id: selectedJob.id, + ro_number: selectedJob.ro_number, + ownr_co_nm: selectedJob.ownr_co_nm, + ownr_fn: selectedJob.ownr_fn, + ownr_ln: selectedJob.ownr_ln + } + } + ] }); } diff --git a/client/src/utils/GraphQLClient.js b/client/src/utils/GraphQLClient.js index c60545f02..689083139 100644 --- a/client/src/utils/GraphQLClient.js +++ b/client/src/utils/GraphQLClient.js @@ -3,7 +3,7 @@ import { setContext } from "@apollo/client/link/context"; import { HttpLink } from "@apollo/client/link/http"; //"apollo-link-http"; import { RetryLink } from "@apollo/client/link/retry"; import { WebSocketLink } from "@apollo/client/link/ws"; -import { getMainDefinition, offsetLimitPagination } from "@apollo/client/utilities"; +import { getMainDefinition } from "@apollo/client/utilities"; //import { split } from "apollo-link"; import apolloLogger from "apollo-link-logger"; //import axios from "axios"; @@ -143,36 +143,7 @@ middlewares.push( new SentryLink().concat(roundTripLink.concat(retryLink.concat(errorLink.concat(authLink.concat(link))))) ); -const cache = new InMemoryCache({ - typePolicies: { - conversations: { - fields: { - job_conversations: { - merge(existing = [], incoming = [], { readField }) { - const merged = new Map(); - - // Add existing data to the map - existing.forEach((jobConversation) => { - // Use `readField` to get the unique `jobid`, fallback to `__ref` - const jobId = readField("jobid", jobConversation) || jobConversation.__ref; - if (jobId) merged.set(jobId, jobConversation); - }); - - // Add or replace with incoming data - incoming.forEach((jobConversation) => { - // Use `readField` to get the unique `jobid`, fallback to `__ref` - const jobId = readField("jobid", jobConversation) || jobConversation.__ref; - if (jobId) merged.set(jobId, jobConversation); - }); - - // Return the merged data as an array - return Array.from(merged.values()); - } - } - } - } - } -}); +const cache = new InMemoryCache({}); const client = new ApolloClient({ link: ApolloLink.from(middlewares), cache, From 44721019fa3ec0b2217326a7c0dd0b23a84849e4 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 3 Dec 2024 10:39:14 -0800 Subject: [PATCH 2/4] feature/IO-3048-Fix-Job-Bug-Messaging - Do not allow more than 1 of the same job to be associated with a conversation Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index 41d65427d..56a2dbe70 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -353,22 +353,26 @@ export const registerMessagingHandlers = ({ socket, client }) => { id: client.cache.identify({ __typename: "conversations", id: conversationId }), fields: { job_conversations: (existing = []) => { - // Ensure no duplicates based on `jobid` - const existingIds = new Set( - existing.map( - (jc) => - client.cache.readFragment({ - id: client.cache.identify(jc), - fragment: gql` - fragment JobConversationId on job_conversations { - jobid - } - ` - })?.jobid - ) + // Ensure no duplicates based on both `conversationid` and `jobid` + const existingLinks = new Set( + existing.map((jc) => { + const jobId = client.cache.readFragment({ + id: client.cache.identify(jc), + fragment: gql` + fragment JobConversationLink on job_conversations { + jobid + conversationid + } + ` + })?.jobid; + return `${jobId}:${conversationId}`; // Unique identifier for a job-conversation link + }) ); - const newItems = formattedJobConversations.filter((jc) => !existingIds.has(jc.jobid)); + const newItems = formattedJobConversations.filter((jc) => { + const uniqueLink = `${jc.jobid}:${jc.conversationid}`; + return !existingLinks.has(uniqueLink); + }); return [...existing, ...newItems]; } From b490ab96be82cc0ccda02728ebf2f5ab90e20c14 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 3 Dec 2024 12:17:11 -0800 Subject: [PATCH 3/4] feature/IO-3048-Fix-Job-Bug-Messaging - Do not allow more than 1 of the same job to be associated with a conversation Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index 56a2dbe70..f2d9d1ef9 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -359,7 +359,7 @@ export const registerMessagingHandlers = ({ socket, client }) => { const jobId = client.cache.readFragment({ id: client.cache.identify(jc), fragment: gql` - fragment JobConversationLink on job_conversations { + fragment JobConversationLinkAdded on job_conversations { jobid conversationid } @@ -382,20 +382,27 @@ export const registerMessagingHandlers = ({ socket, client }) => { break; case "tag-removed": - client.cache.modify({ - id: client.cache.identify({ __typename: "conversations", id: conversationId }), - fields: { - job_conversations: (existing = [], { readField }) => { - return existing.filter((jobRef) => { - // Read the `jobid` field safely, even if the structure is normalized - const jobId = readField("jobid", jobRef); - return jobId !== fields.jobId; - }); - } - } - }); - break; + try { + const conversationCacheId = client.cache.identify({ __typename: "conversations", id: conversationId }); + // Evict the specific cache entry for job_conversations + client.cache.evict({ + id: conversationCacheId, + fieldName: "job_conversations" + }); + + // Garbage collect evicted entries + client.cache.gc(); + + logLocal("handleConversationChanged - tag removed - Refetched conversation list after state change", { + conversationId, + type + }); + } catch (error) { + console.error("Error refetching queries after conversation state change: (Tag Removed)", error); + } + + break; default: logLocal("handleConversationChanged - Unhandled type", { type }); client.cache.modify({ From a2ada7d88ebf17063e36d7ca66453314284dad42 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 3 Dec 2024 13:58:16 -0800 Subject: [PATCH 4/4] feature/IO-3048-Fix-Job-Bug-Messaging - Unread count Signed-off-by: Dave Richer --- .../chat-popup/chat-popup.component.jsx | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx index 443dfde3e..96a87885e 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -1,11 +1,11 @@ import { InfoCircleOutlined, MessageOutlined, ShrinkOutlined, SyncOutlined } from "@ant-design/icons"; -import { useApolloClient, useLazyQuery } from "@apollo/client"; +import { useApolloClient, useLazyQuery, useQuery } from "@apollo/client"; import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd"; import React, { useContext, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { CONVERSATION_LIST_QUERY } from "../../graphql/conversations.queries"; +import { CONVERSATION_LIST_QUERY, UNREAD_CONVERSATION_COUNT } from "../../graphql/conversations.queries"; import { toggleChatVisible } from "../../redux/messaging/messaging.actions"; import { selectChatVisible, selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; import ChatConversationListComponent from "../chat-conversation-list/chat-conversation-list.component"; @@ -38,6 +38,14 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh ...(pollInterval > 0 ? { pollInterval } : {}) }); + // Query for unread count when chat is not visible + const { data: unreadData } = useQuery(UNREAD_CONVERSATION_COUNT, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + skip: chatVisible, // Skip when chat is visible + ...(pollInterval > 0 ? { pollInterval } : {}) + }); + // Socket connection status useEffect(() => { const handleSocketStatus = () => { @@ -77,23 +85,29 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh // Get unread count from the cache const unreadCount = (() => { - try { - const cachedData = client.readQuery({ - query: CONVERSATION_LIST_QUERY, - variables: { offset: 0 } - }); + if (chatVisible) { + try { + const cachedData = client.readQuery({ + query: CONVERSATION_LIST_QUERY, + variables: { offset: 0 } + }); - if (!cachedData?.conversations) return 0; + if (!cachedData?.conversations) return 0; - // Aggregate unread message count - return cachedData.conversations.reduce((total, conversation) => { - const unread = conversation.messages_aggregate?.aggregate?.count || 0; - return total + unread; - }, 0); - } catch (error) { - console.warn("Unread count not found in cache:", error); - return 0; // Fallback if not in cache + // Aggregate unread message count + return cachedData.conversations.reduce((total, conversation) => { + const unread = conversation.messages_aggregate?.aggregate?.count || 0; + return total + unread; + }, 0); + } catch (error) { + console.warn("Unread count not found in cache:", error); + return 0; // Fallback if not in cache + } + } else if (unreadData?.messages_aggregate?.aggregate?.count) { + // Use the unread count from the query result + return unreadData.messages_aggregate.aggregate.count; } + return 0; })(); return (