Skip to content

Commit dbf6845

Browse files
committed
add grouping mode: 'hours' or 'days'
1 parent dafb4cb commit dbf6845

File tree

6 files changed

+103
-75
lines changed

6 files changed

+103
-75
lines changed

src/models/eventsFactory.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -402,17 +402,21 @@ class EventsFactory extends Factory {
402402
};
403403
}
404404

405-
async getChartData(hours = 24, timezoneOffset = 0, projectId = '', groupHash = '') {
405+
async getChartData(groupingBy = 'hours', rangeValue = 24, timezoneOffset = 0, projectId = '', groupHash = '') {
406406
try {
407-
const redisData = await this.redis.getChartDataFromRedis(hours, timezoneOffset, projectId, groupHash);
407+
const redisData = await this.redis.getChartDataFromRedis(groupingBy, rangeValue, timezoneOffset, projectId, groupHash);
408408

409409
if (redisData && redisData.length > 0) {
410410
return redisData;
411411
}
412412

413-
return this.findChartData(days = hours, timezoneOffset, groupHash);
413+
const hours = groupingBy === 'hours' ? rangeValue : Math.max(1, rangeValue) * 24;
414+
const days = Math.max(1, Math.ceil(hours / 24));
415+
return this.findChartData(days, timezoneOffset, groupHash);
414416
} catch (err) {
415-
return this.findChartData(days = hours, timezoneOffset, groupHash);
417+
const hours = groupingBy === 'hours' ? rangeValue : Math.max(1, rangeValue) * 24;
418+
const days = Math.max(1, Math.ceil(hours / 24));
419+
return this.findChartData(days, timezoneOffset, groupHash);
416420
}
417421
}
418422

src/redisHelper.ts

Lines changed: 77 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ export default class RedisHelper {
5959
}
6060

6161
public async getChartDataFromRedis(
62-
hours: number, // количество интервалов (часов или дней)
62+
groupingBy: 'hours' | 'days',
63+
rangeValue: number,
6364
timezoneOffset = 0,
6465
projectId = '',
6566
groupHash = ''
@@ -68,71 +69,84 @@ export default class RedisHelper {
6869
throw new Error('Redis client not connected');
6970
}
7071

72+
const suffix = groupingBy === 'hours' ? 'hourly' : 'daily';
7173
const key = groupHash
72-
? `ts:events:${groupHash}:hourly`
74+
? `ts:events:${groupHash}:${suffix}`
7375
: projectId
74-
? `ts:events:${projectId}:hourly`
75-
: `ts:events:hourly`;
76-
77-
const now = Date.now();
78-
79-
// определяем начало выборки
80-
const fromDate = new Date(now);
81-
fromDate.setMinutes(0, 0, 0);
82-
fromDate.setMilliseconds(fromDate.getMilliseconds() - (hours * 60 * 60 * 1000));
83-
const from = fromDate.getTime();
84-
85-
let result: [string, string][] = [];
86-
try {
87-
result = (await this.redisClient.sendCommand([
88-
'TS.RANGE',
89-
key,
90-
from.toString(),
91-
now.toString(),
92-
])) as [string, string][] | [];
93-
} catch (err: any) {
94-
if (err.message.includes('TSDB: the key does not exist')) {
95-
console.warn(`[Redis] Key ${key} does not exist, returning zeroed data`);
96-
result = [];
76+
? `ts:events:${projectId}:${suffix}`
77+
: `ts:events:${suffix}`;
78+
79+
const now = Date.now();
80+
81+
// определяем начало выборки
82+
const fromDate = new Date(now);
83+
if (groupingBy === 'hours') {
84+
fromDate.setMinutes(0, 0, 0);
9785
} else {
98-
throw err;
86+
fromDate.setHours(0, 0, 0, 0);
9987
}
100-
}
101-
102-
console.log(groupHash, result)
103-
104-
// агрегируем события по интервалу
105-
const dataPoints: { [ts: number]: number } = {};
106-
for (const [tsStr] of result) {
107-
const tsMs = Number(tsStr);
108-
const date = new Date(tsMs);
109-
110-
let intervalStart: number;
111-
date.setMinutes(0, 0, 0);
112-
intervalStart = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours());
113-
114-
const intervalWithOffset = intervalStart + timezoneOffset * 60 * 1000;
115-
116-
dataPoints[intervalWithOffset] = (dataPoints[intervalWithOffset] || 0) + 1;
117-
}
118-
119-
// заполняем пропущенные интервалы нулями
120-
const filled: { timestamp: number; count: number }[] = [];
121-
const nowDate = new Date(now);
122-
123-
for (let i = 0; i < hours; i++) {
124-
const date = new Date(nowDate);
125-
126-
date.setHours(date.getHours() - i, 0, 0, 0);
127-
var intervalStart = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours());
128-
129-
const intervalWithOffset = intervalStart + timezoneOffset * 60 * 1000;
130-
filled.push({
131-
timestamp: Math.floor(intervalWithOffset / 1000),
132-
count: dataPoints[intervalWithOffset] || 0,
133-
});
134-
}
135-
136-
return filled.sort((a, b) => a.timestamp - b.timestamp);
88+
fromDate.setMilliseconds(fromDate.getMilliseconds() - (groupingBy === 'hours' ? rangeValue * 60 * 60 * 1000 : rangeValue * 24 * 60 * 60 * 1000));
89+
const from = fromDate.getTime();
90+
91+
let result: [string, string][] = [];
92+
try {
93+
result = (await this.redisClient.sendCommand([
94+
'TS.RANGE',
95+
key,
96+
from.toString(),
97+
now.toString(),
98+
])) as [string, string][] | [];
99+
} catch (err: any) {
100+
if (err.message.includes('TSDB: the key does not exist')) {
101+
console.warn(`[Redis] Key ${key} does not exist, returning zeroed data`);
102+
result = [];
103+
} else {
104+
throw err;
105+
}
106+
}
107+
108+
// агрегируем события по интервалу
109+
const dataPoints: { [ts: number]: number } = {};
110+
for (const [tsStr] of result) {
111+
const tsMs = Number(tsStr);
112+
const date = new Date(tsMs);
113+
114+
let intervalStart: number;
115+
if (groupingBy === 'hours') {
116+
date.setMinutes(0, 0, 0);
117+
intervalStart = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours());
118+
} else {
119+
date.setHours(0, 0, 0, 0);
120+
intervalStart = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
121+
}
122+
123+
const intervalWithOffset = intervalStart + timezoneOffset * 60 * 1000;
124+
dataPoints[intervalWithOffset] = (dataPoints[intervalWithOffset] || 0) + 1;
125+
}
126+
127+
// заполняем пропущенные интервалы нулями
128+
const filled: { timestamp: number; count: number }[] = [];
129+
const nowDate = new Date(now);
130+
131+
for (let i = 0; i < rangeValue; i++) {
132+
const date = new Date(nowDate);
133+
134+
if (groupingBy === 'hours') {
135+
date.setHours(date.getHours() - i, 0, 0, 0);
136+
var intervalStart = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours());
137+
} else {
138+
date.setDate(date.getDate() - i);
139+
date.setHours(0, 0, 0, 0);
140+
var intervalStart = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
141+
}
142+
143+
const intervalWithOffset = intervalStart + timezoneOffset * 60 * 1000;
144+
filled.push({
145+
timestamp: Math.floor(intervalWithOffset / 1000),
146+
count: dataPoints[intervalWithOffset] || 0,
147+
});
148+
}
149+
150+
return filled.sort((a, b) => a.timestamp - b.timestamp);
137151
}
138152
}

