Skip to content

Commit 3f76c88

Browse files
pavelzotikovgithub-actions[bot]neSpecc
authored
Expose chart data as labeled series for accepted and rate-limited metrics (#591)
* Expose rate-limited series alongside accepted chart * new type for chart data * Bump version up to 1.2.24 * Update eventsFactory.js * Update chartDataService.ts * change response in getEventDailyChart * Bump version up to 1.2.27 * create ChartType enum --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Peter <[email protected]>
1 parent 0f8dad9 commit 3f76c88

File tree

7 files changed

+105
-22
lines changed

7 files changed

+105
-22
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hawk.api",
3-
"version": "1.2.26",
3+
"version": "1.2.27",
44
"main": "index.ts",
55
"license": "BUSL-1.1",
66
"scripts": {

src/models/eventsFactory.js

Lines changed: 74 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ const { composeEventPayloadByRepetition } = require('../utils/merge');
1212

1313
const MAX_DB_READ_BATCH_SIZE = Number(process.env.MAX_DB_READ_BATCH_SIZE);
1414

15+
/**
16+
* Chart series labels
17+
*/
18+
const ChartType = {
19+
Accepted: 'accepted',
20+
RateLimited: 'rate-limited',
21+
};
22+
1523
/**
1624
* @typedef {import('mongodb').UpdateWriteOpResult} UpdateWriteOpResult
1725
*/
@@ -449,25 +457,50 @@ class EventsFactory extends Factory {
449457
const days = Math.ceil((end - start) / (24 * 60 * 60 * 1000));
450458

451459
try {
452-
const redisData = await this.chartDataService.getProjectChartData(
453-
projectId,
454-
startDate,
455-
endDate,
456-
groupBy,
457-
timezoneOffset
458-
);
459-
460-
if (redisData && redisData.length > 0) {
461-
return redisData;
462-
}
463-
464-
// Fallback to Mongo (empty groupHash for project-level data)
465-
return this.findChartData(days, timezoneOffset, '');
460+
const [acceptedSeries, rateLimitedSeries] = await Promise.all([
461+
this.chartDataService.getProjectChartData(
462+
projectId,
463+
startDate,
464+
endDate,
465+
groupBy,
466+
timezoneOffset,
467+
'events-accepted'
468+
),
469+
this.chartDataService.getProjectChartData(
470+
projectId,
471+
startDate,
472+
endDate,
473+
groupBy,
474+
timezoneOffset,
475+
'events-rate-limited'
476+
),
477+
]);
478+
479+
return [
480+
{
481+
label: ChartType.Accepted,
482+
data: acceptedSeries,
483+
},
484+
{
485+
label: ChartType.RateLimited,
486+
data: rateLimitedSeries,
487+
},
488+
];
466489
} catch (err) {
467490
console.error('[EventsFactory] getProjectChartData error:', err);
468491

469-
// Fallback to Mongo on error (empty groupHash for project-level data)
470-
return this.findChartData(days, timezoneOffset, '');
492+
const fallbackAccepted = await this.findChartData(days, timezoneOffset, '');
493+
494+
return [
495+
{
496+
label: ChartType.Accepted,
497+
data: fallbackAccepted,
498+
},
499+
{
500+
label: ChartType.RateLimited,
501+
data: this._composeZeroSeries(fallbackAccepted),
502+
},
503+
];
471504
}
472505
}
473506

@@ -480,7 +513,14 @@ class EventsFactory extends Factory {
480513
* @returns {Promise<Array>}
481514
*/
482515
async getEventDailyChart(groupHash, days, timezoneOffset = 0) {
483-
return this.findChartData(days, timezoneOffset, groupHash);
516+
const data = await this.findChartData(days, timezoneOffset, groupHash);
517+
518+
return [
519+
{
520+
label: ChartType.Accepted,
521+
data,
522+
},
523+
];
484524
}
485525

486526
/**
@@ -574,6 +614,23 @@ class EventsFactory extends Factory {
574614
return result;
575615
}
576616

617+
/**
618+
* Compose zero-filled chart series using timestamps from the provided template
619+
*
620+
* @param {Array<{timestamp: number, count: number}>} template - reference series for timestamps
621+
* @returns {Array<{timestamp: number, count: number}>}
622+
*/
623+
_composeZeroSeries(template = []) {
624+
if (!Array.isArray(template) || template.length === 0) {
625+
return [];
626+
}
627+
628+
return template.map((point) => ({
629+
timestamp: point.timestamp,
630+
count: 0,
631+
}));
632+
}
633+
577634
/**
578635
* Returns number of documents that occurred after the last visit time
579636
*

src/redisHelper.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export default class RedisHelper {
3636
constructor() {
3737
if (!process.env.REDIS_URL) {
3838
console.warn('[Redis] REDIS_URL not set, Redis features will be disabled');
39+
3940
return;
4041
}
4142

@@ -49,7 +50,9 @@ export default class RedisHelper {
4950
* Max wait time: 30 seconds
5051
*/
5152
const delay = Math.min(retries * 1000, 30000);
53+
5254
console.log(`[Redis] Reconnecting... attempt ${retries}, waiting ${delay}ms`);
55+
5356
return delay;
5457
},
5558
},
@@ -93,6 +96,7 @@ export default class RedisHelper {
9396
if (!RedisHelper.instance) {
9497
RedisHelper.instance = new RedisHelper();
9598
}
99+
96100
return RedisHelper.instance;
97101
}
98102

@@ -102,6 +106,7 @@ export default class RedisHelper {
102106
public async initialize(): Promise<void> {
103107
if (!this.redisClient) {
104108
console.warn('[Redis] Client not initialized, skipping connection');
109+
105110
return;
106111
}
107112

src/services/chartDataService.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default class ChartDataService {
1919
* @param endDate - end date as ISO string (e.g., '2025-01-31T23:59:59Z')
2020
* @param groupBy - grouping interval in minutes (1=minute, 60=hour, 1440=day)
2121
* @param timezoneOffset - user's local timezone offset in minutes (default: 0)
22+
* @param metricType - Redis metric type suffix (e.g., 'events-accepted', 'events-rate-limited')
2223
* @returns Array of data points with timestamp and count
2324
* @throws Error if Redis is not connected (caller should fallback to MongoDB)
2425
*/
@@ -27,7 +28,8 @@ export default class ChartDataService {
2728
startDate: string,
2829
endDate: string,
2930
groupBy: number,
30-
timezoneOffset = 0
31+
timezoneOffset = 0,
32+
metricType = 'events-accepted'
3133
): Promise<{ timestamp: number; count: number }[]> {
3234
// Check if Redis is connected
3335
if (!this.redisHelper.isConnected()) {
@@ -37,7 +39,7 @@ export default class ChartDataService {
3739

3840
// Determine granularity and compose key
3941
const granularity = getTimeSeriesSuffix(groupBy);
40-
const key = composeProjectMetricsKey(granularity, projectId);
42+
const key = composeProjectMetricsKey(granularity, projectId, metricType);
4143

4244
// Parse ISO date strings to milliseconds
4345
const start = new Date(startDate).getTime();
@@ -46,6 +48,7 @@ export default class ChartDataService {
4648

4749
// Fetch data from Redis
4850
let result: TsRangeResult[] = [];
51+
4952
try {
5053
result = await this.redisHelper.tsRange(
5154
key,
@@ -65,8 +68,10 @@ export default class ChartDataService {
6568

6669
// Transform data from Redis
6770
const dataPoints: { [ts: number]: number } = {};
71+
6872
for (const [tsStr, valStr] of result) {
6973
const tsMs = Number(tsStr);
74+
7075
dataPoints[tsMs] = Number(valStr) || 0;
7176
}
7277

@@ -79,6 +84,7 @@ export default class ChartDataService {
7984

8085
while (current <= end) {
8186
const count = dataPoints[current] || 0;
87+
8288
filled.push({
8389
timestamp: Math.floor((current + timezoneOffset * 60 * 1000) / 1000),
8490
count,

src/typeDefs/chart.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,19 @@ export default gql`
1212
"""
1313
count: Int
1414
}
15+
16+
"""
17+
Chart line definition
18+
"""
19+
type ChartLine {
20+
"""
21+
Series label (e.g., events-accepted)
22+
"""
23+
label: String!
24+
25+
"""
26+
Data points for the series
27+
"""
28+
data: [ChartDataItem!]!
29+
}
1530
`;

src/typeDefs/event.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ type Event {
295295
User's local timezone offset in minutes
296296
"""
297297
timezoneOffset: Int! = 0
298-
): [ChartDataItem!]!
298+
): [ChartLine!]!
299299
}
300300
301301
"""

src/typeDefs/project.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ type Project {
372372
User's local timezone offset in minutes
373373
"""
374374
timezoneOffset: Int! = 0
375-
): [ChartDataItem]
375+
): [ChartLine!]!
376376
"""
377377
Returns number of unread events
378378
"""

0 commit comments

Comments
 (0)