feature/IO-3000-messaging-sockets-migration2 -

-misc

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-11-27 11:27:34 -08:00
parent 70c31eae9e
commit 8d6fba2b61
4 changed files with 95 additions and 136 deletions

View File

@@ -2,39 +2,65 @@ import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
const logLocal = (message, ...args) => { const logLocal = (message, ...args) => {
if (import.meta.env.PROD) { if (import.meta.env.VITE_APP_IS_TEST || !import.meta.env.PROD) {
return; console.log(`==================== ${message} ====================`);
console.dir({ ...args });
} }
console.log(`==================== ${message} ====================`);
console.dir({ ...args });
}; };
// Utility function to enrich conversation data
const enrichConversation = (conversation, isOutbound) => ({
...conversation,
updated_at: conversation.updated_at || new Date().toISOString(),
unreadcnt: conversation.unreadcnt || 0,
archived: conversation.archived || false,
label: conversation.label || null,
job_conversations: conversation.job_conversations || [],
messages_aggregate: conversation.messages_aggregate || {
__typename: "messages_aggregate",
aggregate: {
__typename: "messages_aggregate_fields",
count: isOutbound ? 0 : 1
}
},
__typename: "conversations"
});
export const registerMessagingHandlers = ({ socket, client }) => { export const registerMessagingHandlers = ({ socket, client }) => {
if (!(socket && client)) return; if (!(socket && client)) return;
const handleNewMessageSummary = async (message) => { const handleNewMessageSummary = async (message) => {
const { conversationId, newConversation, existingConversation, isoutbound } = message; const { conversationId, newConversation, existingConversation, isoutbound } = message;
logLocal("handleNewMessageSummary - Start", { message, isNew: !existingConversation }); logLocal("handleNewMessageSummary - Start", { message, isNew: !existingConversation });
const queryVariables = { offset: 0 }; const queryVariables = { offset: 0 };
// Utility function to enrich conversation data if (!existingConversation && conversationId) {
const enrichConversation = (conversation, isOutbound) => ({ // Attempt to read from the cache to determine if this is actually a new conversation
...conversation, try {
updated_at: conversation.updated_at || new Date().toISOString(), const cachedConversation = client.cache.readFragment({
unreadcnt: conversation.unreadcnt || 0, id: client.cache.identify({ __typename: "conversations", id: conversationId }),
archived: conversation.archived || false, fragment: gql`
label: conversation.label || null, fragment ExistingConversationCheck on conversations {
job_conversations: conversation.job_conversations || [], id
messages_aggregate: conversation.messages_aggregate || { }
__typename: "messages_aggregate", `
aggregate: { });
__typename: "messages_aggregate_fields",
count: isOutbound ? 0 : 1 if (cachedConversation) {
logLocal("handleNewMessageSummary - Existing Conversation inferred from cache", {
conversationId
});
return handleNewMessageSummary({
...message,
existingConversation: true
});
} }
}, } catch (error) {
__typename: "conversations" logLocal("handleNewMessageSummary - Cache miss", { conversationId });
}); }
}
// Handle new conversation // Handle new conversation
if (!existingConversation && newConversation?.phone_num) { if (!existingConversation && newConversation?.phone_num) {
@@ -49,7 +75,6 @@ export const registerMessagingHandlers = ({ socket, client }) => {
const existingConversations = queryResults?.conversations || []; const existingConversations = queryResults?.conversations || [];
const enrichedConversation = enrichConversation(newConversation, isoutbound); const enrichedConversation = enrichConversation(newConversation, isoutbound);
// Avoid adding duplicate conversations
if (!existingConversations.some((conv) => conv.id === enrichedConversation.id)) { if (!existingConversations.some((conv) => conv.id === enrichedConversation.id)) {
client.cache.modify({ client.cache.modify({
id: "ROOT_QUERY", id: "ROOT_QUERY",
@@ -68,81 +93,7 @@ export const registerMessagingHandlers = ({ socket, client }) => {
// Handle existing conversation // Handle existing conversation
if (existingConversation) { if (existingConversation) {
let conversationDetails;
// Attempt to read existing conversation details from cache
try { try {
conversationDetails = client.cache.readFragment({
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
fragment: gql`
fragment ExistingConversation on conversations {
id
phone_num
updated_at
archived
label
unreadcnt
job_conversations {
jobid
conversationid
}
messages_aggregate {
aggregate {
count
}
}
__typename
}
`
});
} catch (error) {
logLocal("handleNewMessageSummary - Cache miss for conversation, fetching from server", { conversationId });
}
// Fetch conversation details from server if not in cache
if (!conversationDetails) {
try {
const { data } = await client.query({
query: GET_CONVERSATION_DETAILS,
variables: { conversationId },
fetchPolicy: "network-only"
});
conversationDetails = data?.conversations_by_pk;
} catch (error) {
console.error("Failed to fetch conversation details from server:", error);
return;
}
}
// Validate that conversation details were retrieved
if (!conversationDetails) {
console.error("Unable to retrieve conversation details. Skipping cache update.");
return;
}
try {
// Check if the conversation is already in the cache
const queryResults = client.cache.readQuery({
query: CONVERSATION_LIST_QUERY,
variables: queryVariables
});
const isAlreadyInCache = queryResults?.conversations.some((conv) => conv.id === conversationId);
if (!isAlreadyInCache) {
const enrichedConversation = enrichConversation(conversationDetails, isoutbound);
client.cache.modify({
id: "ROOT_QUERY",
fields: {
conversations(existingConversations = []) {
return [enrichedConversation, ...existingConversations];
}
}
});
}
// Update fields for the existing conversation in the cache
client.cache.modify({ client.cache.modify({
id: client.cache.identify({ __typename: "conversations", id: conversationId }), id: client.cache.identify({ __typename: "conversations", id: conversationId }),
fields: { fields: {
@@ -166,7 +117,11 @@ export const registerMessagingHandlers = ({ socket, client }) => {
} catch (error) { } catch (error) {
console.error("Error updating cache for existing conversation:", error); console.error("Error updating cache for existing conversation:", error);
} }
return;
} }
logLocal("New Conversation Summary finished without work", { message });
}; };
const handleNewMessageDetailed = (message) => { const handleNewMessageDetailed = (message) => {

View File

@@ -32,8 +32,13 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
return () => clearInterval(interval); // Cleanup on unmount return () => clearInterval(interval); // Cleanup on unmount
}, []); }, []);
// Memoize the sorted conversation list
const sortedConversationList = React.useMemo(() => {
return _.orderBy(conversationList, ["updated_at"], ["desc"]);
}, [conversationList]);
const renderConversation = (index) => { const renderConversation = (index) => {
const item = conversationList[index]; const item = sortedConversationList[index];
const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>; const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
const cardContentLeft = const cardContentLeft =
item.job_conversations.length > 0 item.job_conversations.length > 0
@@ -76,13 +81,10 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
); );
}; };
// CAN DO: Can go back into virtuoso for additional fetch
// endReached={loadMoreConversations} // Calls loadMoreConversations when scrolled to the bottom
return ( return (
<div className="chat-list-container"> <div className="chat-list-container">
<Virtuoso <Virtuoso
data={conversationList} data={sortedConversationList}
itemContent={(index) => renderConversation(index)} itemContent={(index) => renderConversation(index)}
style={{ height: "100%", width: "100%" }} style={{ height: "100%", width: "100%" }}
/> />

View File

@@ -20,10 +20,10 @@ export function ChatConversationTitleTags({ jobConversations, bodyshop }) {
const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG); const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG);
const { socket } = useContext(SocketContext); const { socket } = useContext(SocketContext);
const handleRemoveTag = (jobId) => { const handleRemoveTag = async (jobId) => {
const convId = jobConversations[0].conversationid; const convId = jobConversations[0].conversationid;
if (!!convId) { if (!!convId) {
removeJobConversation({ await removeJobConversation({
variables: { variables: {
conversationId: convId, conversationId: convId,
jobId: jobId jobId: jobId
@@ -38,17 +38,18 @@ export function ChatConversationTitleTags({ jobConversations, bodyshop }) {
} }
}); });
} }
}).then(() => {
if (socket) {
// Emit the `conversation-modified` event
socket.emit("conversation-modified", {
bodyshopId: bodyshop.id,
conversationId: convId,
type: "tag-removed",
jobId: jobId
});
}
}); });
if (socket) {
// Emit the `conversation-modified` event
socket.emit("conversation-modified", {
bodyshopId: bodyshop.id,
conversationId: convId,
type: "tag-removed",
jobId: jobId
});
}
logImEXEvent("messaging_remove_job_tag", { logImEXEvent("messaging_remove_job_tag", {
conversationId: convId, conversationId: convId,
jobId: jobId jobId: jobId

View File

@@ -28,7 +28,7 @@ export function ChatTagRoContainer({ conversation, bodyshop }) {
const executeSearch = (v) => { const executeSearch = (v) => {
logImEXEvent("messaging_search_job_tag", { searchTerm: v }); logImEXEvent("messaging_search_job_tag", { searchTerm: v });
loadRo(v); loadRo(v).catch((e) => console.error("Error in ChatTagRoContainer executeSearch:", e));
}; };
const debouncedExecuteSearch = _.debounce(executeSearch, 500); const debouncedExecuteSearch = _.debounce(executeSearch, 500);
@@ -41,33 +41,34 @@ export function ChatTagRoContainer({ conversation, bodyshop }) {
variables: { conversationId: conversation.id } variables: { conversationId: conversation.id }
}); });
const handleInsertTag = (value, option) => { const handleInsertTag = async (value, option) => {
logImEXEvent("messaging_add_job_tag"); logImEXEvent("messaging_add_job_tag");
insertTag({ await insertTag({
variables: { jobId: option.key } variables: { jobId: option.key }
}).then(() => {
if (socket) {
// 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]
});
}
}); });
if (socket) {
// 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]
});
}
setOpen(false); setOpen(false);
}; };