src/resolvers/event.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ module.exports = {
8383
* @param {number} timezoneOffset - user's local timezone offset in minutes
8484
* @returns {Promise<ProjectChartItem[]>}
8585
*/
86-
async chartData({ projectId, groupHash }, { days, timezoneOffset }, context) {
86+
async chartData({ projectId, groupHash }, { groupingBy, rangeValue, timezoneOffset }, context) {
8787
const factory = getEventsFactory(context, projectId);
8888

89-
return factory.getChartData(days, timezoneOffset, projectId, groupHash);
89+
return factory.getChartData(groupingBy, rangeValue, timezoneOffset, projectId, groupHash);
9090
},
9191

9292
/**

src/resolvers/project.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,10 +465,10 @@ module.exports = {
465465
*
466466
* @return {Promise<ProjectChartItem[]>}
467467
*/
468-
async chartData(project, { days, timezoneOffset }, context) {
468+
async chartData(project, { groupingBy, rangeValue, timezoneOffset }, context) {
469469
const factory = getEventsFactory(context, project._id);
470470

471-
return factory.getChartData(days, timezoneOffset, project._id);
471+
return factory.getChartData(groupingBy, rangeValue, timezoneOffset, project._id);
472472
},
473473

474474
/**

src/typeDefs/event.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,14 @@ type Event {
282282
"""
283283
chartData(
284284
"""
285-
How many days we need to fetch for displaying in a chart
285+
Grouping mode: 'hours' or 'days'
286286
"""
287-
days: Int! = 0
287+
groupingBy: String! = "hours"
288+
289+
"""
290+
Range value: number of hours or days depending on groupingBy
291+
"""
292+
rangeValue: Int! = 0
288293
289294
"""
290295
User's local timezone offset in minutes

src/typeDefs/project.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,14 @@ type Project {
289289
"""
290290
chartData(
291291
"""
292-
How many days we need to fetch for displaying in a chart
292+
Grouping mode: 'hours' or 'days'
293293
"""
294-
days: Int! = 0
294+
groupingBy: String!
295+
296+
"""
297+
Range value: number of hours or days depending on groupingBy
298+
"""
299+
rangeValue: Int!
295300
296301
"""
297302
User's local timezone offset in minutes

0 commit comments

Comments
 (0)