@checkstack/healthcheck-backend 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +80 -0
- package/drizzle/0008_broad_black_tom.sql +1 -0
- package/drizzle/meta/0008_snapshot.json +420 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +2 -1
- package/src/aggregation-utils.test.ts +65 -0
- package/src/aggregation-utils.ts +111 -29
- package/src/aggregation.test.ts +382 -0
- package/src/index.ts +5 -0
- package/src/queue-executor.test.ts +133 -0
- package/src/queue-executor.ts +40 -1
- package/src/router.ts +12 -0
- package/src/schema.ts +2 -0
- package/src/service-ordering.test.ts +316 -0
- package/src/service-pause.test.ts +50 -0
- package/src/service.ts +67 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,85 @@
|
|
|
1
1
|
# @checkstack/healthcheck-backend
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1f81b60: ### Clickable Run History with Deep Linking
|
|
8
|
+
|
|
9
|
+
**Backend (`healthcheck-backend`):**
|
|
10
|
+
|
|
11
|
+
- Added `getRunById` service method to fetch a single health check run by ID
|
|
12
|
+
|
|
13
|
+
**Schema (`healthcheck-common`):**
|
|
14
|
+
|
|
15
|
+
- Added `getRunById` RPC procedure for fetching individual runs
|
|
16
|
+
- Added `historyRun` route for deep linking to specific runs (`/history/:systemId/:configurationId/:runId`)
|
|
17
|
+
|
|
18
|
+
**Frontend (`healthcheck-frontend`):**
|
|
19
|
+
|
|
20
|
+
- Table rows in Recent Runs and Run History now navigate to detailed view instead of expanding inline
|
|
21
|
+
- Added "Selected Run" card that displays when navigating to a specific run
|
|
22
|
+
- Extracted `ExpandedResultView` into reusable component
|
|
23
|
+
- Fixed layout shift during table pagination by preserving previous data while loading
|
|
24
|
+
- Removed accordion expansion in favor of consistent navigation UX
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- 090143b: ### Health Check Aggregation & UI Fixes
|
|
29
|
+
|
|
30
|
+
**Backend (`healthcheck-backend`):**
|
|
31
|
+
|
|
32
|
+
- Fixed tail-end bucket truncation where the last aggregated bucket was cut off at the interval boundary instead of extending to the query end date
|
|
33
|
+
- Added `rangeEnd` parameter to `reaggregateBuckets()` to properly extend the last bucket
|
|
34
|
+
- Fixed cross-tier merge logic (`mergeTieredBuckets`) to prevent hourly aggregates from blocking fresh raw data
|
|
35
|
+
|
|
36
|
+
**Schema (`healthcheck-common`):**
|
|
37
|
+
|
|
38
|
+
- Added `bucketEnd` field to `AggregatedBucketBaseSchema` so frontends know the actual end time of each bucket
|
|
39
|
+
|
|
40
|
+
**Frontend (`healthcheck-frontend`):**
|
|
41
|
+
|
|
42
|
+
- Updated all components to use `bucket.bucketEnd` instead of calculating from `bucketIntervalSeconds`
|
|
43
|
+
- Fixed aggregation mode detection: changed `>` to `>=` so 7-day queries use aggregated data when `rawRetentionDays` is 7
|
|
44
|
+
- Added ref-based memoization in `useHealthCheckData` to prevent layout shift during signal-triggered refetches
|
|
45
|
+
- Exposed `isFetching` state to show loading spinner during background refetches
|
|
46
|
+
- Added debounced custom date range with Apply button to prevent fetching on every field change
|
|
47
|
+
- Added validation preventing start date >= end date in custom ranges
|
|
48
|
+
- Added sparkline downsampling: when there are 60+ data points, they are aggregated into buckets with informative tooltips
|
|
49
|
+
|
|
50
|
+
**UI (`ui`):**
|
|
51
|
+
|
|
52
|
+
- Fixed `DateRangeFilter` presets to use true sliding windows (removed `startOfDay` from 7-day and 30-day ranges)
|
|
53
|
+
- Added `disabled` prop to `DateRangeFilter` and `DateTimePicker` components
|
|
54
|
+
- Added `onCustomChange` prop to `DateRangeFilter` for debounced custom date handling
|
|
55
|
+
- Improved layout: custom date pickers now inline with preset buttons on desktop
|
|
56
|
+
- Added responsive mobile layout: date pickers stack vertically with down arrow
|
|
57
|
+
- Added validation error display for invalid date ranges
|
|
58
|
+
|
|
59
|
+
- Updated dependencies [1f81b60]
|
|
60
|
+
- Updated dependencies [090143b]
|
|
61
|
+
- @checkstack/healthcheck-common@0.7.0
|
|
62
|
+
|
|
63
|
+
## 0.6.0
|
|
64
|
+
|
|
65
|
+
### Minor Changes
|
|
66
|
+
|
|
67
|
+
- 11d2679: Add ability to pause health check configurations globally. When paused, health checks continue to be scheduled but execution is skipped for all systems using that configuration. Users with manage access can pause/resume from the Health Checks config page.
|
|
68
|
+
- cce5453: Add notification suppression for incidents
|
|
69
|
+
|
|
70
|
+
- Added `suppressNotifications` field to incidents, allowing active incidents to optionally suppress health check notifications
|
|
71
|
+
- When enabled, health status change notifications will not be sent for affected systems while the incident is active (not resolved)
|
|
72
|
+
- Mirrors the existing maintenance notification suppression pattern
|
|
73
|
+
- Added toggle UI in the IncidentEditor dialog
|
|
74
|
+
- Added `hasActiveIncidentWithSuppression` RPC endpoint for service-to-service queries
|
|
75
|
+
|
|
76
|
+
### Patch Changes
|
|
77
|
+
|
|
78
|
+
- Updated dependencies [11d2679]
|
|
79
|
+
- Updated dependencies [cce5453]
|
|
80
|
+
- @checkstack/healthcheck-common@0.6.0
|
|
81
|
+
- @checkstack/incident-common@0.4.0
|
|
82
|
+
|
|
3
83
|
## 0.5.0
|
|
4
84
|
|
|
5
85
|
### Minor Changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "health_check_configurations" ADD COLUMN "paused" boolean DEFAULT false NOT NULL;
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "86171dc8-efcc-4246-a95a-665fdefb1a1f",
|
|
3
|
+
"prevId": "bb50b71f-3f81-4cb2-aac6-7e7564060fa1",
|
|
4
|
+
"version": "7",
|
|
5
|
+
"dialect": "postgresql",
|
|
6
|
+
"tables": {
|
|
7
|
+
"public.health_check_aggregates": {
|
|
8
|
+
"name": "health_check_aggregates",
|
|
9
|
+
"schema": "",
|
|
10
|
+
"columns": {
|
|
11
|
+
"id": {
|
|
12
|
+
"name": "id",
|
|
13
|
+
"type": "uuid",
|
|
14
|
+
"primaryKey": true,
|
|
15
|
+
"notNull": true,
|
|
16
|
+
"default": "gen_random_uuid()"
|
|
17
|
+
},
|
|
18
|
+
"configuration_id": {
|
|
19
|
+
"name": "configuration_id",
|
|
20
|
+
"type": "uuid",
|
|
21
|
+
"primaryKey": false,
|
|
22
|
+
"notNull": true
|
|
23
|
+
},
|
|
24
|
+
"system_id": {
|
|
25
|
+
"name": "system_id",
|
|
26
|
+
"type": "text",
|
|
27
|
+
"primaryKey": false,
|
|
28
|
+
"notNull": true
|
|
29
|
+
},
|
|
30
|
+
"bucket_start": {
|
|
31
|
+
"name": "bucket_start",
|
|
32
|
+
"type": "timestamp",
|
|
33
|
+
"primaryKey": false,
|
|
34
|
+
"notNull": true
|
|
35
|
+
},
|
|
36
|
+
"bucket_size": {
|
|
37
|
+
"name": "bucket_size",
|
|
38
|
+
"type": "bucket_size",
|
|
39
|
+
"typeSchema": "public",
|
|
40
|
+
"primaryKey": false,
|
|
41
|
+
"notNull": true
|
|
42
|
+
},
|
|
43
|
+
"run_count": {
|
|
44
|
+
"name": "run_count",
|
|
45
|
+
"type": "integer",
|
|
46
|
+
"primaryKey": false,
|
|
47
|
+
"notNull": true
|
|
48
|
+
},
|
|
49
|
+
"healthy_count": {
|
|
50
|
+
"name": "healthy_count",
|
|
51
|
+
"type": "integer",
|
|
52
|
+
"primaryKey": false,
|
|
53
|
+
"notNull": true
|
|
54
|
+
},
|
|
55
|
+
"degraded_count": {
|
|
56
|
+
"name": "degraded_count",
|
|
57
|
+
"type": "integer",
|
|
58
|
+
"primaryKey": false,
|
|
59
|
+
"notNull": true
|
|
60
|
+
},
|
|
61
|
+
"unhealthy_count": {
|
|
62
|
+
"name": "unhealthy_count",
|
|
63
|
+
"type": "integer",
|
|
64
|
+
"primaryKey": false,
|
|
65
|
+
"notNull": true
|
|
66
|
+
},
|
|
67
|
+
"latency_sum_ms": {
|
|
68
|
+
"name": "latency_sum_ms",
|
|
69
|
+
"type": "integer",
|
|
70
|
+
"primaryKey": false,
|
|
71
|
+
"notNull": false
|
|
72
|
+
},
|
|
73
|
+
"avg_latency_ms": {
|
|
74
|
+
"name": "avg_latency_ms",
|
|
75
|
+
"type": "integer",
|
|
76
|
+
"primaryKey": false,
|
|
77
|
+
"notNull": false
|
|
78
|
+
},
|
|
79
|
+
"min_latency_ms": {
|
|
80
|
+
"name": "min_latency_ms",
|
|
81
|
+
"type": "integer",
|
|
82
|
+
"primaryKey": false,
|
|
83
|
+
"notNull": false
|
|
84
|
+
},
|
|
85
|
+
"max_latency_ms": {
|
|
86
|
+
"name": "max_latency_ms",
|
|
87
|
+
"type": "integer",
|
|
88
|
+
"primaryKey": false,
|
|
89
|
+
"notNull": false
|
|
90
|
+
},
|
|
91
|
+
"p95_latency_ms": {
|
|
92
|
+
"name": "p95_latency_ms",
|
|
93
|
+
"type": "integer",
|
|
94
|
+
"primaryKey": false,
|
|
95
|
+
"notNull": false
|
|
96
|
+
},
|
|
97
|
+
"aggregated_result": {
|
|
98
|
+
"name": "aggregated_result",
|
|
99
|
+
"type": "jsonb",
|
|
100
|
+
"primaryKey": false,
|
|
101
|
+
"notNull": false
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"indexes": {
|
|
105
|
+
"health_check_aggregates_bucket_unique": {
|
|
106
|
+
"name": "health_check_aggregates_bucket_unique",
|
|
107
|
+
"columns": [
|
|
108
|
+
{
|
|
109
|
+
"expression": "configuration_id",
|
|
110
|
+
"isExpression": false,
|
|
111
|
+
"asc": true,
|
|
112
|
+
"nulls": "last"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"expression": "system_id",
|
|
116
|
+
"isExpression": false,
|
|
117
|
+
"asc": true,
|
|
118
|
+
"nulls": "last"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"expression": "bucket_start",
|
|
122
|
+
"isExpression": false,
|
|
123
|
+
"asc": true,
|
|
124
|
+
"nulls": "last"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"expression": "bucket_size",
|
|
128
|
+
"isExpression": false,
|
|
129
|
+
"asc": true,
|
|
130
|
+
"nulls": "last"
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
"isUnique": true,
|
|
134
|
+
"concurrently": false,
|
|
135
|
+
"method": "btree",
|
|
136
|
+
"with": {}
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"foreignKeys": {
|
|
140
|
+
"health_check_aggregates_configuration_id_health_check_configurations_id_fk": {
|
|
141
|
+
"name": "health_check_aggregates_configuration_id_health_check_configurations_id_fk",
|
|
142
|
+
"tableFrom": "health_check_aggregates",
|
|
143
|
+
"tableTo": "health_check_configurations",
|
|
144
|
+
"columnsFrom": [
|
|
145
|
+
"configuration_id"
|
|
146
|
+
],
|
|
147
|
+
"columnsTo": [
|
|
148
|
+
"id"
|
|
149
|
+
],
|
|
150
|
+
"onDelete": "cascade",
|
|
151
|
+
"onUpdate": "no action"
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"compositePrimaryKeys": {},
|
|
155
|
+
"uniqueConstraints": {},
|
|
156
|
+
"policies": {},
|
|
157
|
+
"checkConstraints": {},
|
|
158
|
+
"isRLSEnabled": false
|
|
159
|
+
},
|
|
160
|
+
"public.health_check_configurations": {
|
|
161
|
+
"name": "health_check_configurations",
|
|
162
|
+
"schema": "",
|
|
163
|
+
"columns": {
|
|
164
|
+
"id": {
|
|
165
|
+
"name": "id",
|
|
166
|
+
"type": "uuid",
|
|
167
|
+
"primaryKey": true,
|
|
168
|
+
"notNull": true,
|
|
169
|
+
"default": "gen_random_uuid()"
|
|
170
|
+
},
|
|
171
|
+
"name": {
|
|
172
|
+
"name": "name",
|
|
173
|
+
"type": "text",
|
|
174
|
+
"primaryKey": false,
|
|
175
|
+
"notNull": true
|
|
176
|
+
},
|
|
177
|
+
"strategy_id": {
|
|
178
|
+
"name": "strategy_id",
|
|
179
|
+
"type": "text",
|
|
180
|
+
"primaryKey": false,
|
|
181
|
+
"notNull": true
|
|
182
|
+
},
|
|
183
|
+
"config": {
|
|
184
|
+
"name": "config",
|
|
185
|
+
"type": "jsonb",
|
|
186
|
+
"primaryKey": false,
|
|
187
|
+
"notNull": true
|
|
188
|
+
},
|
|
189
|
+
"collectors": {
|
|
190
|
+
"name": "collectors",
|
|
191
|
+
"type": "jsonb",
|
|
192
|
+
"primaryKey": false,
|
|
193
|
+
"notNull": false
|
|
194
|
+
},
|
|
195
|
+
"interval_seconds": {
|
|
196
|
+
"name": "interval_seconds",
|
|
197
|
+
"type": "integer",
|
|
198
|
+
"primaryKey": false,
|
|
199
|
+
"notNull": true
|
|
200
|
+
},
|
|
201
|
+
"is_template": {
|
|
202
|
+
"name": "is_template",
|
|
203
|
+
"type": "boolean",
|
|
204
|
+
"primaryKey": false,
|
|
205
|
+
"notNull": false,
|
|
206
|
+
"default": false
|
|
207
|
+
},
|
|
208
|
+
"paused": {
|
|
209
|
+
"name": "paused",
|
|
210
|
+
"type": "boolean",
|
|
211
|
+
"primaryKey": false,
|
|
212
|
+
"notNull": true,
|
|
213
|
+
"default": false
|
|
214
|
+
},
|
|
215
|
+
"created_at": {
|
|
216
|
+
"name": "created_at",
|
|
217
|
+
"type": "timestamp",
|
|
218
|
+
"primaryKey": false,
|
|
219
|
+
"notNull": true,
|
|
220
|
+
"default": "now()"
|
|
221
|
+
},
|
|
222
|
+
"updated_at": {
|
|
223
|
+
"name": "updated_at",
|
|
224
|
+
"type": "timestamp",
|
|
225
|
+
"primaryKey": false,
|
|
226
|
+
"notNull": true,
|
|
227
|
+
"default": "now()"
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"indexes": {},
|
|
231
|
+
"foreignKeys": {},
|
|
232
|
+
"compositePrimaryKeys": {},
|
|
233
|
+
"uniqueConstraints": {},
|
|
234
|
+
"policies": {},
|
|
235
|
+
"checkConstraints": {},
|
|
236
|
+
"isRLSEnabled": false
|
|
237
|
+
},
|
|
238
|
+
"public.health_check_runs": {
|
|
239
|
+
"name": "health_check_runs",
|
|
240
|
+
"schema": "",
|
|
241
|
+
"columns": {
|
|
242
|
+
"id": {
|
|
243
|
+
"name": "id",
|
|
244
|
+
"type": "uuid",
|
|
245
|
+
"primaryKey": true,
|
|
246
|
+
"notNull": true,
|
|
247
|
+
"default": "gen_random_uuid()"
|
|
248
|
+
},
|
|
249
|
+
"configuration_id": {
|
|
250
|
+
"name": "configuration_id",
|
|
251
|
+
"type": "uuid",
|
|
252
|
+
"primaryKey": false,
|
|
253
|
+
"notNull": true
|
|
254
|
+
},
|
|
255
|
+
"system_id": {
|
|
256
|
+
"name": "system_id",
|
|
257
|
+
"type": "text",
|
|
258
|
+
"primaryKey": false,
|
|
259
|
+
"notNull": true
|
|
260
|
+
},
|
|
261
|
+
"status": {
|
|
262
|
+
"name": "status",
|
|
263
|
+
"type": "health_check_status",
|
|
264
|
+
"typeSchema": "public",
|
|
265
|
+
"primaryKey": false,
|
|
266
|
+
"notNull": true
|
|
267
|
+
},
|
|
268
|
+
"latency_ms": {
|
|
269
|
+
"name": "latency_ms",
|
|
270
|
+
"type": "integer",
|
|
271
|
+
"primaryKey": false,
|
|
272
|
+
"notNull": false
|
|
273
|
+
},
|
|
274
|
+
"result": {
|
|
275
|
+
"name": "result",
|
|
276
|
+
"type": "jsonb",
|
|
277
|
+
"primaryKey": false,
|
|
278
|
+
"notNull": false
|
|
279
|
+
},
|
|
280
|
+
"timestamp": {
|
|
281
|
+
"name": "timestamp",
|
|
282
|
+
"type": "timestamp",
|
|
283
|
+
"primaryKey": false,
|
|
284
|
+
"notNull": true,
|
|
285
|
+
"default": "now()"
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
"indexes": {},
|
|
289
|
+
"foreignKeys": {
|
|
290
|
+
"health_check_runs_configuration_id_health_check_configurations_id_fk": {
|
|
291
|
+
"name": "health_check_runs_configuration_id_health_check_configurations_id_fk",
|
|
292
|
+
"tableFrom": "health_check_runs",
|
|
293
|
+
"tableTo": "health_check_configurations",
|
|
294
|
+
"columnsFrom": [
|
|
295
|
+
"configuration_id"
|
|
296
|
+
],
|
|
297
|
+
"columnsTo": [
|
|
298
|
+
"id"
|
|
299
|
+
],
|
|
300
|
+
"onDelete": "cascade",
|
|
301
|
+
"onUpdate": "no action"
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
"compositePrimaryKeys": {},
|
|
305
|
+
"uniqueConstraints": {},
|
|
306
|
+
"policies": {},
|
|
307
|
+
"checkConstraints": {},
|
|
308
|
+
"isRLSEnabled": false
|
|
309
|
+
},
|
|
310
|
+
"public.system_health_checks": {
|
|
311
|
+
"name": "system_health_checks",
|
|
312
|
+
"schema": "",
|
|
313
|
+
"columns": {
|
|
314
|
+
"system_id": {
|
|
315
|
+
"name": "system_id",
|
|
316
|
+
"type": "text",
|
|
317
|
+
"primaryKey": false,
|
|
318
|
+
"notNull": true
|
|
319
|
+
},
|
|
320
|
+
"configuration_id": {
|
|
321
|
+
"name": "configuration_id",
|
|
322
|
+
"type": "uuid",
|
|
323
|
+
"primaryKey": false,
|
|
324
|
+
"notNull": true
|
|
325
|
+
},
|
|
326
|
+
"enabled": {
|
|
327
|
+
"name": "enabled",
|
|
328
|
+
"type": "boolean",
|
|
329
|
+
"primaryKey": false,
|
|
330
|
+
"notNull": true,
|
|
331
|
+
"default": true
|
|
332
|
+
},
|
|
333
|
+
"state_thresholds": {
|
|
334
|
+
"name": "state_thresholds",
|
|
335
|
+
"type": "jsonb",
|
|
336
|
+
"primaryKey": false,
|
|
337
|
+
"notNull": false
|
|
338
|
+
},
|
|
339
|
+
"retention_config": {
|
|
340
|
+
"name": "retention_config",
|
|
341
|
+
"type": "jsonb",
|
|
342
|
+
"primaryKey": false,
|
|
343
|
+
"notNull": false
|
|
344
|
+
},
|
|
345
|
+
"created_at": {
|
|
346
|
+
"name": "created_at",
|
|
347
|
+
"type": "timestamp",
|
|
348
|
+
"primaryKey": false,
|
|
349
|
+
"notNull": true,
|
|
350
|
+
"default": "now()"
|
|
351
|
+
},
|
|
352
|
+
"updated_at": {
|
|
353
|
+
"name": "updated_at",
|
|
354
|
+
"type": "timestamp",
|
|
355
|
+
"primaryKey": false,
|
|
356
|
+
"notNull": true,
|
|
357
|
+
"default": "now()"
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
"indexes": {},
|
|
361
|
+
"foreignKeys": {
|
|
362
|
+
"system_health_checks_configuration_id_health_check_configurations_id_fk": {
|
|
363
|
+
"name": "system_health_checks_configuration_id_health_check_configurations_id_fk",
|
|
364
|
+
"tableFrom": "system_health_checks",
|
|
365
|
+
"tableTo": "health_check_configurations",
|
|
366
|
+
"columnsFrom": [
|
|
367
|
+
"configuration_id"
|
|
368
|
+
],
|
|
369
|
+
"columnsTo": [
|
|
370
|
+
"id"
|
|
371
|
+
],
|
|
372
|
+
"onDelete": "cascade",
|
|
373
|
+
"onUpdate": "no action"
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
"compositePrimaryKeys": {
|
|
377
|
+
"system_health_checks_system_id_configuration_id_pk": {
|
|
378
|
+
"name": "system_health_checks_system_id_configuration_id_pk",
|
|
379
|
+
"columns": [
|
|
380
|
+
"system_id",
|
|
381
|
+
"configuration_id"
|
|
382
|
+
]
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
"uniqueConstraints": {},
|
|
386
|
+
"policies": {},
|
|
387
|
+
"checkConstraints": {},
|
|
388
|
+
"isRLSEnabled": false
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
"enums": {
|
|
392
|
+
"public.bucket_size": {
|
|
393
|
+
"name": "bucket_size",
|
|
394
|
+
"schema": "public",
|
|
395
|
+
"values": [
|
|
396
|
+
"hourly",
|
|
397
|
+
"daily"
|
|
398
|
+
]
|
|
399
|
+
},
|
|
400
|
+
"public.health_check_status": {
|
|
401
|
+
"name": "health_check_status",
|
|
402
|
+
"schema": "public",
|
|
403
|
+
"values": [
|
|
404
|
+
"healthy",
|
|
405
|
+
"unhealthy",
|
|
406
|
+
"degraded"
|
|
407
|
+
]
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
"schemas": {},
|
|
411
|
+
"sequences": {},
|
|
412
|
+
"roles": {},
|
|
413
|
+
"policies": {},
|
|
414
|
+
"views": {},
|
|
415
|
+
"_meta": {
|
|
416
|
+
"columns": {},
|
|
417
|
+
"schemas": {},
|
|
418
|
+
"tables": {}
|
|
419
|
+
}
|
|
420
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/healthcheck-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"@checkstack/healthcheck-common": "workspace:*",
|
|
17
17
|
"@checkstack/integration-backend": "workspace:*",
|
|
18
18
|
"@checkstack/maintenance-common": "workspace:*",
|
|
19
|
+
"@checkstack/incident-common": "workspace:*",
|
|
19
20
|
"@checkstack/queue-api": "workspace:*",
|
|
20
21
|
"@checkstack/signal-common": "workspace:*",
|
|
21
22
|
"@checkstack/command-backend": "workspace:*",
|
|
@@ -342,6 +342,66 @@ describe("aggregation-utils", () => {
|
|
|
342
342
|
expect(result[1].sourceTier).toBe("hourly");
|
|
343
343
|
expect(result[2].sourceTier).toBe("raw");
|
|
344
344
|
});
|
|
345
|
+
|
|
346
|
+
it("raw buckets take precedence even when hourly starts earlier (regression test)", () => {
|
|
347
|
+
/**
|
|
348
|
+
* Regression test for the "Tail-End Stale" bug:
|
|
349
|
+
* When an hourly aggregate (e.g., 21:00-22:00) exists and raw data
|
|
350
|
+
* arrives mid-hour (e.g., 21:48), the raw data should take precedence,
|
|
351
|
+
* not be blocked by the hourly aggregate.
|
|
352
|
+
*
|
|
353
|
+
* Bug scenario:
|
|
354
|
+
* - Hourly aggregate: 21:00 to 22:00
|
|
355
|
+
* - Raw buckets: 21:48 to 22:11 (fresh data)
|
|
356
|
+
* - Old buggy behavior: hourly was processed first (earlier start time),
|
|
357
|
+
* set coveredUntil=22:00, and raw was skipped
|
|
358
|
+
* - Correct behavior: raw always takes precedence, hourly is excluded
|
|
359
|
+
*/
|
|
360
|
+
const baseTime = 21 * HOUR; // 21:00
|
|
361
|
+
|
|
362
|
+
// Hourly bucket covering 21:00-22:00 (stale aggregate)
|
|
363
|
+
const hourlyBuckets = [
|
|
364
|
+
createBucket({
|
|
365
|
+
startMs: baseTime,
|
|
366
|
+
durationMs: HOUR,
|
|
367
|
+
runCount: 60, // Old stale data
|
|
368
|
+
sourceTier: "hourly",
|
|
369
|
+
}),
|
|
370
|
+
];
|
|
371
|
+
|
|
372
|
+
// Raw buckets at 21:48 and 22:00 (fresh data that should NOT be blocked)
|
|
373
|
+
const rawBuckets = [
|
|
374
|
+
createBucket({
|
|
375
|
+
startMs: baseTime + 48 * MINUTE, // 21:48
|
|
376
|
+
durationMs: 12 * MINUTE,
|
|
377
|
+
runCount: 12, // Fresh data
|
|
378
|
+
sourceTier: "raw",
|
|
379
|
+
}),
|
|
380
|
+
createBucket({
|
|
381
|
+
startMs: baseTime + HOUR, // 22:00
|
|
382
|
+
durationMs: 11 * MINUTE,
|
|
383
|
+
runCount: 11, // Fresh data
|
|
384
|
+
sourceTier: "raw",
|
|
385
|
+
}),
|
|
386
|
+
];
|
|
387
|
+
|
|
388
|
+
const result = mergeTieredBuckets({
|
|
389
|
+
rawBuckets,
|
|
390
|
+
hourlyBuckets,
|
|
391
|
+
dailyBuckets: [],
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// CRITICAL: Both raw buckets should be included
|
|
395
|
+
expect(result).toHaveLength(2);
|
|
396
|
+
expect(result[0].sourceTier).toBe("raw");
|
|
397
|
+
expect(result[1].sourceTier).toBe("raw");
|
|
398
|
+
expect(result[0].runCount).toBe(12); // 21:48 bucket
|
|
399
|
+
expect(result[1].runCount).toBe(11); // 22:00 bucket
|
|
400
|
+
|
|
401
|
+
// Hourly bucket should be excluded because raw data covers its range
|
|
402
|
+
const hourlyInResult = result.find((b) => b.sourceTier === "hourly");
|
|
403
|
+
expect(hourlyInResult).toBeUndefined();
|
|
404
|
+
});
|
|
345
405
|
});
|
|
346
406
|
|
|
347
407
|
describe("combineBuckets", () => {
|
|
@@ -520,6 +580,7 @@ describe("aggregation-utils", () => {
|
|
|
520
580
|
sourceBuckets: [],
|
|
521
581
|
targetIntervalMs: HOUR,
|
|
522
582
|
rangeStart: new Date(0),
|
|
583
|
+
rangeEnd: new Date(HOUR),
|
|
523
584
|
});
|
|
524
585
|
|
|
525
586
|
expect(result).toEqual([]);
|
|
@@ -552,6 +613,7 @@ describe("aggregation-utils", () => {
|
|
|
552
613
|
sourceBuckets,
|
|
553
614
|
targetIntervalMs: HOUR,
|
|
554
615
|
rangeStart: new Date(0),
|
|
616
|
+
rangeEnd: new Date(HOUR),
|
|
555
617
|
});
|
|
556
618
|
|
|
557
619
|
expect(result).toHaveLength(1);
|
|
@@ -585,6 +647,7 @@ describe("aggregation-utils", () => {
|
|
|
585
647
|
sourceBuckets,
|
|
586
648
|
targetIntervalMs: HOUR,
|
|
587
649
|
rangeStart: new Date(0),
|
|
650
|
+
rangeEnd: new Date(2 * HOUR),
|
|
588
651
|
});
|
|
589
652
|
|
|
590
653
|
expect(result).toHaveLength(2);
|
|
@@ -611,6 +674,7 @@ describe("aggregation-utils", () => {
|
|
|
611
674
|
sourceBuckets,
|
|
612
675
|
targetIntervalMs: HOUR,
|
|
613
676
|
rangeStart,
|
|
677
|
+
rangeEnd: new Date(rangeStart.getTime() + HOUR),
|
|
614
678
|
});
|
|
615
679
|
|
|
616
680
|
expect(result).toHaveLength(1);
|
|
@@ -633,6 +697,7 @@ describe("aggregation-utils", () => {
|
|
|
633
697
|
sourceBuckets,
|
|
634
698
|
targetIntervalMs: HOUR,
|
|
635
699
|
rangeStart: new Date(0),
|
|
700
|
+
rangeEnd: new Date(3 * HOUR),
|
|
636
701
|
});
|
|
637
702
|
|
|
638
703
|
expect(result).toHaveLength(3);
|