From bf207b52e413b30a8fb0d47f7b694cc99a0e9b3d Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Tue, 10 Dec 2024 12:33:38 -0300 Subject: [PATCH 1/8] fix: Always attach best first response time metric to the first agent who responded to the room --- .../meteor/server/models/raw/LivechatRooms.ts | 2 +- .../omnichannel-analytics/AgentData.ts | 12 ++-- .../end-to-end/api/livechat/04-dashboards.ts | 58 ++++++++++++++++++- .../omnichannel-analytics/AgentData.tests.ts | 50 +++++++++++----- .../src/models/ILivechatRoomsModel.ts | 2 +- 5 files changed, 99 insertions(+), 25 deletions(-) diff --git a/apps/meteor/server/models/raw/LivechatRooms.ts b/apps/meteor/server/models/raw/LivechatRooms.ts index e422615fecbd..de18a8ec3d22 100644 --- a/apps/meteor/server/models/raw/LivechatRooms.ts +++ b/apps/meteor/server/models/raw/LivechatRooms.ts @@ -2147,7 +2147,7 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive }; return this.find(query, { - projection: { ts: 1, departmentId: 1, open: 1, servedBy: 1, metrics: 1, msgs: 1 }, + projection: { ts: 1, departmentId: 1, open: 1, servedBy: 1, responseBy: 1, metrics: 1, msgs: 1 }, }); } diff --git a/apps/meteor/server/services/omnichannel-analytics/AgentData.ts b/apps/meteor/server/services/omnichannel-analytics/AgentData.ts index 40ce0f1236cb..770fea291b46 100644 --- a/apps/meteor/server/services/omnichannel-analytics/AgentData.ts +++ b/apps/meteor/server/services/omnichannel-analytics/AgentData.ts @@ -267,7 +267,7 @@ export class AgentOverviewData { } async Best_first_response_time(from: moment.Moment, to: moment.Moment, departmentId?: string, extraQuery: Filter = {}) { - const agentFirstRespTime = new Map(); // stores avg response time for each agent + const agentFirstRespTime = new Map(); // stores best response time for each agent const date = { gte: from.toDate(), lte: to.toDate(), @@ -285,12 +285,12 @@ export class AgentOverviewData { data: [], }; - await this.roomsModel.getAnalyticsMetricsBetweenDate('l', date, { departmentId }, extraQuery).forEach(({ metrics, servedBy }) => { - if (servedBy && metrics && metrics.response && metrics.response.ft) { - if (agentFirstRespTime.has(servedBy.username)) { - agentFirstRespTime.set(servedBy.username, Math.min(agentFirstRespTime.get(servedBy.username), metrics.response.ft)); + await this.roomsModel.getAnalyticsMetricsBetweenDate('l', date, { departmentId }, extraQuery).forEach(({ metrics, responseBy }) => { + if (responseBy && metrics && metrics.response && metrics.response.ft) { + if (agentFirstRespTime.has(responseBy.username)) { + agentFirstRespTime.set(responseBy.username, Math.min(agentFirstRespTime.get(responseBy.username), metrics.response.ft)); } else { - agentFirstRespTime.set(servedBy.username, metrics.response.ft); + agentFirstRespTime.set(responseBy.username, metrics.response.ft); } } }); diff --git a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts index 33cb2f1f26b0..4c8237c34045 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts @@ -1019,14 +1019,16 @@ describe('LIVECHAT - dashboards', function () { describe('[livechat/analytics/agent-overview] - Best first response time', () => { let agent: { credentials: Credentials; user: IUser & { username: string } }; + let forwardAgent: { credentials: Credentials; user: IUser & { username: string } }; let originalBestFirstResponseTimeInSeconds: number; let roomId: string; before(async () => { agent = await createAnOnlineAgent(); + forwardAgent = await createAnOnlineAgent(); }); - after(() => deleteUser(agent.user)); + after(() => Promise.all([deleteUser(agent.user), deleteUser(forwardAgent.user)])); it('should return no best response time for an agent if no response has been sent in the period', async () => { await startANewLivechatRoomAndTakeIt({ agent: agent.credentials }); @@ -1110,6 +1112,60 @@ describe('LIVECHAT - dashboards', function () { const bestFirstResponseTimeInSeconds = moment.duration(agentData.value).asSeconds(); expect(bestFirstResponseTimeInSeconds).to.be.equal(originalBestFirstResponseTimeInSeconds); }); + + it('should correctly associate best first response time to the first agent who responded the room', async () => { + const response = await startANewLivechatRoomAndTakeIt({ agent: forwardAgent.credentials }); + roomId = response.room._id; + + await sendAgentMessage(roomId, 'first response from agent', forwardAgent.credentials); + + await request + .post(api('livechat/room.forward')) + .set(credentials) + .send({ + roomId, + userId: agent.user._id, + comment: 'test comment', + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + }); + + const today = moment().startOf('day').format('YYYY-MM-DD'); + const result = await request + .get(api('livechat/analytics/agent-overview')) + .query({ from: today, to: today, name: 'Best_first_response_time' }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(result.body).to.have.property('success', true); + expect(result.body).to.have.property('head'); + expect(result.body).to.have.property('data'); + expect(result.body.data).to.be.an('array'); + + // The agent to whom the room has been forwarded shouldn't have their best first response time changed + const agentData = result.body.data.find( + (agentOverviewData: { name: string; value: string }) => agentOverviewData.name === agent.user.username, + ); + expect(agentData).to.not.be.undefined; + expect(agentData).to.have.property('name', agent.user.username); + expect(agentData).to.have.property('value'); + const bestFirstResponseTimeInSeconds = moment.duration(agentData.value).asSeconds(); + expect(bestFirstResponseTimeInSeconds).to.be.equal(originalBestFirstResponseTimeInSeconds); + + // A room's first response time should be attached to the agent who first responded to it even if it has been forwarded + const forwardAgentData = result.body.data.find( + (agentOverviewData: { name: string; value: string }) => agentOverviewData.name === forwardAgent.user.username, + ); + expect(forwardAgentData).to.not.be.undefined; + expect(forwardAgentData).to.have.property('name', forwardAgent.user.username); + expect(forwardAgentData).to.have.property('value'); + const forwardAgentBestFirstResponseTimeInSeconds = moment.duration(forwardAgentData.value).asSeconds(); + expect(forwardAgentBestFirstResponseTimeInSeconds).to.be.lessThan(originalBestFirstResponseTimeInSeconds); + }); }); describe('livechat/analytics/overview', () => { diff --git a/apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts b/apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts index 4c9320163774..cdc085a8b87d 100644 --- a/apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts +++ b/apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts @@ -966,9 +966,12 @@ describe('AgentData Analytics', () => { getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { return [ { - servedBy: { + responseBy: { username: 'agent 1', }, + servedBy: { + username: 'agent 2', + }, metrics: { response: { ft: 100, @@ -976,9 +979,12 @@ describe('AgentData Analytics', () => { }, }, { - servedBy: { + responseBy: { username: 'agent 2', }, + servedBy: { + username: 'agent 3', + }, metrics: { response: { ft: 200, @@ -986,9 +992,12 @@ describe('AgentData Analytics', () => { }, }, { - servedBy: { + responseBy: { username: 'agent 3', }, + servedBy: { + username: 'agent 4', + }, metrics: { response: { ft: 50, @@ -996,9 +1005,12 @@ describe('AgentData Analytics', () => { }, }, { - servedBy: { + responseBy: { username: 'agent 4', }, + servedBy: { + username: 'agent 5', + }, metrics: { response: { ft: 150, @@ -1006,9 +1018,12 @@ describe('AgentData Analytics', () => { }, }, { - servedBy: { + responseBy: { username: 'agent 5', }, + servedBy: { + username: 'agent 6', + }, metrics: { response: { ft: 250, @@ -1016,9 +1031,12 @@ describe('AgentData Analytics', () => { }, }, { - servedBy: { + responseBy: { username: 'agent 6', }, + servedBy: { + username: 'agent 7', + }, metrics: { response: { ft: 300, @@ -1055,7 +1073,7 @@ describe('AgentData Analytics', () => { getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { return [ { - servedBy: { + responseBy: { username: 'agent 1', }, metrics: { @@ -1065,7 +1083,7 @@ describe('AgentData Analytics', () => { }, }, { - servedBy: { + responseBy: { username: 'agent 2', }, metrics: { @@ -1075,7 +1093,7 @@ describe('AgentData Analytics', () => { }, }, { - servedBy: { + responseBy: { username: 'agent 3', }, metrics: { @@ -1085,7 +1103,7 @@ describe('AgentData Analytics', () => { }, }, { - servedBy: { + responseBy: { username: 'agent 4', }, metrics: { @@ -1095,7 +1113,7 @@ describe('AgentData Analytics', () => { }, }, { - servedBy: { + responseBy: { username: 'agent 5', }, metrics: { @@ -1105,7 +1123,7 @@ describe('AgentData Analytics', () => { }, }, { - servedBy: { + responseBy: { username: 'agent 6', }, metrics: { @@ -1115,7 +1133,7 @@ describe('AgentData Analytics', () => { }, }, { - servedBy: { + responseBy: { username: 'agent 1', }, metrics: { @@ -1149,10 +1167,10 @@ describe('AgentData Analytics', () => { ], }); }); - it('should ignore conversations not being served by any agent', async () => { + it('should ignore conversations not responded by any agent', async () => { const modelMock = { getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { - return [{ servedBy: undefined, metrics: { response: { ft: 100 } } }]; + return [{ responseBy: undefined, metrics: { response: { ft: 100 } } }]; }, }; @@ -1173,7 +1191,7 @@ describe('AgentData Analytics', () => { it('should ignore conversations with no metrics', async () => { const modelMock = { getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { - return [{ servedBy: { username: 'agent 1' }, metrics: undefined }]; + return [{ responseBy: { username: 'agent 1' }, metrics: undefined }]; }, }; diff --git a/packages/model-typings/src/models/ILivechatRoomsModel.ts b/packages/model-typings/src/models/ILivechatRoomsModel.ts index 7ba6f9e74a3b..00dea51969c7 100644 --- a/packages/model-typings/src/models/ILivechatRoomsModel.ts +++ b/packages/model-typings/src/models/ILivechatRoomsModel.ts @@ -238,7 +238,7 @@ export interface ILivechatRoomsModel extends IBaseModel { date: { gte: Date; lte: Date }, data?: { departmentId?: string }, extraQuery?: Filter, - ): FindCursor>; + ): FindCursor>; getAnalyticsMetricsBetweenDateWithMessages( t: string, date: { gte: Date; lte: Date }, From 954f8ea1329ab88809158312fc73f6b44637d904 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Tue, 10 Dec 2024 13:13:00 -0300 Subject: [PATCH 2/8] fix: Always attach avg first response time metric to the first agent who responded to the room --- .../omnichannel-analytics/AgentData.ts | 14 ++--- .../end-to-end/api/livechat/04-dashboards.ts | 60 ++++++++++++++++++- .../omnichannel-analytics/AgentData.tests.ts | 21 +++++-- 3 files changed, 79 insertions(+), 16 deletions(-) diff --git a/apps/meteor/server/services/omnichannel-analytics/AgentData.ts b/apps/meteor/server/services/omnichannel-analytics/AgentData.ts index 770fea291b46..e92f7c7b6716 100644 --- a/apps/meteor/server/services/omnichannel-analytics/AgentData.ts +++ b/apps/meteor/server/services/omnichannel-analytics/AgentData.ts @@ -235,15 +235,15 @@ export class AgentOverviewData { data: [], }; - await this.roomsModel.getAnalyticsMetricsBetweenDate('l', date, { departmentId }, extraQuery).forEach(({ metrics, servedBy }) => { - if (servedBy && metrics && metrics.response && metrics.response.ft) { - if (agentAvgRespTime.has(servedBy.username)) { - agentAvgRespTime.set(servedBy.username, { - frt: agentAvgRespTime.get(servedBy.username).frt + metrics.response.ft, - total: agentAvgRespTime.get(servedBy.username).total + 1, + await this.roomsModel.getAnalyticsMetricsBetweenDate('l', date, { departmentId }, extraQuery).forEach(({ metrics, responseBy }) => { + if (responseBy && metrics && metrics.response && metrics.response.ft) { + if (agentAvgRespTime.has(responseBy.username)) { + agentAvgRespTime.set(responseBy.username, { + frt: agentAvgRespTime.get(responseBy.username).frt + metrics.response.ft, + total: agentAvgRespTime.get(responseBy.username).total + 1, }); } else { - agentAvgRespTime.set(servedBy.username, { + agentAvgRespTime.set(responseBy.username, { frt: metrics.response.ft, total: 1, }); diff --git a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts index 4c8237c34045..0858d2685f53 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts @@ -922,6 +922,7 @@ describe('LIVECHAT - dashboards', function () { describe('[livechat/analytics/agent-overview] - Average first response time', () => { let agent: { credentials: Credentials; user: IUser & { username: string } }; + let forwardAgent: { credentials: Credentials; user: IUser & { username: string } }; let originalFirstResponseTimeInSeconds: number; let roomId: string; const firstDelayInSeconds = 4; @@ -929,11 +930,10 @@ describe('LIVECHAT - dashboards', function () { before(async () => { agent = await createAnOnlineAgent(); + forwardAgent = await createAnOnlineAgent(); }); - after(async () => { - await deleteUser(agent.user); - }); + after(async () => Promise.all([deleteUser(agent.user), deleteUser(forwardAgent.user)])); it('should return no average response time for an agent if no response has been sent in the period', async () => { await startANewLivechatRoomAndTakeIt({ agent: agent.credentials }); @@ -984,6 +984,60 @@ describe('LIVECHAT - dashboards', function () { expect(originalFirstResponseTimeInSeconds).to.be.greaterThanOrEqual(firstDelayInSeconds); }); + it('should correctly associate the first response time to the first agent who responded the room', async () => { + const response = await startANewLivechatRoomAndTakeIt({ agent: forwardAgent.credentials }); + roomId = response.room._id; + + await sendAgentMessage(roomId, 'first response from agent', forwardAgent.credentials); + + await request + .post(api('livechat/room.forward')) + .set(credentials) + .send({ + roomId, + userId: agent.user._id, + comment: 'test comment', + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + }); + + const today = moment().startOf('day').format('YYYY-MM-DD'); + const result = await request + .get(api('livechat/analytics/agent-overview')) + .query({ from: today, to: today, name: 'Avg_first_response_time' }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(result.body).to.have.property('success', true); + expect(result.body).to.have.property('head'); + expect(result.body).to.have.property('data'); + expect(result.body.data).to.be.an('array'); + + // The agent to whom the room has been forwarded shouldn't have their average first response time changed + const agentData = result.body.data.find( + (agentOverviewData: { name: string; value: string }) => agentOverviewData.name === agent.user.username, + ); + expect(agentData).to.not.be.undefined; + expect(agentData).to.have.property('name', agent.user.username); + expect(agentData).to.have.property('value'); + const averageFirstResponseTimeInSeconds = moment.duration(agentData.value).asSeconds(); + expect(originalFirstResponseTimeInSeconds).to.be.equal(averageFirstResponseTimeInSeconds); + + // A room's first response time should be attached to the agent who first responded to it even if it has been forwarded + const forwardAgentData = result.body.data.find( + (agentOverviewData: { name: string; value: string }) => agentOverviewData.name === forwardAgent.user.username, + ); + expect(forwardAgentData).to.not.be.undefined; + expect(forwardAgentData).to.have.property('name', forwardAgent.user.username); + expect(forwardAgentData).to.have.property('value'); + const forwardAgentAverageFirstResponseTimeInSeconds = moment.duration(forwardAgentData.value).asSeconds(); + expect(originalFirstResponseTimeInSeconds).to.be.greaterThan(forwardAgentAverageFirstResponseTimeInSeconds); + }); + it('should correctly calculate the average time of first responses for an agent', async () => { const response = await startANewLivechatRoomAndTakeIt({ agent: agent.credentials }); roomId = response.room._id; diff --git a/apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts b/apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts index cdc085a8b87d..b143bad7244a 100644 --- a/apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts +++ b/apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts @@ -735,7 +735,7 @@ describe('AgentData Analytics', () => { getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { return [ { - servedBy: { + responseBy: { username: 'agent 1', }, metrics: { @@ -772,7 +772,7 @@ describe('AgentData Analytics', () => { getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { return [ { - servedBy: { + responseBy: { username: 'agent 1', }, metrics: { @@ -782,7 +782,7 @@ describe('AgentData Analytics', () => { }, }, { - servedBy: { + responseBy: { username: 'agent 2', }, metrics: { @@ -824,6 +824,9 @@ describe('AgentData Analytics', () => { return [ { servedBy: { + username: 'agent 3', + }, + responseBy: { username: 'agent 1', }, metrics: { @@ -834,6 +837,9 @@ describe('AgentData Analytics', () => { }, { servedBy: { + username: 'agent 4', + }, + responseBy: { username: 'agent 2', }, metrics: { @@ -844,6 +850,9 @@ describe('AgentData Analytics', () => { }, { servedBy: { + username: 'agent 5', + }, + responseBy: { username: 'agent 1', }, metrics: { @@ -879,12 +888,12 @@ describe('AgentData Analytics', () => { ], }); }); - it('should ignore conversations not being served by any agent', async () => { + it('should ignore conversations not responded by any agent', async () => { const modelMock = { getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { return [ { - servedBy: undefined, + responseBy: undefined, metrics: { response: { ft: 100, @@ -914,7 +923,7 @@ describe('AgentData Analytics', () => { getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { return [ { - servedBy: { + responseBy: { username: 'agent 1', }, metrics: undefined, From 5e8752d3c47c20d67e877daf8e6cfa07b5796f41 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Tue, 10 Dec 2024 13:14:34 -0300 Subject: [PATCH 3/8] fix end-to-end tests --- apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts index 0858d2685f53..d62d41a5d811 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts @@ -1280,7 +1280,7 @@ describe('LIVECHAT - dashboards', function () { expect(result.body).to.be.an('array'); const expectedResult = [ - { title: 'Total_conversations', value: 13 }, + { title: 'Total_conversations', value: 15 }, { title: 'Open_conversations', value: 10 }, { title: 'On_Hold_conversations', value: 1 }, // { title: 'Total_messages', value: 6 }, From a2391a1e104fb5a71fedf7d2f0a9fdecb3f8e07e Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:17:17 -0300 Subject: [PATCH 4/8] Create changeset --- .changeset/fair-carrots-trade.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/fair-carrots-trade.md diff --git a/.changeset/fair-carrots-trade.md b/.changeset/fair-carrots-trade.md new file mode 100644 index 000000000000..15965d726f3f --- /dev/null +++ b/.changeset/fair-carrots-trade.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/model-typings": patch +--- + +Fixes "Average first response time" and "Best first response time" metrics being associated to the last agent who served the room (instead of the first one) From 8f638dd5271e83a989e1f7f6c1eb248503ce3542 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Tue, 10 Dec 2024 14:22:47 -0300 Subject: [PATCH 5/8] fix more end-to-end tests --- apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts index d62d41a5d811..c7afaa2ddb15 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts @@ -1281,11 +1281,11 @@ describe('LIVECHAT - dashboards', function () { const expectedResult = [ { title: 'Total_conversations', value: 15 }, - { title: 'Open_conversations', value: 10 }, + { title: 'Open_conversations', value: 12 }, { title: 'On_Hold_conversations', value: 1 }, // { title: 'Total_messages', value: 6 }, // { title: 'Busiest_day', value: moment().format('dddd') }, - { title: 'Conversations_per_day', value: '6.50' }, + { title: 'Conversations_per_day', value: '7.50' }, // { title: 'Busiest_time', value: '' }, ]; From 456e1f0a0fc36ad66d4c23fc0d05ecd4cb88afd9 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Wed, 11 Dec 2024 10:33:03 -0300 Subject: [PATCH 6/8] Improve unit tests --- .../omnichannel-analytics/AgentData.tests.ts | 206 +++++++++++++++++- 1 file changed, 205 insertions(+), 1 deletion(-) diff --git a/apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts b/apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts index b143bad7244a..7e3003332d50 100644 --- a/apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts +++ b/apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts @@ -818,7 +818,7 @@ describe('AgentData Analytics', () => { ], }); }); - it('should calculate correctly when agents have multiple conversations', async () => { + it('should associate average first response time with the agent who first responded to the room', async () => { const modelMock = { getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { return [ @@ -888,6 +888,67 @@ describe('AgentData Analytics', () => { ], }); }); + it('should calculate correctly when agents have multiple conversations', async () => { + const modelMock = { + getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { + return [ + { + responseBy: { + username: 'agent 1', + }, + metrics: { + response: { + ft: 100, + }, + }, + }, + { + responseBy: { + username: 'agent 2', + }, + metrics: { + response: { + ft: 200, + }, + }, + }, + { + responseBy: { + username: 'agent 1', + }, + metrics: { + response: { + ft: 200, + }, + }, + }, + ]; + }, + }; + + const agentOverview = new AgentOverviewData(modelMock as any); + + const result = await agentOverview.Avg_first_response_time(moment(), moment(), 'departmentId'); + + expect(result).to.be.deep.equal({ + data: [ + { + name: 'agent 1', + value: '00:02:30', + }, + { + name: 'agent 2', + value: '00:03:20', + }, + ], + head: [ + { + name: 'Agent', + }, + { name: 'Avg_first_response_time' }, + ], + }); + }); it('should ignore conversations not responded by any agent', async () => { const modelMock = { getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { @@ -918,6 +979,39 @@ describe('AgentData Analytics', () => { ], }); }); + it('should ignore conversations served, but not responded by any agent', async () => { + const modelMock = { + getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { + return [ + { + servedBy: { + username: 'agent 1', + }, + responseBy: undefined, + metrics: { + response: { + ft: 100, + }, + }, + }, + ]; + }, + }; + + const agentOverview = new AgentOverviewData(modelMock as any); + + const result = await agentOverview.Avg_first_response_time(moment(), moment(), 'departmentId'); + + expect(result).to.be.deep.equal({ + data: [], + head: [ + { + name: 'Agent', + }, + { name: 'Avg_first_response_time' }, + ], + }); + }); it('should ignore conversations with no metrics', async () => { const modelMock = { getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { @@ -971,6 +1065,95 @@ describe('AgentData Analytics', () => { }); }); it('should return an ConversationData object with data when model call returns data', async () => { + const modelMock = { + getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { + return [ + { + responseBy: { + username: 'agent 1', + }, + metrics: { + response: { + ft: 100, + }, + }, + }, + { + responseBy: { + username: 'agent 2', + }, + metrics: { + response: { + ft: 200, + }, + }, + }, + { + responseBy: { + username: 'agent 3', + }, + metrics: { + response: { + ft: 50, + }, + }, + }, + { + responseBy: { + username: 'agent 4', + }, + metrics: { + response: { + ft: 150, + }, + }, + }, + { + responseBy: { + username: 'agent 5', + }, + metrics: { + response: { + ft: 250, + }, + }, + }, + { + responseBy: { + username: 'agent 6', + }, + metrics: { + response: { + ft: 300, + }, + }, + }, + ]; + }, + }; + + const agentOverview = new AgentOverviewData(modelMock as any); + + const result = await agentOverview.Best_first_response_time(moment(), moment(), 'departmentId'); + + expect(result).to.be.deep.equal({ + data: [ + { name: 'agent 1', value: '00:01:40' }, + { name: 'agent 2', value: '00:03:20' }, + { name: 'agent 3', value: '00:00:50' }, + { name: 'agent 4', value: '00:02:30' }, + { name: 'agent 5', value: '00:04:10' }, + { name: 'agent 6', value: '00:05:00' }, + ], + head: [ + { + name: 'Agent', + }, + { name: 'Best_first_response_time' }, + ], + }); + }); + it('should associate best first response time with the agent who first responded to the room', async () => { const modelMock = { getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { return [ @@ -1197,6 +1380,27 @@ describe('AgentData Analytics', () => { ], }); }); + it('should ignore conversations served, but not responded by any agent', async () => { + const modelMock = { + getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { + return [{ servedBy: { username: 'agent1' }, responseBy: undefined, metrics: { response: { ft: 100 } } }]; + }, + }; + + const agentOverview = new AgentOverviewData(modelMock as any); + + const result = await agentOverview.Best_first_response_time(moment(), moment(), 'departmentId'); + + expect(result).to.be.deep.equal({ + data: [], + head: [ + { + name: 'Agent', + }, + { name: 'Best_first_response_time' }, + ], + }); + }); it('should ignore conversations with no metrics', async () => { const modelMock = { getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) { From f61a406848b43f6f4978c88352b793df86469c1f Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Wed, 11 Dec 2024 10:33:31 -0300 Subject: [PATCH 7/8] Improve changeset --- .changeset/fair-carrots-trade.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/fair-carrots-trade.md b/.changeset/fair-carrots-trade.md index 15965d726f3f..9c479c941ddf 100644 --- a/.changeset/fair-carrots-trade.md +++ b/.changeset/fair-carrots-trade.md @@ -3,4 +3,4 @@ "@rocket.chat/model-typings": patch --- -Fixes "Average first response time" and "Best first response time" metrics being associated to the last agent who served the room (instead of the first one) +Fixes "Average first response time" and "Best first response time" metrics being associated with the last agent who served the room (instead of the first one) From e3dc5a44afe753505bad9d1fa391fc1331bbdc6a Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Fri, 13 Dec 2024 14:32:19 -0300 Subject: [PATCH 8/8] send message as forwarded agent in tests --- apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts index c7afaa2ddb15..351abecff21c 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts @@ -1004,6 +1004,8 @@ describe('LIVECHAT - dashboards', function () { expect(res.body).to.have.property('success', true); }); + await sendAgentMessage(roomId, 'first response from forwarded agent', agent.credentials); + const today = moment().startOf('day').format('YYYY-MM-DD'); const result = await request .get(api('livechat/analytics/agent-overview')) @@ -1187,6 +1189,8 @@ describe('LIVECHAT - dashboards', function () { expect(res.body).to.have.property('success', true); }); + await sendAgentMessage(roomId, 'first response from forwarded agent', agent.credentials); + const today = moment().startOf('day').format('YYYY-MM-DD'); const result = await request .get(api('livechat/analytics/agent-overview'))