@oneuptime/common 11.0.4 → 11.0.5

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.
Files changed (62) hide show
  1. package/Server/Infrastructure/GlobalCache.ts +11 -0
  2. package/Server/Services/CephClusterService.ts +22 -10
  3. package/Server/Services/CloudResourceService.ts +22 -10
  4. package/Server/Services/DockerHostService.ts +22 -10
  5. package/Server/Services/DockerSwarmClusterService.ts +22 -10
  6. package/Server/Services/HostService.ts +22 -10
  7. package/Server/Services/KubernetesClusterService.ts +22 -10
  8. package/Server/Services/PodmanHostService.ts +22 -10
  9. package/Server/Services/ProxmoxClusterService.ts +22 -10
  10. package/Server/Services/RumApplicationService.ts +22 -10
  11. package/Server/Services/ServerlessFunctionService.ts +22 -10
  12. package/Tests/Server/Infrastructure/GlobalCache.test.ts +43 -0
  13. package/UI/Components/Charts/Area/AreaChart.tsx +4 -8
  14. package/UI/Components/Charts/Bar/BarChart.tsx +4 -8
  15. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +4 -8
  16. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +1 -1
  17. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +1 -1
  18. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +1 -1
  19. package/UI/Components/Charts/Line/LineChart.tsx +4 -8
  20. package/UI/Components/EditionLabel/EditionLabel.tsx +68 -16
  21. package/UI/Components/Modal/Modal.tsx +4 -1
  22. package/build/dist/Server/Infrastructure/GlobalCache.js +13 -0
  23. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  24. package/build/dist/Server/Services/CephClusterService.js +19 -2
  25. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  26. package/build/dist/Server/Services/CloudResourceService.js +19 -2
  27. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  28. package/build/dist/Server/Services/DockerHostService.js +19 -2
  29. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  30. package/build/dist/Server/Services/DockerSwarmClusterService.js +19 -2
  31. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  32. package/build/dist/Server/Services/HostService.js +19 -2
  33. package/build/dist/Server/Services/HostService.js.map +1 -1
  34. package/build/dist/Server/Services/KubernetesClusterService.js +19 -2
  35. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  36. package/build/dist/Server/Services/PodmanHostService.js +19 -2
  37. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  38. package/build/dist/Server/Services/ProxmoxClusterService.js +19 -2
  39. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  40. package/build/dist/Server/Services/RumApplicationService.js +19 -2
  41. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  42. package/build/dist/Server/Services/ServerlessFunctionService.js +19 -2
  43. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  44. package/build/dist/UI/Components/Charts/Area/AreaChart.js +2 -6
  45. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  46. package/build/dist/UI/Components/Charts/Bar/BarChart.js +2 -6
  47. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  48. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +8 -8
  49. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  50. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +1 -1
  51. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  52. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +1 -1
  53. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  54. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +1 -1
  55. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  56. package/build/dist/UI/Components/Charts/Line/LineChart.js +2 -6
  57. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  58. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +31 -5
  59. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  60. package/build/dist/UI/Components/Modal/Modal.js +1 -1
  61. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  62. package/package.json +1 -1
@@ -175,4 +175,15 @@ export default abstract class GlobalCache {
175
175
  */
176
176
  await client.set(`${namespace}-${key}`, value, "EX", expiresInSeconds);
177
177
  }
178
+
179
+ @CaptureSpan()
180
+ public static async deleteKey(namespace: string, key: string): Promise<void> {
181
+ const client: ClientType | null = Redis.getClient();
182
+
183
+ if (!client || !Redis.isConnected()) {
184
+ throw new DatabaseNotConnectedException("Cache is not connected");
185
+ }
186
+
187
+ await client.del(`${namespace}-${key}`);
188
+ }
178
189
  }
@@ -200,21 +200,33 @@ export class Service extends DatabaseService<Model> {
200
200
  )
201
201
  .digest("hex");
202
202
 
