Skip to content

Commit

Permalink
fix: First response time livechat metrics are associated with the las…
Browse files Browse the repository at this point in the history
…t agent who served the room (#34156)
  • Loading branch information
matheusbsilva137 authored Dec 16, 2024
1 parent b32c629 commit 47f24c2
Show file tree
Hide file tree
Showing 6 changed files with 395 additions and 44 deletions.
6 changes: 6 additions & 0 deletions .changeset/fair-carrots-trade.md
Original file line number Diff line number Diff line change
@@ -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 with the last agent who served the room (instead of the first one)
2 changes: 1 addition & 1 deletion apps/meteor/server/models/raw/LivechatRooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2147,7 +2147,7 @@ export class LivechatRoomsRaw extends BaseRaw<IOmnichannelRoom> 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 },
});
}

Expand Down
26 changes: 13 additions & 13 deletions apps/meteor/server/services/omnichannel-analytics/AgentData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand All @@ -267,7 +267,7 @@ export class AgentOverviewData {
}

async Best_first_response_time(from: moment.Moment, to: moment.Moment, departmentId?: string, extraQuery: Filter<IOmnichannelRoom> = {}) {
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(),
Expand All @@ -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);
}
}
});
Expand Down
128 changes: 121 additions & 7 deletions apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -922,18 +922,18 @@ 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;
const secondDelayInSeconds = 8;

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 });
Expand Down Expand Up @@ -984,6 +984,62 @@ 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);
});

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'))
.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;
Expand Down Expand Up @@ -1019,14 +1075,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 });
Expand Down Expand Up @@ -1110,6 +1168,62 @@ 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);
});

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'))
.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', () => {
Expand Down Expand Up @@ -1170,12 +1284,12 @@ describe('LIVECHAT - dashboards', function () {
expect(result.body).to.be.an('array');

const expectedResult = [
{ title: 'Total_conversations', value: 13 },
{ title: 'Open_conversations', value: 10 },
{ title: 'Total_conversations', value: 15 },
{ 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: '' },
];

Expand Down
Loading

0 comments on commit 47f24c2

Please sign in to comment.