diff --git a/.changeset/bump-patch-1760546047114.md b/.changeset/bump-patch-1760546047114.md new file mode 100644 index 0000000000000..e1eaa7980afb1 --- /dev/null +++ b/.changeset/bump-patch-1760546047114.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Bump @rocket.chat/meteor version. diff --git a/.changeset/rare-schools-laugh.md b/.changeset/rare-schools-laugh.md new file mode 100644 index 0000000000000..69cd0af9040d6 --- /dev/null +++ b/.changeset/rare-schools-laugh.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixes real-time monitoring displaying incorrect data diff --git a/apps/meteor/app/livechat/client/lib/chartHandler.ts b/apps/meteor/app/livechat/client/lib/chartHandler.ts index 55f4baafd5924..9e86c5d323513 100644 --- a/apps/meteor/app/livechat/client/lib/chartHandler.ts +++ b/apps/meteor/app/livechat/client/lib/chartHandler.ts @@ -209,3 +209,12 @@ export const updateChart = async ( chart.update(); }; + +export const resetChart = (chart: chartjs.Chart): void => { + chart.data.labels = []; + chart.data.datasets.forEach((dataset) => { + dataset.data = []; + }); + + chart.update(); +}; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.tsx b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.tsx index c3e98e6762619..742876c1498a7 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.tsx +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.tsx @@ -9,6 +9,7 @@ import { useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import Chart from './Chart'; +import { useChartContext } from './useChartContext'; import { useUpdateChartData } from './useUpdateChartData'; import { drawDoughnutChart } from '../../../../../app/livechat/client/lib/chartHandler'; import { omnichannelQueryKeys } from '../../../../lib/queryKeys'; @@ -39,40 +40,41 @@ const AgentStatusChart = ({ departmentId, ...props }: AgentStatusChartsProps) => const { t } = useTranslation(); const canvas: MutableRefObject = useRef(null); - const context: MutableRefObject | undefined> = useRef(); - - const updateChartData = useUpdateChartData({ - context, - canvas, - t, - init, - }); const getAgentStatus = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts/agents-status'); const { isSuccess, data: { offline = 0, available = 0, away = 0, busy = 0 } = initialData } = useQuery({ queryKey: omnichannelQueryKeys.analytics.agentsStatus(departmentId), queryFn: () => getAgentStatus({ departmentId }), + gcTime: 0, }); - useEffect(() => { - const initChart = async () => { - if (!canvas.current) { - return; - } + const context = useChartContext({ + canvas, + init, + t, + }); - context.current = await init(canvas.current, context.current, t); - }; - initChart(); - }, [t]); + const updateChartData = useUpdateChartData({ + context, + canvas, + init, + t, + }); useEffect(() => { - if (!isSuccess) return; + if (!context) { + return; + } + + if (!isSuccess) { + return; + } updateChartData(t('Offline'), [offline]); updateChartData(t('Available'), [available]); updateChartData(t('Away'), [away]); updateChartData(t('Busy'), [busy]); - }, [available, away, busy, offline, isSuccess, t, updateChartData]); + }, [context, available, away, busy, offline, isSuccess, t, updateChartData]); return ; }; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatDurationChart.tsx b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatDurationChart.tsx index e7349209c8c48..3340d590da44a 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatDurationChart.tsx +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatDurationChart.tsx @@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next'; import Chart from './Chart'; import { getMomentChartLabelsAndData } from './getMomentChartLabelsAndData'; import { getMomentCurrentLabel } from './getMomentCurrentLabel'; +import { useChartContext } from './useChartContext'; import { useUpdateChartData } from './useUpdateChartData'; import { drawLineChart } from '../../../../../app/livechat/client/lib/chartHandler'; import { secondsToHHMMSS } from '../../../../../lib/utils/secondsToHHMMSS'; @@ -48,19 +49,25 @@ const ChatDurationChart = ({ departmentId, dateRange, ...props }: ChatDurationCh const { t } = useTranslation(); const canvas = useRef(null); - const context = useRef>(); - - const updateChartData = useUpdateChartData({ - context, - canvas, - t, - init, - }); const getTimings = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts/timings'); const { isSuccess, data } = useQuery({ queryKey: omnichannelQueryKeys.analytics.timings(departmentId, dateRange), queryFn: () => getTimings({ departmentId, ...dateRange }), + gcTime: 0, + }); + + const context = useChartContext({ + canvas, + init, + t, + }); + + const updateChartData = useUpdateChartData({ + context, + canvas, + init, + t, }); const { @@ -73,22 +80,17 @@ const ChatDurationChart = ({ departmentId, dateRange, ...props }: ChatDurationCh }; useEffect(() => { - const initChart = async () => { - if (!canvas.current) { - return; - } + if (!context) { + return; + } - context.current = await init(canvas.current, context.current, t); - }; - initChart(); - }, [t]); - - useEffect(() => { - if (!isSuccess) return; + if (!isSuccess) { + return; + } const label = getMomentCurrentLabel(); updateChartData(label, [avg, longest]); - }, [avg, longest, isSuccess, t, updateChartData]); + }, [context, avg, longest, isSuccess, t, updateChartData]); return ; }; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.tsx b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.tsx index 4cea9cbd2fb54..42bd57a98dee8 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.tsx +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.tsx @@ -9,6 +9,7 @@ import { useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import Chart from './Chart'; +import { useChartContext } from './useChartContext'; import { useUpdateChartData } from './useUpdateChartData'; import { drawDoughnutChart } from '../../../../../app/livechat/client/lib/chartHandler'; import { omnichannelQueryKeys } from '../../../../lib/queryKeys'; @@ -40,41 +41,43 @@ const ChatsChart = ({ departmentId, dateRange, ...props }: ChatsChartProps) => { const { t } = useTranslation(); const canvas: MutableRefObject = useRef(null); - const context: MutableRefObject | undefined> = useRef(); - - const updateChartData = useUpdateChartData({ - context, - canvas, - t, - init, - }); const getChats = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts/chats'); const { isSuccess, data } = useQuery({ queryKey: omnichannelQueryKeys.analytics.chats(departmentId, dateRange), queryFn: () => getChats({ departmentId, ...dateRange }), + gcTime: 0, + }); + + const context = useChartContext({ + canvas, + init, + t, + }); + + const updateChartData = useUpdateChartData({ + context, + canvas, + init, + t, }); const { open, queued, closed, onhold } = data ?? initialData; useEffect(() => { - const initChart = async () => { - if (!canvas.current) { - return; - } - context.current = await init(canvas.current, context.current, t); - }; - initChart(); - }, [t]); + if (!context) { + return; + } - useEffect(() => { - if (!isSuccess) return; + if (!isSuccess) { + return; + } updateChartData(t('Open'), [open]); updateChartData(t('Closed'), [closed]); updateChartData(t('On_Hold_Chats'), [onhold]); updateChartData(t('Queued'), [queued]); - }, [closed, open, queued, onhold, isSuccess, t, updateChartData]); + }, [context, closed, open, queued, onhold, isSuccess, t, updateChartData]); return ; }; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.tsx b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.tsx index 3dc37a99f1909..9d09de4f39248 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.tsx +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.tsx @@ -9,8 +9,9 @@ import { useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import Chart from './Chart'; +import { useChartContext } from './useChartContext'; import { useUpdateChartData } from './useUpdateChartData'; -import { drawLineChart } from '../../../../../app/livechat/client/lib/chartHandler'; +import { drawLineChart, resetChart } from '../../../../../app/livechat/client/lib/chartHandler'; import { omnichannelQueryKeys } from '../../../../lib/queryKeys'; const init = (canvas: HTMLCanvasElement, context: chartjs.Chart<'line'> | undefined, t: TFunction) => @@ -29,42 +30,41 @@ const ChatsPerAgentChart = ({ departmentId, dateRange, ...props }: ChatsPerAgent const { t } = useTranslation(); const canvas = useRef(null); - const context = useRef>(); - - const updateChartData = useUpdateChartData({ - context, - canvas, - t, - init, - }); const getChatsPerAgent = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts/chats-per-agent'); const { isSuccess, data } = useQuery({ queryKey: omnichannelQueryKeys.analytics.chatsPerAgent(departmentId, dateRange), queryFn: () => getChatsPerAgent({ departmentId, ...dateRange }), + select: ({ success: _, ...data }) => Object.entries(data), + gcTime: 0, }); - useEffect(() => { - const initChart = async () => { - if (!canvas.current) { - return; - } - context.current = await init(canvas.current, context.current, t); - }; - initChart(); - }, [t]); + const context = useChartContext({ + canvas, + init, + t, + }); + + const updateChartData = useUpdateChartData({ + context, + canvas, + init, + t, + }); useEffect(() => { - if (!isSuccess) return; + if (!context) return; + + if (!isSuccess) { + return; + } - Object.entries(data).forEach(([name, value]) => { - if (name === 'success') { - return; - } + resetChart(context); + data.forEach(([name, value]) => { updateChartData(name, [value.open, value.closed, value.onhold]); }); - }, [data, isSuccess, t, updateChartData]); + }, [context, data, isSuccess, t, updateChartData]); return ; }; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.tsx b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.tsx index 7881cc48636ac..1f4073af6fc1c 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.tsx +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.tsx @@ -9,8 +9,9 @@ import { useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import Chart from './Chart'; +import { useChartContext } from './useChartContext'; import { useUpdateChartData } from './useUpdateChartData'; -import { drawLineChart } from '../../../../../app/livechat/client/lib/chartHandler'; +import { drawLineChart, resetChart } from '../../../../../app/livechat/client/lib/chartHandler'; import { omnichannelQueryKeys } from '../../../../lib/queryKeys'; const init = (canvas: HTMLCanvasElement, context: chartjs.Chart<'line'> | undefined, t: TFunction) => @@ -29,41 +30,43 @@ const ChatsPerDepartmentChart = ({ departmentId, dateRange, ...props }: ChatsPer const { t } = useTranslation(); const canvas = useRef(null); - const context = useRef>(); - - const updateChartData = useUpdateChartData({ - context, - canvas, - t, - init, - }); const getChatsPerDepartment = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts/chats-per-department'); const { isSuccess, data } = useQuery({ queryKey: omnichannelQueryKeys.analytics.chatsPerDepartment(departmentId, dateRange), queryFn: () => getChatsPerDepartment({ departmentId, ...dateRange }), + select: ({ success: _, ...data }) => Object.entries(data), + gcTime: 0, }); - useEffect(() => { - const initChart = async () => { - if (!canvas.current) { - return; - } - context.current = await init(canvas.current, context.current, t); - }; - initChart(); - }, [t]); + const context = useChartContext({ + canvas, + init, + t, + }); + + const updateChartData = useUpdateChartData({ + context, + canvas, + init, + t, + }); useEffect(() => { - if (!isSuccess) return; - Object.entries(data).forEach(([name, value]) => { - if (name === 'success') { - return; - } + if (!context) { + return; + } + + if (!isSuccess) { + return; + } + + resetChart(context); + data.forEach(([name, value]) => { updateChartData(name, [value.open, value.closed]); }); - }, [data, isSuccess, t, updateChartData]); + }, [context, data, isSuccess, t, updateChartData]); return ; }; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.tsx b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.tsx index 40bc38a100b9c..394ba49249f58 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.tsx +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.tsx @@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next'; import Chart from './Chart'; import { getMomentChartLabelsAndData } from './getMomentChartLabelsAndData'; import { getMomentCurrentLabel } from './getMomentCurrentLabel'; +import { useChartContext } from './useChartContext'; import { useUpdateChartData } from './useUpdateChartData'; import { drawLineChart } from '../../../../../app/livechat/client/lib/chartHandler'; import { secondsToHHMMSS } from '../../../../../lib/utils/secondsToHHMMSS'; @@ -49,14 +50,6 @@ const ResponseTimesChart = ({ departmentId, dateRange, ...props }: ResponseTimes const { t } = useTranslation(); const canvas = useRef(null); - const context = useRef>(); - - const updateChartData = useUpdateChartData({ - context, - canvas, - t, - init, - }); const getTimings = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts/timings'); const { @@ -74,25 +67,34 @@ const ResponseTimesChart = ({ departmentId, dateRange, ...props }: ResponseTimes } = useQuery({ queryKey: omnichannelQueryKeys.analytics.timings(departmentId, dateRange), queryFn: () => getTimings({ departmentId, ...dateRange }), + gcTime: 0, }); - useEffect(() => { - const initChart = async () => { - if (!canvas.current) { - return; - } + const context = useChartContext({ + canvas, + init, + t, + }); - context.current = await init(canvas.current, context.current, t); - }; - initChart(); - }, [t]); + const updateChartData = useUpdateChartData({ + context, + canvas, + init, + t, + }); useEffect(() => { - if (!isSuccess) return; + if (!context) { + return; + } + + if (!isSuccess) { + return; + } const label = getMomentCurrentLabel(); updateChartData(label, [reactionAvg, reactionLongest, responseAvg, responseLongest]); - }, [reactionAvg, reactionLongest, responseAvg, responseLongest, isSuccess, t, updateChartData]); + }, [context, reactionAvg, reactionLongest, responseAvg, responseLongest, isSuccess, t, updateChartData]); return ; }; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useChartContext.tsx b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useChartContext.tsx new file mode 100644 index 0000000000000..fbdbb6b60e9a3 --- /dev/null +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useChartContext.tsx @@ -0,0 +1,44 @@ +import type { Chart, ChartType } from 'chart.js'; +import type { TFunction } from 'i18next'; +import type { MutableRefObject } from 'react'; +import { useEffect, useState } from 'react'; + +type UseChartContextProps = { + canvas: MutableRefObject; + init: (canvas: HTMLCanvasElement, context: TChart | undefined, t: TFunction) => Promise; + t: TFunction; +}; + +export const useChartContext = ({ canvas, init, t }: UseChartContextProps>) => { + const [context, setContext] = useState>(); + + useEffect(() => { + let chart: Chart | undefined; + let unmounted = false; + + const initializeChart = async () => { + if (!canvas.current) { + return; + } + + chart = await init(canvas.current, undefined, t); + + if (unmounted) { + chart?.destroy(); + return; + } + + setContext(chart); + }; + + void initializeChart(); + + return () => { + unmounted = true; + chart?.destroy(); + setContext(undefined); + }; + }, [canvas, init, t]); + + return context; +}; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts index ba44e0bb722f4..3439d117dbc91 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts @@ -6,15 +6,15 @@ import { type MutableRefObject } from 'react'; import { updateChart } from '../../../../../app/livechat/client/lib/chartHandler'; type UseUpdateChartDataOptions = { - context: MutableRefObject; + context: TChart | undefined; canvas: MutableRefObject; init: (canvas: HTMLCanvasElement, context: TChart | undefined, t: TFunction) => Promise; t: TFunction; }; export function useUpdateChartData({ - context: contextRef, canvas: canvasRef, + context, init, t, }: UseUpdateChartDataOptions>) { @@ -25,8 +25,8 @@ export function useUpdateChartData({ return; } - const context = contextRef.current ?? (await init(canvas, undefined, t)); + const chartContext = context ?? (await init(canvas, undefined, t)); - await updateChart(context, label, data); + await updateChart(chartContext, label, data); }); } diff --git a/yarn.lock b/yarn.lock index fc855640fb328..33cf7681fa018 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9387,7 +9387,7 @@ __metadata: peerDependencies: "@rocket.chat/layout": "*" "@rocket.chat/tools": 0.2.3 - "@rocket.chat/ui-contexts": 22.0.0 + "@rocket.chat/ui-contexts": 22.0.1 "@tanstack/react-query": "*" react: "*" react-hook-form: "*"