203
- const cached: string | null = await GlobalCache.getString(
204
- LAST_SEEN_CACHE_NAMESPACE,
205
- cacheKey,
206
- );
203
+ let cached: string | null = null;
204
+ try {
205
+ cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
206
+ } catch {
207
+ /*
208
+ * Cache unavailable — fail open and refresh lastSeenAt anyway. A
209
+ * cache error must never skip the DB write below, otherwise the
210
+ * resource is wrongly marked "disconnected" while telemetry is
211
+ * still flowing. Mirrors shouldRunMaintenance's fail-open stance.
212
+ */
213
+ cached = null;
214
+ }
207
215
 
208
216
  if (cached === extrasFingerprint) {
209
217
  return; // same data was written recently
210
218
  }
211
219
 
212
- await GlobalCache.setString(
213
- LAST_SEEN_CACHE_NAMESPACE,
214
- cacheKey,
215
- extrasFingerprint,
216
- { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
217
- );
220
+ try {
221
+ await GlobalCache.setString(
222
+ LAST_SEEN_CACHE_NAMESPACE,
223
+ cacheKey,
224
+ extrasFingerprint,
225
+ { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
226
+ );
227
+ } catch {
228
+ // Best-effort throttle write; proceed with the DB update regardless.
229
+ }
218
230
 
219
231
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
220
232
  const data: any = {
@@ -170,21 +170,33 @@ export class Service extends DatabaseService<Model> {
170
170
  )
171
171
  .digest("hex");
172
172
 
173
- const cached: string | null = await GlobalCache.getString(
174
- LAST_SEEN_CACHE_NAMESPACE,
175
- cacheKey,
176
- );
173
+ let cached: string | null = null;
174
+ try {
175
+ cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
176
+ } catch {
177
+ /*
178
+ * Cache unavailable — fail open and refresh lastSeenAt anyway. A
179
+ * cache error must never skip the DB write below, otherwise the
180
+ * resource is wrongly marked "disconnected" while telemetry is
181
+ * still flowing. Mirrors shouldRunMaintenance's fail-open stance.
182
+ */
183
+ cached = null;
184
+ }
177
185
 
178
186
  if (cached === extrasFingerprint) {
179
187
  return; // same data was written recently
180
188
  }
181
189
 
182
- await GlobalCache.setString(
183
- LAST_SEEN_CACHE_NAMESPACE,
184
- cacheKey,
185
- extrasFingerprint,
186
- { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
187
- );
190
+ try {
191
+ await GlobalCache.setString(
192
+ LAST_SEEN_CACHE_NAMESPACE,
193
+ cacheKey,
194
+ extrasFingerprint,
195
+ { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
196
+ );
197
+ } catch {
198
+ // Best-effort throttle write; proceed with the DB update regardless.
199
+ }
188
200
 
189
201
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
190
202
  const data: any = {
@@ -192,21 +192,33 @@ export class Service extends DatabaseService<Model> {
192
192
  )
193
193
  .digest("hex");
194
194
 
195
- const cached: string | null = await GlobalCache.getString(
196
- LAST_SEEN_CACHE_NAMESPACE,
197
- cacheKey,
198
- );
195
+ let cached: string | null = null;
196
+ try {
197
+ cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
198
+ } catch {
199
+ /*
200
+ * Cache unavailable — fail open and refresh lastSeenAt anyway. A
201
+ * cache error must never skip the DB write below, otherwise the
202
+ * resource is wrongly marked "disconnected" while telemetry is
203
+ * still flowing. Mirrors shouldRunMaintenance's fail-open stance.
204
+ */
205
+ cached = null;
206
+ }
199
207
 
200
208
  if (cached === extrasFingerprint) {
201
209
  return; // same data was written recently
202
210
  }
203
211
 
204
- await GlobalCache.setString(
205
- LAST_SEEN_CACHE_NAMESPACE,
206
- cacheKey,
207
- extrasFingerprint,
208
- { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
209
- );
212
+ try {
213
+ await GlobalCache.setString(
214
+ LAST_SEEN_CACHE_NAMESPACE,
215
+ cacheKey,
216
+ extrasFingerprint,
217
+ { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
218
+ );
219
+ } catch {
220
+ // Best-effort throttle write; proceed with the DB update regardless.
221
+ }
210
222
 
211
223
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
212
224
  const data: any = {
@@ -202,21 +202,33 @@ export class Service extends DatabaseService<Model> {
202
202
  )
203
203
  .digest("hex");
204
204
 
205
- const cached: string | null = await GlobalCache.getString(
206
- LAST_SEEN_CACHE_NAMESPACE,
207
- cacheKey,
208
- );
205
+ let cached: string | null = null;
206
+ try {
207
+ cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
208
+ } catch {
209
+ /*
210
+ * Cache unavailable — fail open and refresh lastSeenAt anyway. A
211
+ * cache error must never skip the DB write below, otherwise the
212
+ * resource is wrongly marked "disconnected" while telemetry is
213
+ * still flowing. Mirrors shouldRunMaintenance's fail-open stance.
214
+ */
215
+ cached = null;
216
+ }
209
217
 
210
218
  if (cached === extrasFingerprint) {
211
219
  return; // same data was written recently
212
220
  }
213
221
 
214
- await GlobalCache.setString(
215
- LAST_SEEN_CACHE_NAMESPACE,
216
- cacheKey,
217
- extrasFingerprint,
218
- { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
219
- );
222
+ try {
223
+ await GlobalCache.setString(
224
+ LAST_SEEN_CACHE_NAMESPACE,
225
+ cacheKey,
226
+ extrasFingerprint,
227
+ { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
228
+ );
229
+ } catch {
230
+ // Best-effort throttle write; proceed with the DB update regardless.
231
+ }
220
232
 
221
233
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
222
234
  const data: any = {
@@ -214,21 +214,33 @@ export class Service extends DatabaseService<Model> {
214
214
  */
215
215
  const cacheKey: string = hostId.toString();
216
216
  const extrasFingerprint: string = this.fingerprintExtras(extra);
217
- const cached: string | null = await GlobalCache.getString(
218
- LAST_SEEN_CACHE_NAMESPACE,
219
- cacheKey,
220
- );
217
+ let cached: string | null = null;
218
+ try {
219
+ cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
220
+ } catch {
221
+ /*
222
+ * Cache unavailable — fail open and refresh lastSeenAt anyway. A
223
+ * cache error must never skip the DB write below, otherwise the
224
+ * resource is wrongly marked "disconnected" while telemetry is
225
+ * still flowing. Mirrors shouldRunMaintenance's fail-open stance.
226
+ */
227
+ cached = null;
228
+ }
221
229
 
222
230
  if (cached === extrasFingerprint) {
223
231
  return; // same data was written recently
224
232
  }
225
233
 
226
- await GlobalCache.setString(
227
- LAST_SEEN_CACHE_NAMESPACE,
228
- cacheKey,
229
- extrasFingerprint,
230
- { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
231
- );
234
+ try {
235
+ await GlobalCache.setString(
236
+ LAST_SEEN_CACHE_NAMESPACE,
237
+ cacheKey,
238
+ extrasFingerprint,
239
+ { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
240
+ );
241
+ } catch {
242
+ // Best-effort throttle write; proceed with the DB update regardless.
243
+ }
232
244
 
233
245
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
234
246
  const data: any = {
@@ -155,21 +155,33 @@ export class Service extends DatabaseService<Model> {
155
155
  )
156
156
  .digest("hex");
157
157
 
158
- const cached: string | null = await GlobalCache.getString(
159
- LAST_SEEN_CACHE_NAMESPACE,
160
- cacheKey,
161
- );
158
+ let cached: string | null = null;
159
+ try {
160
+ cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
161
+ } catch {
162
+ /*
163
+ * Cache unavailable — fail open and refresh lastSeenAt anyway. A
164
+ * cache error must never skip the DB write below, otherwise the
165
+ * resource is wrongly marked "disconnected" while telemetry is
166
+ * still flowing. Mirrors shouldRunMaintenance's fail-open stance.
167
+ */
168
+ cached = null;
169
+ }
162
170
 
163
171
  if (cached === extrasFingerprint) {
164
172
  return; // same data was written recently
165
173
  }
166
174
 
167
- await GlobalCache.setString(
168
- LAST_SEEN_CACHE_NAMESPACE,
169
- cacheKey,
170
- extrasFingerprint,
171
- { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
172
- );
175
+ try {
176
+ await GlobalCache.setString(
177
+ LAST_SEEN_CACHE_NAMESPACE,
178
+ cacheKey,
179
+ extrasFingerprint,
180
+ { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
181
+ );
182
+ } catch {
183
+ // Best-effort throttle write; proceed with the DB update regardless.
184
+ }
173
185
 
174
186
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
175
187
  const data: any = {
@@ -192,21 +192,33 @@ export class Service extends DatabaseService<Model> {
192
192
  )
193
193
  .digest("hex");
194
194
 
195
- const cached: string | null = await GlobalCache.getString(
196
- LAST_SEEN_CACHE_NAMESPACE,
197
- cacheKey,
198
- );
195
+ let cached: string | null = null;
196
+ try {
197
+ cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
198
+ } catch {
199
+ /*
200
+ * Cache unavailable — fail open and refresh lastSeenAt anyway. A
201
+ * cache error must never skip the DB write below, otherwise the
202
+ * resource is wrongly marked "disconnected" while telemetry is
203
+ * still flowing. Mirrors shouldRunMaintenance's fail-open stance.
204
+ */
205
+ cached = null;
206
+ }
199
207
 
200
208
  if (cached === extrasFingerprint) {
201
209
  return; // same data was written recently
202
210
  }
203
211
 
204
- await GlobalCache.setString(
205
- LAST_SEEN_CACHE_NAMESPACE,
206
- cacheKey,
207
- extrasFingerprint,
208
- { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
209
- );
212
+ try {
213
+ await GlobalCache.setString(
214
+ LAST_SEEN_CACHE_NAMESPACE,
215
+ cacheKey,
216
+ extrasFingerprint,
217
+ { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
218
+ );
219
+ } catch {
220
+ // Best-effort throttle write; proceed with the DB update regardless.
221
+ }
210
222
 
211
223
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
212
224
  const data: any = {
@@ -193,21 +193,33 @@ export class Service extends DatabaseService<Model> {
193
193
  )
194
194
  .digest("hex");
195
195
 
196
- const cached: string | null = await GlobalCache.getString(
197
- LAST_SEEN_CACHE_NAMESPACE,
198
- cacheKey,
199
- );
196
+ let cached: string | null = null;
197
+ try {
198
+ cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
199
+ } catch {
200
+ /*
201
+ * Cache unavailable — fail open and refresh lastSeenAt anyway. A
202
+ * cache error must never skip the DB write below, otherwise the
203
+ * resource is wrongly marked "disconnected" while telemetry is
204
+ * still flowing. Mirrors shouldRunMaintenance's fail-open stance.
205
+ */
206
+ cached = null;
207
+ }
200
208
 
201
209
  if (cached === extrasFingerprint) {
202
210
  return; // same data was written recently
203
211
  }
204
212
 
205
- await GlobalCache.setString(
206
- LAST_SEEN_CACHE_NAMESPACE,
207
- cacheKey,
208
- extrasFingerprint,
209
- { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
210
- );
213
+ try {
214
+ await GlobalCache.setString(
215
+ LAST_SEEN_CACHE_NAMESPACE,
216
+ cacheKey,
217
+ extrasFingerprint,
218
+ { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
219
+ );
220
+ } catch {
221
+ // Best-effort throttle write; proceed with the DB update regardless.
222
+ }
211
223
 
212
224
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
213
225
  const data: any = {
@@ -141,21 +141,33 @@ export class Service extends DatabaseService<Model> {
141
141
  )
142
142
  .digest("hex");
143
143
 
144
- const cached: string | null = await GlobalCache.getString(
145
- LAST_SEEN_CACHE_NAMESPACE,
146
- cacheKey,
147
- );
144
+ let cached: string | null = null;
145
+ try {
146
+ cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
147
+ } catch {
148
+ /*
149
+ * Cache unavailable — fail open and refresh lastSeenAt anyway. A
150
+ * cache error must never skip the DB write below, otherwise the
151
+ * resource is wrongly marked "disconnected" while telemetry is
152
+ * still flowing. Mirrors shouldRunMaintenance's fail-open stance.
153
+ */
154
+ cached = null;
155
+ }
148
156
 
149
157
  if (cached === extrasFingerprint) {
150
158
  return; // same data was written recently
151
159
  }
152
160
 
153
- await GlobalCache.setString(
154
- LAST_SEEN_CACHE_NAMESPACE,
155
- cacheKey,
156
- extrasFingerprint,
157
- { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
158
- );
161
+ try {
162
+ await GlobalCache.setString(
163
+ LAST_SEEN_CACHE_NAMESPACE,
164
+ cacheKey,
165
+ extrasFingerprint,
166
+ { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
167
+ );
168
+ } catch {
169
+ // Best-effort throttle write; proceed with the DB update regardless.
170
+ }
159
171
 
160
172
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
161
173
  const data: any = {
@@ -170,21 +170,33 @@ export class Service extends DatabaseService<Model> {
170
170
  )
171
171
  .digest("hex");
172
172
 
173
- const cached: string | null = await GlobalCache.getString(
174
- LAST_SEEN_CACHE_NAMESPACE,
175
- cacheKey,
176
- );
173
+ let cached: string | null = null;
174
+ try {
175
+ cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
176
+ } catch {
177
+ /*
178
+ * Cache unavailable — fail open and refresh lastSeenAt anyway. A
179
+ * cache error must never skip the DB write below, otherwise the
180
+ * resource is wrongly marked "disconnected" while telemetry is
181
+ * still flowing. Mirrors shouldRunMaintenance's fail-open stance.
182
+ */
183
+ cached = null;
184
+ }
177
185
 
178
186
  if (cached === extrasFingerprint) {
179
187
  return; // same data was written recently
180
188
  }
181
189
 
182
- await GlobalCache.setString(
183
- LAST_SEEN_CACHE_NAMESPACE,
184
- cacheKey,
185
- extrasFingerprint,
186
- { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
187
- );
190
+ try {
191
+ await GlobalCache.setString(
192
+ LAST_SEEN_CACHE_NAMESPACE,
193
+ cacheKey,
194
+ extrasFingerprint,
195
+ { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
196
+ );
197
+ } catch {
198
+ // Best-effort throttle write; proceed with the DB update regardless.
199
+ }
188
200
 
189
201
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
190
202
  const data: any = {
@@ -17,6 +17,7 @@ type MockClient = {
17
17
  set: jest.Mock;
18
18
  expire: jest.Mock;
19
19
  get: jest.Mock;
20
+ del: jest.Mock;
20
21
  };
21
22
 
22
23
  describe("GlobalCache.setString", () => {
@@ -27,6 +28,7 @@ describe("GlobalCache.setString", () => {
27
28
  set: jest.fn().mockResolvedValue("OK"),
28
29
  expire: jest.fn().mockResolvedValue(1),
29
30
  get: jest.fn(),
31
+ del: jest.fn().mockResolvedValue(1),
30
32
  };
31
33
  (Redis.getClient as jest.Mock).mockReturnValue(client);
32
34
  (Redis.isConnected as jest.Mock).mockReturnValue(true);
@@ -98,3 +100,44 @@ describe("GlobalCache.setString", () => {
98
100
  expect(client.expire).not.toHaveBeenCalled();
99
101
  });
100
102
  });
103
+
104
+ describe("GlobalCache.deleteKey", () => {
105
+ let client: MockClient;
106
+
107
+ beforeEach(() => {
108
+ client = {
109
+ set: jest.fn().mockResolvedValue("OK"),
110
+ expire: jest.fn().mockResolvedValue(1),
111
+ get: jest.fn(),
112
+ del: jest.fn().mockResolvedValue(1),
113
+ };
114
+ (Redis.getClient as jest.Mock).mockReturnValue(client);
115
+ (Redis.isConnected as jest.Mock).mockReturnValue(true);
116
+ });
117
+
118
+ afterEach(() => {
119
+ jest.clearAllMocks();
120
+ });
121
+
122
+ /*
123
+ * deleteKey backs clearMaintenanceFence (OtelIngestBaseService): when
124
+ * the fenced maintenance work (updateLastSeen) fails, the fence is
125
+ * released so the next ingest batch retries instead of leaving the
126
+ * resource stranded as "disconnected" for the whole TTL window.
127
+ */
128
+ test("deletes the namespaced key", async () => {
129
+ await GlobalCache.deleteKey("ns", "key");
130
+
131
+ expect(client.del).toHaveBeenCalledTimes(1);
132
+ expect(client.del).toHaveBeenCalledWith("ns-key");
133
+ });
134
+
135
+ test("throws when the cache is not connected", async () => {
136
+ (Redis.isConnected as jest.Mock).mockReturnValue(false);
137
+
138
+ await expect(GlobalCache.deleteKey("ns", "key")).rejects.toThrow(
139
+ DatabaseNotConnectedException,
140
+ );
141
+ expect(client.del).not.toHaveBeenCalled();
142
+ });
143
+ });
@@ -108,11 +108,6 @@ const AreaChartElement: FunctionComponent<AreaInternalProps> = (
108
108
  });
109
109
  }, [props.exemplarPoints, props.xAxis]);
110
110
 
111
- const className: string = props.heightInPx ? `` : "h-80";
112
- const style: React.CSSProperties = props.heightInPx
113
- ? { height: `${props.heightInPx}px` }
114
- : {};
115
-
116
111
  const hasNoData: boolean =
117
112
  !props.data ||
118
113
  props.data.length === 0 ||
@@ -134,10 +129,11 @@ const AreaChartElement: FunctionComponent<AreaInternalProps> = (
134
129
  typeof yAxisMaxOption === "number" ? { maxValue: yAxisMaxOption } : {};
135
130
 
136
131
  return (
137
- <div className="relative">
132
+ <div
133
+ className="relative flex flex-1"
134
+ style={props.heightInPx ? { height: `${props.heightInPx}px` } : undefined}
135
+ >
138
136
  <AreaChart
139
- className={className}
140
- style={style}
141
137
  data={records}
142
138
  tickGap={30}
143
139
  index={"Time"}
@@ -54,11 +54,6 @@ const BarChartElement: FunctionComponent<BarInternalProps> = (
54
54
  setRecords(records);
55
55
  }, [props.data]);
56
56
 
57
- const className: string = props.heightInPx ? `` : "h-80";
58
- const style: React.CSSProperties = props.heightInPx
59
- ? { height: `${props.heightInPx}px` }
60
- : {};
61
-
62
57
  const hasNoData: boolean =
63
58
  !props.data ||
64
59
  props.data.length === 0 ||
@@ -67,10 +62,11 @@ const BarChartElement: FunctionComponent<BarInternalProps> = (
67
62
  });
68
63
 
69
64
  return (
70
- <div className="relative">
65
+ <div
66
+ className="relative flex flex-1"
67
+ style={props.heightInPx ? { height: `${props.heightInPx}px` } : undefined}
68
+ >
71
69
  <BarChart
72
- className={className}
73
- style={style}
74
70
  data={records}
75
71
  tickGap={30}
76
72
  index={"Time"}
@@ -49,7 +49,6 @@ export interface Chart {
49
49
  export interface ComponentProps {
50
50
  charts: Array<Chart>;
51
51
  hideCard?: boolean | undefined;
52
- heightInPx?: number | undefined;
53
52
  chartCssClass?: string | undefined;
54
53
  }
55
54
 
@@ -80,7 +79,6 @@ const ChartGroup: FunctionComponent<ComponentProps> = (
80
79
  key={index}
81
80
  {...(chart.props as LineChartProps)}
82
81
  syncid={syncId}
83
- heightInPx={props.heightInPx}
84
82
  exemplarPoints={chart.exemplarPoints}
85
83
  onExemplarClick={chart.onExemplarClick}
86
84
  showLegend={showLegend}
@@ -92,7 +90,6 @@ const ChartGroup: FunctionComponent<ComponentProps> = (
92
90
  key={index}
93
91
  {...(chart.props as BarChartProps)}
94
92
  syncid={syncId}
95
- heightInPx={props.heightInPx}
96
93
  showLegend={showLegend}
97
94
  />
98
95
  );
@@ -102,7 +99,6 @@ const ChartGroup: FunctionComponent<ComponentProps> = (
102
99
  key={index}
103
100
  {...(chart.props as AreaChartProps)}
104
101
  syncid={syncId}
105
- heightInPx={props.heightInPx}
106
102
  exemplarPoints={chart.exemplarPoints}
107
103
  onExemplarClick={chart.onExemplarClick}
108
104
  showLegend={showLegend}
@@ -244,14 +240,14 @@ const ChartGroup: FunctionComponent<ComponentProps> = (
244
240
  return (
245
241
  <>
246
242
  {renderMetricInfoModal()}
247
- <div className="space-y-3">
243
+ <div className="space-y-3 flex-col flex flex-1 w-full">
248
244
  {props.charts.map((chart: Chart, index: number) => {
249
245
  return (
250
246
  <div
251
247
  key={index}
252
- className={`bg-white ${props.chartCssClass || ""}`}
248
+ className={`bg-white ${props.chartCssClass || ""} flex-1 flex-col flex w-full`}
253
249
  >
254
- <div className="px-5 pt-4 pb-4">
250
+ <div className="px-5 pt-4 pb-4 flex flex-col flex-1">
255
251
  <div className="mb-3 pb-3 border-b border-gray-100">
256
252
  <div className="flex items-center">
257
253
  <h3 className="text-sm font-semibold text-gray-800 tracking-tight">
@@ -265,10 +261,10 @@ const ChartGroup: FunctionComponent<ComponentProps> = (
265
261
  </p>
266
262
  )}
267
263
  </div>
264
+ {getChartContent(chart, index)}
268
265
  {chart.seriesControls ? (
269
266
  <div className="mb-3">{chart.seriesControls}</div>
270
267
  ) : null}
271
- {getChartContent(chart, index)}
272
268
  </div>
273
269
  </div>
274
270
  );
@@ -689,7 +689,7 @@ const AreaChart: React.ForwardRefExoticComponent<
689
689
  }
690
690
 
691
691
  return (
692
- <div ref={ref} className={cx("h-80 w-full", className)} {...other}>
692
+ <div ref={ref} className={cx("flex-1 w-full", className)} {...other}>
693
693
  <ResponsiveContainer>
694
694
  <RechartsAreaChart
695
695
  data={data}
@@ -780,7 +780,7 @@ const BarChart: React.ForwardRefExoticComponent<
780
780
  return (
781
781
  <div
782
782
  ref={forwardedRef}
783
- className={cx("h-80 w-full", className)}
783
+ className={cx("flex-1 w-full", className)}
784
784
  data-tremor-id="tremor-raw"
785
785
  {...other}
786
786
  >
@@ -695,7 +695,7 @@ const LineChart: React.ForwardRefExoticComponent<
695
695
  }
696
696
 
697
697
  return (
698
- <div ref={ref} className={cx("h-80 w-full", className)} {...other}>
698
+ <div ref={ref} className={cx("flex-1 w-full", className)} {...other}>
699
699
  <ResponsiveContainer>
700
700
  <RechartsLineChart
701
701
  data={data}