Skip to content
This repository was archived by the owner on Nov 21, 2020. It is now read-only.

Commit 5be1ff6

Browse files
munkhjin0223batamar
authored andcommitted
Fix insights (#388)
Insight fixes
1 parent d0bd51c commit 5be1ff6

File tree

6 files changed

+116
-42
lines changed

6 files changed

+116
-42
lines changed

src/__tests__/insightQueries.test.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as moment from 'moment';
2+
import { CONVERSATION_STATUSES } from '../data/constants';
23
import insightQueries from '../data/resolvers/queries/insights/insights';
34
import { graphqlRequest } from '../db/connection';
45
import {
@@ -46,9 +47,6 @@ describe('insightQueries', () => {
4647
integrationId: integration._id,
4748
});
4849

49-
await conversationFactory({
50-
integrationId: integration._id,
51-
});
5250
await conversationMessageFactory({
5351
conversationId: conversation._id,
5452
userId: null,
@@ -96,8 +94,20 @@ describe('insightQueries', () => {
9694
}
9795
`;
9896

99-
const jsonResponse = await graphqlRequest(qry, 'insights', doc);
100-
expect(jsonResponse.integration).toBeDefined();
97+
const jsonResponseBefore = await graphqlRequest(qry, 'insights', doc);
98+
expect(jsonResponseBefore.integration[5].value).toEqual(1);
99+
100+
// conversation that is closed automatically
101+
await conversationFactory({
102+
status: CONVERSATION_STATUSES.CLOSED,
103+
integrationId: integration._id,
104+
closedAt: undefined,
105+
closedUserId: undefined,
106+
});
107+
108+
const jsonResponseAfter = await graphqlRequest(qry, 'insights', doc);
109+
// don't count an automatically closed conversation
110+
expect(jsonResponseAfter.integration[5].value).toEqual(1);
101111
});
102112

103113
test('insightsPunchCard', async () => {
@@ -135,8 +145,20 @@ describe('insightQueries', () => {
135145
}
136146
`;
137147

138-
const jsonResponse = await graphqlRequest(qry, 'insightsConversation', doc);
139-
expect(jsonResponse.summary).toBeDefined();
148+
const responseBefore = await graphqlRequest(qry, 'insightsConversation', doc);
149+
150+
expect(responseBefore.trend[0].y).toBe(1);
151+
152+
// conversation that is closed automatically
153+
await conversationFactory({
154+
status: CONVERSATION_STATUSES.CLOSED,
155+
closedAt: undefined,
156+
closedUserId: undefined,
157+
});
158+
159+
const responseAfter = await graphqlRequest(qry, 'insightsConversation', doc);
160+
// don't count an automatically closed conversation
161+
expect(responseAfter.trend[0].y).toBe(1);
140162
});
141163

142164
test('insightsFirstResponse', async () => {

src/data/constants.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,9 @@ export const COMPANY_BASIC_INFOS = [
288288
];
289289

290290
export const INSIGHT_BASIC_INFOS = {
291-
count: 'Conversations by customer count',
291+
count: 'Customer count',
292292
messageCount: 'Conversation message count',
293-
customerCount: 'Customer count',
293+
customerCount: 'Conversations by customer count',
294294
customerCountPercentage: 'Customer Count Percentage',
295295
resolvedCount: 'Resolved conversation',
296296
averageResponseDuration: 'Average duration of total',

src/data/resolvers/queries/insights/dealInsights.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const dealInsightQueries = {
2525

2626
// check & convert endDate's value
2727
const end = moment(fixDate(endDate)).format('YYYY-MM-DD');
28-
const start = moment(end).add(-7, 'days');
28+
const start = moment(end).add(-30, 'days');
2929

3030
const matchMessageSelector = {
3131
// client or user
@@ -40,7 +40,7 @@ const dealInsightQueries = {
4040
*/
4141
async dealInsightsMain(_root, args: IDealListArgs) {
4242
const { startDate, endDate, status } = args;
43-
const { start, end } = fixDates(startDate, endDate);
43+
const { start, end } = fixDates(startDate, endDate, 30);
4444

4545
const selector = await getDealSelector(args);
4646

src/data/resolvers/queries/insights/insightExport.ts

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
getConversationSelector,
1414
getFilterSelector,
1515
getTimezone,
16+
noConversationSelector,
1617
} from './utils';
1718

1819
import { addCell, addHeader, convertTime, dateToString, fixNumber, nextTime } from './exportUtils';
@@ -57,7 +58,7 @@ const insightExportQueries = {
5758
const { start, end } = fixDates(startDate, endDate, diffCount);
5859
const conversationSelector = {
5960
createdAt: { $gte: start, $lte: end },
60-
$or: [{ userId: { $exists: true }, messageCount: { $gt: 1 } }, { userId: { $exists: false } }],
61+
...noConversationSelector,
6162
};
6263

6364
const mainSelector = await getConversationSelector(args, conversationSelector);
@@ -80,9 +81,6 @@ const insightExportQueries = {
8081
$group: {
8182
_id: '$date',
8283
uniqueCustomerIds: { $addToSet: '$customerId' },
83-
resolvedCount: {
84-
$sum: { $cond: [{ $eq: ['$status', 'closed'] }, 1, 0] },
85-
},
8684
totalCount: { $sum: 1 },
8785
totalResponseTime: { $sum: '$firstRespondTime' },
8886
totalCloseTime: { $sum: '$closeTime' },
@@ -94,7 +92,6 @@ const insightExportQueries = {
9492
totalCount: 1,
9593
totalCloseTime: 1,
9694
totalResponseTime: 1,
97-
resolvedCount: 1,
9895
percentage: {
9996
$multiply: [
10097
{
@@ -107,6 +104,35 @@ const insightExportQueries = {
107104
},
108105
]);
109106

107+
const resolvedSelector = {
108+
closedAt: { $gte: start, $lte: end },
109+
status: 'closed',
110+
...noConversationSelector,
111+
};
112+
113+
const mainResolvedSelector = await getConversationSelector(args, resolvedSelector, 'closedAt');
114+
115+
const resolvedAggregatedData = await Conversations.aggregate([
116+
{
117+
$match: mainResolvedSelector,
118+
},
119+
{
120+
$project: {
121+
date: await getDateFieldAsStr({
122+
fieldName: '$closedAt',
123+
timeFormat: aggregationTimeFormat,
124+
timeZone: getTimezone(user),
125+
}),
126+
},
127+
},
128+
{
129+
$group: {
130+
_id: '$date',
131+
resolvedCount: { $sum: 1 },
132+
},
133+
},
134+
]);
135+
110136
const volumeDictionary = {};
111137

112138
let totalCustomerCount = 0;
@@ -133,7 +159,6 @@ const insightExportQueries = {
133159
{
134160
$project: {
135161
date: await getDateFieldAsStr({ timeFormat: aggregationTimeFormat, timeZone: getTimezone(user) }),
136-
status: 1,
137162
},
138163
},
139164
{
@@ -150,38 +175,38 @@ const insightExportQueries = {
150175
totalConversationMessages += row.totalCount;
151176
});
152177

178+
const resolvedDictionary = {};
179+
resolvedAggregatedData.forEach(row => {
180+
resolvedDictionary[row._id] = row.resolvedCount;
181+
totalResolved += row.resolvedCount;
182+
});
183+
153184
const data: IVolumeReportExportArgs[] = [];
154185

155186
let begin = start;
156187
const generateData = async () => {
157188
const next = nextTime(begin, type);
158189
const dateKey = moment(begin).format(timeFormat);
159-
const {
160-
resolvedCount,
161-
totalCount,
162-
totalResponseTime,
163-
totalCloseTime,
164-
uniqueCustomerCount,
165-
percentage,
166-
} = volumeDictionary[dateKey] || {
167-
resolvedCount: 0,
190+
const { totalCount, totalResponseTime, totalCloseTime, uniqueCustomerCount, percentage } = volumeDictionary[
191+
dateKey
192+
] || {
168193
totalCount: 0,
169194
totalResponseTime: 0,
170195
totalCloseTime: 0,
171196
uniqueCustomerCount: 0,
172197
percentage: 0,
173198
};
174199
const messageCount = conversationDictionary[dateKey] || 0;
200+
const resolvedCount = resolvedDictionary[dateKey] || 0;
175201

176202
totalCustomerCount += totalCount;
177-
totalResolved += resolvedCount;
178203

179204
totalUniqueCount += uniqueCustomerCount;
180205

181206
totalClosedTime += totalCloseTime;
182207
totalRespondTime += totalResponseTime;
183208

184-
averageResponseDuration = fixNumber(totalCloseTime / resolvedCount);
209+
averageResponseDuration = fixNumber(totalCloseTime / totalCount);
185210
firstResponseDuration = fixNumber(totalResponseTime / totalCount);
186211

187212
data.push({
@@ -211,7 +236,7 @@ const insightExportQueries = {
211236
customerCountPercentage: `${((totalUniqueCount / totalCustomerCount) * 100).toFixed(0)}%`,
212237
messageCount: totalConversationMessages,
213238
resolvedCount: totalResolved,
214-
averageResponseDuration: convertTime(fixNumber(totalClosedTime / totalResolved)),
239+
averageResponseDuration: convertTime(fixNumber(totalClosedTime / totalCustomerCount)),
215240
firstResponseDuration: convertTime(fixNumber(totalRespondTime / totalCustomerCount)),
216241
});
217242

@@ -524,7 +549,7 @@ const insightExportQueries = {
524549
const tagData = await Conversations.aggregate([
525550
{
526551
$match: {
527-
$or: [{ userId: { $exists: true }, messageCount: { $gt: 1 } }, { userId: { $exists: false } }],
552+
...noConversationSelector,
528553
integrationId: { $in: rawIntegrationIds },
529554
createdAt: filterSelector.createdAt,
530555
},

src/data/resolvers/queries/insights/insights.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
getFilterSelector,
1919
getSummaryData,
2020
getTimezone,
21+
noConversationSelector,
2122
} from './utils';
2223

2324
const insightQueries = {
@@ -37,7 +38,7 @@ const insightQueries = {
3738

3839
const conversationSelector = {
3940
createdAt: { $gte: start, $lte: end },
40-
$or: [{ userId: { $exists: true }, messageCount: { $gt: 1 } }, { userId: { $exists: false } }],
41+
...noConversationSelector,
4142
};
4243

4344
// count conversations by each integration kind
@@ -152,7 +153,6 @@ const insightQueries = {
152153
*/
153154
async insightsSummaryData(_root, args: IListArgs) {
154155
const { startDate, endDate, type } = args;
155-
156156
const messageSelector = await generateMessageSelector({
157157
args,
158158
excludeBot: true,
@@ -175,18 +175,22 @@ const insightQueries = {
175175
*/
176176
async insightsConversation(_root, args: IListArgs) {
177177
const filterSelector = getFilterSelector(args);
178+
178179
const conversationSelector = {
179180
createdAt: filterSelector.createdAt,
180-
$or: [{ userId: { $exists: true }, messageCount: { $gt: 1 } }, { userId: { $exists: false } }],
181+
...noConversationSelector,
181182
};
183+
182184
const conversations = await findConversations(filterSelector, { ...conversationSelector });
185+
183186
const insightData: any = {
184187
summary: [],
185188
trend: await generateChartDataByCollection(conversations),
186189
};
187190

188191
const { startDate, endDate } = args;
189192
const { start, end } = fixDates(startDate, endDate);
193+
190194
insightData.summary = await getSummaryData({
191195
startDate: start,
192196
endDate: end,

src/data/resolvers/queries/insights/utils.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { IMessageDocument } from '../../../../db/models/definitions/conversation
1313
import { IConversationDocument } from '../../../../db/models/definitions/conversations';
1414
import { IStageDocument } from '../../../../db/models/definitions/deals';
1515
import { IUser } from '../../../../db/models/definitions/users';
16-
import { INSIGHT_TYPES } from '../../../constants';
16+
import { CONVERSATION_STATUSES, INSIGHT_TYPES } from '../../../constants';
1717
import { getDateFieldAsStr } from '../aggregationUtils';
1818
import { fixDate } from '../utils';
1919
import {
@@ -37,7 +37,7 @@ import {
3737
* Return filterSelector
3838
* @param args
3939
*/
40-
export const getFilterSelector = (args: IListArgs): any => {
40+
export const getFilterSelector = (args: IListArgs, fieldName: string = 'createdAt'): any => {
4141
const selector: IFilterSelector = { integration: {} };
4242
const { startDate, endDate, integrationIds, brandIds } = args;
4343
const { start, end } = fixDates(startDate, endDate);
@@ -50,7 +50,7 @@ export const getFilterSelector = (args: IListArgs): any => {
5050
selector.integration.brandId = { $in: brandIds.split(',') };
5151
}
5252

53-
selector.createdAt = { $gte: start, $lte: end };
53+
selector[fieldName] = { $gte: start, $lte: end };
5454

5555
return selector;
5656
};
@@ -61,7 +61,7 @@ export const getFilterSelector = (args: IListArgs): any => {
6161
*/
6262
export const getDealSelector = async (args: IDealListArgs): Promise<IDealSelector> => {
6363
const { startDate, endDate, boardId, pipelineIds, status } = args;
64-
const { start, end } = fixDates(startDate, endDate);
64+
const { start, end } = fixDates(startDate, endDate, 30);
6565

6666
const selector: IDealSelector = {};
6767
const date = {
@@ -110,14 +110,19 @@ export const getDealSelector = async (args: IDealListArgs): Promise<IDealSelecto
110110
* @param conversationSelector
111111
* @param selectIds
112112
*/
113-
export const getConversationSelector = async (args: IListArgs, conversationSelector: any): Promise<any> => {
114-
const filterSelector = await getFilterSelector(args);
113+
export const getConversationSelector = async (
114+
args: IListArgs,
115+
conversationSelector: any,
116+
fieldName: string = 'createdAt',
117+
): Promise<any> => {
118+
const filterSelector = await getFilterSelector(args, fieldName);
115119

116-
if (filterSelector.integration) {
120+
if (Object.keys(filterSelector.integration).length > 0) {
117121
const integrationIds = await Integrations.find(filterSelector.integration).select('_id');
118122
conversationSelector.integrationId = { $in: integrationIds.map(row => row._id) };
119123
}
120-
conversationSelector.createdAt = filterSelector.createdAt;
124+
125+
conversationSelector[fieldName] = filterSelector[fieldName];
121126

122127
return conversationSelector;
123128
};
@@ -230,7 +235,6 @@ export const generateMessageSelector = async ({
230235
// While searching by integration
231236
if (Object.keys(filterSelector.integration).length > 0) {
232237
const conversationIds = await findConversations(filterSelector, { createdAt: updatedCreatedAt }, true);
233-
234238
const rawConversationIds = conversationIds.map(obj => obj._id);
235239
messageSelector.conversationId = { $in: rawConversationIds };
236240
}
@@ -539,3 +543,22 @@ export const generateResponseData = async (
539543
export const getTimezone = (user: IUser): string => {
540544
return (user.details ? user.details.location : '+08') || '+08';
541545
};
546+
547+
export const noConversationSelector = {
548+
$or: [
549+
{ userId: { $exists: true }, messageCount: { $gt: 1 } },
550+
{
551+
userId: { $exists: false },
552+
$or: [
553+
{
554+
closedAt: { $exists: true },
555+
closedUserId: { $exists: true },
556+
status: CONVERSATION_STATUSES.CLOSED,
557+
},
558+
{
559+
status: { $ne: CONVERSATION_STATUSES.CLOSED },
560+
},
561+
],
562+
},
563+
],
564+
};

0 commit comments

Comments
 (0)