Skip to content

Commit

Permalink
fix: Always attach best first response time metric to the first agent…
Browse files Browse the repository at this point in the history
… who responded to the room
  • Loading branch information
matheusbsilva137 committed Dec 10, 2024
1 parent ed69269 commit bf207b5
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 25 deletions.
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
12 changes: 6 additions & 6 deletions apps/meteor/server/services/omnichannel-analytics/AgentData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
58 changes: 57 additions & 1 deletion apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -966,59 +966,77 @@ describe('AgentData Analytics', () => {
getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) {
return [
{
servedBy: {
responseBy: {
username: 'agent 1',
},
servedBy: {
username: 'agent 2',
},
metrics: {
response: {
ft: 100,
},
},
},
{
servedBy: {
responseBy: {
username: 'agent 2',
},
servedBy: {
username: 'agent 3',
},
metrics: {
response: {
ft: 200,
},
},
},
{
servedBy: {
responseBy: {
username: 'agent 3',
},
servedBy: {
username: 'agent 4',
},
metrics: {
response: {
ft: 50,
},
},
},
{
servedBy: {
responseBy: {
username: 'agent 4',
},
servedBy: {
username: 'agent 5',
},
metrics: {
response: {
ft: 150,
},
},
},
{
servedBy: {
responseBy: {
username: 'agent 5',
},
servedBy: {
username: 'agent 6',
},
metrics: {
response: {
ft: 250,
},
},
},
{
servedBy: {
responseBy: {
username: 'agent 6',
},
servedBy: {
username: 'agent 7',
},
metrics: {
response: {
ft: 300,
Expand Down Expand Up @@ -1055,7 +1073,7 @@ describe('AgentData Analytics', () => {
getAnalyticsMetricsBetweenDate(_params: ILivechatRoomsModel['getAnalyticsMetricsBetweenDate']) {
return [
{
servedBy: {
responseBy: {
username: 'agent 1',
},
metrics: {
Expand All @@ -1065,7 +1083,7 @@ describe('AgentData Analytics', () => {
},
},
{
servedBy: {
responseBy: {
username: 'agent 2',
},
metrics: {
Expand All @@ -1075,7 +1093,7 @@ describe('AgentData Analytics', () => {
},
},
{
servedBy: {
responseBy: {
username: 'agent 3',
},
metrics: {
Expand All @@ -1085,7 +1103,7 @@ describe('AgentData Analytics', () => {
},
},
{
servedBy: {
responseBy: {
username: 'agent 4',
},
metrics: {
Expand All @@ -1095,7 +1113,7 @@ describe('AgentData Analytics', () => {
},
},
{
servedBy: {
responseBy: {
username: 'agent 5',
},
metrics: {
Expand All @@ -1105,7 +1123,7 @@ describe('AgentData Analytics', () => {
},
},
{
servedBy: {
responseBy: {
username: 'agent 6',
},
metrics: {
Expand All @@ -1115,7 +1133,7 @@ describe('AgentData Analytics', () => {
},
},
{
servedBy: {
responseBy: {
username: 'agent 1',
},
metrics: {
Expand Down Expand Up @@ -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 } } }];
},
};

Expand All @@ -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 }];
},
};

Expand Down
2 changes: 1 addition & 1 deletion packages/model-typings/src/models/ILivechatRoomsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export interface ILivechatRoomsModel extends IBaseModel<IOmnichannelRoom> {
date: { gte: Date; lte: Date },
data?: { departmentId?: string },
extraQuery?: Filter<IOmnichannelRoom>,
): FindCursor<Pick<IOmnichannelRoom, 'ts' | 'departmentId' | 'open' | 'servedBy' | 'metrics' | 'msgs'>>;
): FindCursor<Pick<IOmnichannelRoom, 'ts' | 'departmentId' | 'open' | 'servedBy' | 'responseBy' | 'metrics' | 'msgs'>>;
getAnalyticsMetricsBetweenDateWithMessages(
t: string,
date: { gte: Date; lte: Date },
Expand Down

0 comments on commit bf207b5

Please sign in to comment.