@oneuptime/common 10.0.31 → 10.0.33
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/Models/AnalyticsModels/ExceptionInstance.ts +29 -4
- package/Models/AnalyticsModels/Log.ts +110 -4
- package/Models/AnalyticsModels/Metric.ts +16 -9
- package/Models/AnalyticsModels/MonitorLog.ts +4 -2
- package/Models/AnalyticsModels/Span.ts +79 -6
- package/Models/DatabaseModels/Index.ts +8 -0
- package/Models/DatabaseModels/LogDropFilter.ts +480 -0
- package/Models/DatabaseModels/LogPipeline.ts +412 -0
- package/Models/DatabaseModels/LogPipelineProcessor.ts +430 -0
- package/Models/DatabaseModels/LogScrubRule.ts +516 -0
- package/Server/API/TelemetryAPI.ts +261 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1773402621107-MigrationName.ts +131 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.ts +79 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1773676206197-MigrationName.ts +57 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/AnalyticsDatabaseService.ts +61 -0
- package/Server/Services/LogAggregationService.ts +238 -1
- package/Server/Services/LogDropFilterService.ts +10 -0
- package/Server/Services/LogPipelineProcessorService.ts +10 -0
- package/Server/Services/LogPipelineService.ts +10 -0
- package/Server/Services/LogScrubRuleService.ts +10 -0
- package/Server/Services/TelemetryAttributeService.ts +4 -6
- package/Server/Utils/AnalyticsDatabase/Statement.ts +15 -1
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +126 -11
- package/Tests/Server/Services/LogAggregationService.test.ts +3 -2
- package/Types/AnalyticsDatabase/AnalyticsTableName.ts +9 -0
- package/Types/AnalyticsDatabase/TableColumnType.ts +4 -0
- package/Types/Date.ts +22 -0
- package/Types/Log/LogDropFilterAction.ts +6 -0
- package/Types/Log/LogPipelineProcessorType.ts +44 -0
- package/Types/Log/LogScrubAction.ts +7 -0
- package/Types/Log/LogScrubPatternType.ts +10 -0
- package/Types/Permission.ts +174 -0
- package/UI/Components/LogsViewer/LogsViewer.tsx +152 -4
- package/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.tsx +92 -0
- package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +332 -117
- package/UI/Components/LogsViewer/components/LogSearchBar.tsx +294 -274
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +513 -234
- package/UI/Components/LogsViewer/components/LogsFilterCard.tsx +37 -29
- package/UI/Components/LogsViewer/components/LogsTable.tsx +6 -1
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +106 -0
- package/UI/Utils/LogExport.ts +160 -0
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +28 -4
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Log.js +97 -4
- package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Metric.js +16 -9
- package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/MonitorLog.js +4 -2
- package/build/dist/Models/AnalyticsModels/MonitorLog.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Span.js +73 -6
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +8 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LogDropFilter.js +508 -0
- package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -0
- package/build/dist/Models/DatabaseModels/LogPipeline.js +438 -0
- package/build/dist/Models/DatabaseModels/LogPipeline.js.map +1 -0
- package/build/dist/Models/DatabaseModels/LogPipelineProcessor.js +452 -0
- package/build/dist/Models/DatabaseModels/LogPipelineProcessor.js.map +1 -0
- package/build/dist/Models/DatabaseModels/LogScrubRule.js +545 -0
- package/build/dist/Models/DatabaseModels/LogScrubRule.js.map +1 -0
- package/build/dist/Server/API/TelemetryAPI.js +155 -0
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773402621107-MigrationName.js +52 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773402621107-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773676206197-MigrationName.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773676206197-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +30 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +188 -1
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/LogDropFilterService.js +9 -0
- package/build/dist/Server/Services/LogDropFilterService.js.map +1 -0
- package/build/dist/Server/Services/LogPipelineProcessorService.js +9 -0
- package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -0
- package/build/dist/Server/Services/LogPipelineService.js +9 -0
- package/build/dist/Server/Services/LogPipelineService.js.map +1 -0
- package/build/dist/Server/Services/LogScrubRuleService.js +9 -0
- package/build/dist/Server/Services/LogScrubRuleService.js.map +1 -0
- package/build/dist/Server/Services/TelemetryAttributeService.js +4 -6
- package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +13 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +89 -2
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Tests/Server/Services/LogAggregationService.test.js +3 -2
- package/build/dist/Tests/Server/Services/LogAggregationService.test.js.map +1 -1
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +10 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -0
- package/build/dist/Types/AnalyticsDatabase/TableColumnType.js +4 -0
- package/build/dist/Types/AnalyticsDatabase/TableColumnType.js.map +1 -1
- package/build/dist/Types/Date.js +16 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Log/LogDropFilterAction.js +7 -0
- package/build/dist/Types/Log/LogDropFilterAction.js.map +1 -0
- package/build/dist/Types/Log/LogPipelineProcessorType.js +9 -0
- package/build/dist/Types/Log/LogPipelineProcessorType.js.map +1 -0
- package/build/dist/Types/Log/LogScrubAction.js +8 -0
- package/build/dist/Types/Log/LogScrubAction.js.map +1 -0
- package/build/dist/Types/Log/LogScrubPatternType.js +11 -0
- package/build/dist/Types/Log/LogScrubPatternType.js.map +1 -0
- package/build/dist/Types/Permission.js +152 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +124 -11
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js +36 -0
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +114 -4
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js +17 -5
- package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +229 -122
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js +5 -4
- package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +4 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +28 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Utils/LogExport.js +129 -0
- package/build/dist/UI/Utils/LogExport.js.map +1 -0
- package/package.json +1 -1
|
@@ -14,7 +14,8 @@ import {
|
|
|
14
14
|
Tooltip,
|
|
15
15
|
ResponsiveContainer,
|
|
16
16
|
CartesianGrid,
|
|
17
|
-
|
|
17
|
+
AreaChart,
|
|
18
|
+
Area,
|
|
18
19
|
} from "recharts";
|
|
19
20
|
import RangeStartAndEndDateTime, {
|
|
20
21
|
RangeStartAndEndDateTimeUtil,
|
|
@@ -57,15 +58,28 @@ export interface LogsAnalyticsViewProps {
|
|
|
57
58
|
|
|
58
59
|
const CHART_COLORS: Array<string> = [
|
|
59
60
|
"#6366f1", // indigo
|
|
60
|
-
"#
|
|
61
|
+
"#ec4899", // pink
|
|
61
62
|
"#10b981", // emerald
|
|
62
63
|
"#f59e0b", // amber
|
|
63
64
|
"#06b6d4", // cyan
|
|
64
|
-
"#
|
|
65
|
-
"#
|
|
66
|
-
"#
|
|
65
|
+
"#8b5cf6", // violet
|
|
66
|
+
"#f43f5e", // rose
|
|
67
|
+
"#14b8a6", // teal
|
|
67
68
|
"#64748b", // slate
|
|
68
|
-
"#
|
|
69
|
+
"#84cc16", // lime
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const CHART_COLORS_MUTED: Array<string> = [
|
|
73
|
+
"rgba(99,102,241,0.15)",
|
|
74
|
+
"rgba(236,72,153,0.15)",
|
|
75
|
+
"rgba(16,185,129,0.15)",
|
|
76
|
+
"rgba(245,158,11,0.15)",
|
|
77
|
+
"rgba(6,182,212,0.15)",
|
|
78
|
+
"rgba(139,92,246,0.15)",
|
|
79
|
+
"rgba(244,63,94,0.15)",
|
|
80
|
+
"rgba(20,184,166,0.15)",
|
|
81
|
+
"rgba(100,116,139,0.15)",
|
|
82
|
+
"rgba(132,204,22,0.15)",
|
|
69
83
|
];
|
|
70
84
|
|
|
71
85
|
const DIMENSION_OPTIONS: Array<{ value: string; label: string }> = [
|
|
@@ -162,6 +176,113 @@ function computeDefaultBucketSize(startTime: Date, endTime: Date): number {
|
|
|
162
176
|
return 1440;
|
|
163
177
|
}
|
|
164
178
|
|
|
179
|
+
interface AnalyticsTooltipProps {
|
|
180
|
+
active?: boolean;
|
|
181
|
+
label?: string;
|
|
182
|
+
payload?: Array<{
|
|
183
|
+
dataKey: string;
|
|
184
|
+
value: number;
|
|
185
|
+
color: string;
|
|
186
|
+
}>;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const AnalyticsTooltip: FunctionComponent<AnalyticsTooltipProps> = (
|
|
190
|
+
props: AnalyticsTooltipProps,
|
|
191
|
+
): ReactElement | null => {
|
|
192
|
+
if (!props.active || !props.payload || props.payload.length === 0) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const entries: Array<{ key: string; value: number; color: string }> =
|
|
197
|
+
props.payload
|
|
198
|
+
.filter((entry: { value: number }): boolean => {
|
|
199
|
+
return entry.value > 0;
|
|
200
|
+
})
|
|
201
|
+
.map(
|
|
202
|
+
(entry: {
|
|
203
|
+
dataKey: string;
|
|
204
|
+
value: number;
|
|
205
|
+
color: string;
|
|
206
|
+
}): { key: string; value: number; color: string } => {
|
|
207
|
+
return {
|
|
208
|
+
key: entry.dataKey,
|
|
209
|
+
value: entry.value,
|
|
210
|
+
color: entry.color,
|
|
211
|
+
};
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
if (entries.length === 0) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const total: number = entries.reduce(
|
|
220
|
+
(sum: number, e: { value: number }): number => {
|
|
221
|
+
return sum + e.value;
|
|
222
|
+
},
|
|
223
|
+
0,
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const formatTime: (label: string | undefined) => string = (
|
|
227
|
+
label: string | undefined,
|
|
228
|
+
): string => {
|
|
229
|
+
if (!label) {
|
|
230
|
+
return "";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const date: Date = new Date(label);
|
|
234
|
+
|
|
235
|
+
if (isNaN(date.getTime())) {
|
|
236
|
+
return label;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return date.toLocaleString([], {
|
|
240
|
+
month: "short",
|
|
241
|
+
day: "numeric",
|
|
242
|
+
hour: "2-digit",
|
|
243
|
+
minute: "2-digit",
|
|
244
|
+
hour12: false,
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<div className="rounded-lg border border-gray-200/80 bg-white/95 px-3.5 py-2.5 shadow-lg backdrop-blur-sm">
|
|
250
|
+
<p className="mb-2 border-b border-gray-100 pb-2 font-mono text-[11px] font-medium text-gray-400">
|
|
251
|
+
{formatTime(props.label)}
|
|
252
|
+
</p>
|
|
253
|
+
<div className="space-y-1">
|
|
254
|
+
{entries.map((entry: { key: string; value: number; color: string }) => {
|
|
255
|
+
return (
|
|
256
|
+
<div
|
|
257
|
+
key={entry.key}
|
|
258
|
+
className="flex items-center justify-between gap-8"
|
|
259
|
+
>
|
|
260
|
+
<div className="flex items-center gap-2">
|
|
261
|
+
<span
|
|
262
|
+
className="inline-block h-2.5 w-2.5 rounded-[3px]"
|
|
263
|
+
style={{ backgroundColor: entry.color }}
|
|
264
|
+
/>
|
|
265
|
+
<span className="text-xs text-gray-600">{entry.key}</span>
|
|
266
|
+
</div>
|
|
267
|
+
<span className="font-mono text-xs font-semibold tabular-nums text-gray-800">
|
|
268
|
+
{entry.value.toLocaleString()}
|
|
269
|
+
</span>
|
|
270
|
+
</div>
|
|
271
|
+
);
|
|
272
|
+
})}
|
|
273
|
+
</div>
|
|
274
|
+
{entries.length > 1 && (
|
|
275
|
+
<div className="mt-2 flex items-center justify-between border-t border-gray-100 pt-2">
|
|
276
|
+
<span className="text-[11px] font-medium text-gray-400">Total</span>
|
|
277
|
+
<span className="font-mono text-xs font-bold tabular-nums text-gray-900">
|
|
278
|
+
{total.toLocaleString()}
|
|
279
|
+
</span>
|
|
280
|
+
</div>
|
|
281
|
+
)}
|
|
282
|
+
</div>
|
|
283
|
+
);
|
|
284
|
+
};
|
|
285
|
+
|
|
165
286
|
const LogsAnalyticsView: FunctionComponent<LogsAnalyticsViewProps> = (
|
|
166
287
|
props: LogsAnalyticsViewProps,
|
|
167
288
|
): ReactElement => {
|
|
@@ -324,154 +445,171 @@ const LogsAnalyticsView: FunctionComponent<LogsAnalyticsViewProps> = (
|
|
|
324
445
|
return pivotTimeseriesData(timeseriesData);
|
|
325
446
|
}, [timeseriesData]);
|
|
326
447
|
|
|
448
|
+
const renderSelectControl: (
|
|
449
|
+
label: string,
|
|
450
|
+
value: string | number,
|
|
451
|
+
onChange: (val: string) => void,
|
|
452
|
+
options: Array<{ value: string | number; label: string }>,
|
|
453
|
+
) => ReactElement = (
|
|
454
|
+
label: string,
|
|
455
|
+
value: string | number,
|
|
456
|
+
onChange: (val: string) => void,
|
|
457
|
+
options: Array<{ value: string | number; label: string }>,
|
|
458
|
+
): ReactElement => {
|
|
459
|
+
return (
|
|
460
|
+
<div className="flex items-center">
|
|
461
|
+
<span className="mr-2 text-[11px] font-medium uppercase tracking-wider text-gray-400">
|
|
462
|
+
{label}
|
|
463
|
+
</span>
|
|
464
|
+
<select
|
|
465
|
+
className="appearance-none rounded-md border border-gray-200 bg-white px-2.5 py-1.5 pr-7 text-xs font-medium text-gray-700 shadow-sm transition-all hover:border-gray-300 focus:border-indigo-400 focus:outline-none focus:ring-2 focus:ring-indigo-100"
|
|
466
|
+
value={value}
|
|
467
|
+
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
468
|
+
onChange(e.target.value);
|
|
469
|
+
}}
|
|
470
|
+
style={{
|
|
471
|
+
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E")`,
|
|
472
|
+
backgroundRepeat: "no-repeat",
|
|
473
|
+
backgroundPosition: "right 6px center",
|
|
474
|
+
}}
|
|
475
|
+
>
|
|
476
|
+
{options.map((opt: { value: string | number; label: string }) => {
|
|
477
|
+
return (
|
|
478
|
+
<option key={opt.value} value={opt.value}>
|
|
479
|
+
{opt.label}
|
|
480
|
+
</option>
|
|
481
|
+
);
|
|
482
|
+
})}
|
|
483
|
+
</select>
|
|
484
|
+
</div>
|
|
485
|
+
);
|
|
486
|
+
};
|
|
487
|
+
|
|
327
488
|
const renderQueryBuilder: () => ReactElement = (): ReactElement => {
|
|
328
489
|
return (
|
|
329
|
-
<div className="flex flex-wrap items-center gap-
|
|
330
|
-
{
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
<option value="table">Table</option>
|
|
343
|
-
</select>
|
|
344
|
-
</div>
|
|
490
|
+
<div className="flex flex-wrap items-center gap-4 border-b border-gray-100 px-5 py-3">
|
|
491
|
+
{renderSelectControl(
|
|
492
|
+
"Chart",
|
|
493
|
+
chartType,
|
|
494
|
+
(val: string) => {
|
|
495
|
+
setChartType(val as AnalyticsChartType);
|
|
496
|
+
},
|
|
497
|
+
[
|
|
498
|
+
{ value: "timeseries", label: "Timeseries" },
|
|
499
|
+
{ value: "toplist", label: "Top List" },
|
|
500
|
+
{ value: "table", label: "Table" },
|
|
501
|
+
],
|
|
502
|
+
)}
|
|
345
503
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
</div>
|
|
504
|
+
<div className="h-4 w-px bg-gray-200" />
|
|
505
|
+
|
|
506
|
+
{renderSelectControl(
|
|
507
|
+
"Measure",
|
|
508
|
+
aggregation,
|
|
509
|
+
(val: string) => {
|
|
510
|
+
setAggregation(val as AnalyticsAggregation);
|
|
511
|
+
},
|
|
512
|
+
[
|
|
513
|
+
{ value: "count", label: "Count" },
|
|
514
|
+
{ value: "unique", label: "Unique Count" },
|
|
515
|
+
],
|
|
516
|
+
)}
|
|
360
517
|
|
|
361
|
-
{/* Aggregation field for unique count */}
|
|
362
518
|
{aggregation === "unique" && (
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
{allDimensionOptions.map(
|
|
374
|
-
(opt: { value: string; label: string }) => {
|
|
375
|
-
return (
|
|
376
|
-
<option key={opt.value} value={opt.value}>
|
|
377
|
-
{opt.label}
|
|
378
|
-
</option>
|
|
379
|
-
);
|
|
380
|
-
},
|
|
381
|
-
)}
|
|
382
|
-
</select>
|
|
383
|
-
</div>
|
|
519
|
+
<>
|
|
520
|
+
{renderSelectControl(
|
|
521
|
+
"of",
|
|
522
|
+
aggregationField,
|
|
523
|
+
(val: string) => {
|
|
524
|
+
setAggregationField(val);
|
|
525
|
+
},
|
|
526
|
+
[{ value: "", label: "Select field..." }, ...allDimensionOptions],
|
|
527
|
+
)}
|
|
528
|
+
</>
|
|
384
529
|
)}
|
|
385
530
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const
|
|
531
|
+
<div className="h-4 w-px bg-gray-200" />
|
|
532
|
+
|
|
533
|
+
{renderSelectControl(
|
|
534
|
+
"Group by",
|
|
535
|
+
groupByFields[0] || "",
|
|
536
|
+
(val: string) => {
|
|
537
|
+
setGroupByFields((prev: Array<string>) => {
|
|
538
|
+
const next: Array<string> = [...prev];
|
|
539
|
+
next[0] = val;
|
|
540
|
+
return next.filter((f: string) => {
|
|
541
|
+
return f.length > 0;
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
},
|
|
545
|
+
[{ value: "", label: "None" }, ...allDimensionOptions],
|
|
546
|
+
)}
|
|
547
|
+
|
|
548
|
+
{groupByFields[0] &&
|
|
549
|
+
groupByFields[0].length > 0 &&
|
|
550
|
+
renderSelectControl(
|
|
551
|
+
"then by",
|
|
552
|
+
groupByFields[1] || "",
|
|
553
|
+
(val: string) => {
|
|
394
554
|
setGroupByFields((prev: Array<string>) => {
|
|
395
|
-
const next: Array<string> = [
|
|
396
|
-
|
|
555
|
+
const next: Array<string> = [prev[0] || ""];
|
|
556
|
+
|
|
557
|
+
if (val.length > 0) {
|
|
558
|
+
next.push(val);
|
|
559
|
+
}
|
|
560
|
+
|
|
397
561
|
return next.filter((f: string) => {
|
|
398
562
|
return f.length > 0;
|
|
399
563
|
});
|
|
400
564
|
});
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
565
|
+
},
|
|
566
|
+
[
|
|
567
|
+
{ value: "", label: "None" },
|
|
568
|
+
...allDimensionOptions.filter((opt: { value: string }) => {
|
|
569
|
+
return opt.value !== groupByFields[0];
|
|
570
|
+
}),
|
|
571
|
+
],
|
|
572
|
+
)}
|
|
573
|
+
|
|
574
|
+
{(chartType === "toplist" || chartType === "table") && (
|
|
575
|
+
<>
|
|
576
|
+
<div className="h-4 w-px bg-gray-200" />
|
|
577
|
+
{renderSelectControl(
|
|
578
|
+
"Limit",
|
|
579
|
+
topListLimit,
|
|
580
|
+
(val: string) => {
|
|
581
|
+
setTopListLimit(Number(val));
|
|
411
582
|
},
|
|
583
|
+
TOP_LIST_LIMITS.map((limit: number) => {
|
|
584
|
+
return { value: limit, label: String(limit) };
|
|
585
|
+
}),
|
|
412
586
|
)}
|
|
413
|
-
|
|
414
|
-
</div>
|
|
415
|
-
|
|
416
|
-
{/* Second group by (only if first is set) */}
|
|
417
|
-
{groupByFields[0] && groupByFields[0].length > 0 && (
|
|
418
|
-
<div className="flex items-center gap-1.5">
|
|
419
|
-
<label className="text-xs font-medium text-gray-500">then by</label>
|
|
420
|
-
<select
|
|
421
|
-
className="rounded-md border border-gray-300 bg-white px-2 py-1 text-xs text-gray-700 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
422
|
-
value={groupByFields[1] || ""}
|
|
423
|
-
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
424
|
-
const val: string = e.target.value;
|
|
425
|
-
setGroupByFields((prev: Array<string>) => {
|
|
426
|
-
const next: Array<string> = [prev[0] || ""];
|
|
427
|
-
|
|
428
|
-
if (val.length > 0) {
|
|
429
|
-
next.push(val);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return next.filter((f: string) => {
|
|
433
|
-
return f.length > 0;
|
|
434
|
-
});
|
|
435
|
-
});
|
|
436
|
-
}}
|
|
437
|
-
>
|
|
438
|
-
<option value="">None</option>
|
|
439
|
-
{allDimensionOptions
|
|
440
|
-
.filter((opt: { value: string }) => {
|
|
441
|
-
return opt.value !== groupByFields[0];
|
|
442
|
-
})
|
|
443
|
-
.map((opt: { value: string; label: string }) => {
|
|
444
|
-
return (
|
|
445
|
-
<option key={opt.value} value={opt.value}>
|
|
446
|
-
{opt.label}
|
|
447
|
-
</option>
|
|
448
|
-
);
|
|
449
|
-
})}
|
|
450
|
-
</select>
|
|
451
|
-
</div>
|
|
587
|
+
</>
|
|
452
588
|
)}
|
|
589
|
+
</div>
|
|
590
|
+
);
|
|
591
|
+
};
|
|
453
592
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
)}
|
|
593
|
+
const renderLegend: () => ReactElement = (): ReactElement => {
|
|
594
|
+
if (seriesKeys.length <= 1) {
|
|
595
|
+
return <></>;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return (
|
|
599
|
+
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 px-5 pb-2">
|
|
600
|
+
{seriesKeys.map((key: string, index: number) => {
|
|
601
|
+
return (
|
|
602
|
+
<div key={key} className="flex items-center gap-1.5">
|
|
603
|
+
<span
|
|
604
|
+
className="inline-block h-2.5 w-2.5 rounded-[3px]"
|
|
605
|
+
style={{
|
|
606
|
+
backgroundColor: CHART_COLORS[index % CHART_COLORS.length],
|
|
607
|
+
}}
|
|
608
|
+
/>
|
|
609
|
+
<span className="text-[11px] text-gray-500">{key}</span>
|
|
610
|
+
</div>
|
|
611
|
+
);
|
|
612
|
+
})}
|
|
475
613
|
</div>
|
|
476
614
|
);
|
|
477
615
|
};
|
|
@@ -481,79 +619,139 @@ const LogsAnalyticsView: FunctionComponent<LogsAnalyticsViewProps> = (
|
|
|
481
619
|
return renderEmptyState();
|
|
482
620
|
}
|
|
483
621
|
|
|
484
|
-
|
|
485
|
-
<div className="p-4" style={{ height: 320 }}>
|
|
486
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
487
|
-
<BarChart
|
|
488
|
-
data={pivotedData}
|
|
489
|
-
margin={{ top: 8, right: 16, bottom: 0, left: 0 }}
|
|
490
|
-
barCategoryGap="15%"
|
|
491
|
-
barGap={0}
|
|
492
|
-
>
|
|
493
|
-
<CartesianGrid strokeDasharray="3 3" stroke="#f3f4f6" />
|
|
494
|
-
<XAxis
|
|
495
|
-
dataKey="time"
|
|
496
|
-
tickFormatter={formatTickTime}
|
|
497
|
-
tick={{ fontSize: 11, fill: "#9ca3af" }}
|
|
498
|
-
axisLine={{ stroke: "#e5e7eb" }}
|
|
499
|
-
tickLine={false}
|
|
500
|
-
minTickGap={40}
|
|
501
|
-
interval="preserveStartEnd"
|
|
502
|
-
/>
|
|
503
|
-
<YAxis
|
|
504
|
-
tick={{ fontSize: 11, fill: "#9ca3af" }}
|
|
505
|
-
axisLine={false}
|
|
506
|
-
tickLine={false}
|
|
507
|
-
width={56}
|
|
508
|
-
allowDecimals={false}
|
|
509
|
-
tickFormatter={formatYAxisTick}
|
|
510
|
-
/>
|
|
511
|
-
<Tooltip
|
|
512
|
-
contentStyle={{
|
|
513
|
-
fontSize: 12,
|
|
514
|
-
borderRadius: 6,
|
|
515
|
-
border: "1px solid #e5e7eb",
|
|
516
|
-
}}
|
|
517
|
-
labelFormatter={(label: string) => {
|
|
518
|
-
const d: Date = new Date(label);
|
|
519
|
-
|
|
520
|
-
if (isNaN(d.getTime())) {
|
|
521
|
-
return label;
|
|
522
|
-
}
|
|
622
|
+
const useAreaChart: boolean = seriesKeys.length === 1;
|
|
523
623
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
624
|
+
return (
|
|
625
|
+
<div className="px-2 pt-4 pb-2">
|
|
626
|
+
{renderLegend()}
|
|
627
|
+
<div style={{ height: 320 }}>
|
|
628
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
629
|
+
{useAreaChart ? (
|
|
630
|
+
<AreaChart
|
|
631
|
+
data={pivotedData}
|
|
632
|
+
margin={{ top: 8, right: 20, bottom: 4, left: 0 }}
|
|
633
|
+
>
|
|
634
|
+
<defs>
|
|
635
|
+
<linearGradient
|
|
636
|
+
id="areaGradient0"
|
|
637
|
+
x1="0"
|
|
638
|
+
y1="0"
|
|
639
|
+
x2="0"
|
|
640
|
+
y2="1"
|
|
641
|
+
>
|
|
642
|
+
<stop
|
|
643
|
+
offset="0%"
|
|
644
|
+
stopColor={CHART_COLORS[0]}
|
|
645
|
+
stopOpacity={0.2}
|
|
646
|
+
/>
|
|
647
|
+
<stop
|
|
648
|
+
offset="95%"
|
|
649
|
+
stopColor={CHART_COLORS[0]}
|
|
650
|
+
stopOpacity={0.02}
|
|
651
|
+
/>
|
|
652
|
+
</linearGradient>
|
|
653
|
+
</defs>
|
|
654
|
+
<CartesianGrid
|
|
655
|
+
strokeDasharray="none"
|
|
656
|
+
stroke="#f1f5f9"
|
|
657
|
+
vertical={false}
|
|
658
|
+
/>
|
|
659
|
+
<XAxis
|
|
660
|
+
dataKey="time"
|
|
661
|
+
tickFormatter={formatTickTime}
|
|
662
|
+
tick={{ fontSize: 11, fill: "#94a3b8" }}
|
|
663
|
+
axisLine={{ stroke: "#e2e8f0" }}
|
|
664
|
+
tickLine={false}
|
|
665
|
+
minTickGap={50}
|
|
666
|
+
interval="preserveStartEnd"
|
|
667
|
+
dy={8}
|
|
668
|
+
/>
|
|
669
|
+
<YAxis
|
|
670
|
+
tick={{ fontSize: 11, fill: "#94a3b8" }}
|
|
671
|
+
axisLine={false}
|
|
672
|
+
tickLine={false}
|
|
673
|
+
width={52}
|
|
674
|
+
allowDecimals={false}
|
|
675
|
+
tickFormatter={formatYAxisTick}
|
|
676
|
+
/>
|
|
677
|
+
<Tooltip
|
|
678
|
+
content={<AnalyticsTooltip />}
|
|
679
|
+
cursor={{
|
|
680
|
+
stroke: "#c7d2fe",
|
|
681
|
+
strokeWidth: 1,
|
|
682
|
+
strokeDasharray: "4 4",
|
|
683
|
+
}}
|
|
684
|
+
/>
|
|
685
|
+
<Area
|
|
686
|
+
dataKey={seriesKeys[0] || "count"}
|
|
687
|
+
stroke={CHART_COLORS[0]}
|
|
688
|
+
strokeWidth={2}
|
|
689
|
+
fill="url(#areaGradient0)"
|
|
690
|
+
dot={false}
|
|
691
|
+
activeDot={{
|
|
692
|
+
r: 4,
|
|
693
|
+
fill: CHART_COLORS[0],
|
|
694
|
+
stroke: "#fff",
|
|
695
|
+
strokeWidth: 2,
|
|
696
|
+
}}
|
|
550
697
|
isAnimationActive={false}
|
|
551
|
-
maxBarSize={32}
|
|
552
698
|
/>
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
699
|
+
</AreaChart>
|
|
700
|
+
) : (
|
|
701
|
+
<BarChart
|
|
702
|
+
data={pivotedData}
|
|
703
|
+
margin={{ top: 8, right: 20, bottom: 4, left: 0 }}
|
|
704
|
+
barCategoryGap="20%"
|
|
705
|
+
barGap={0}
|
|
706
|
+
>
|
|
707
|
+
<CartesianGrid
|
|
708
|
+
strokeDasharray="none"
|
|
709
|
+
stroke="#f1f5f9"
|
|
710
|
+
vertical={false}
|
|
711
|
+
/>
|
|
712
|
+
<XAxis
|
|
713
|
+
dataKey="time"
|
|
714
|
+
tickFormatter={formatTickTime}
|
|
715
|
+
tick={{ fontSize: 11, fill: "#94a3b8" }}
|
|
716
|
+
axisLine={{ stroke: "#e2e8f0" }}
|
|
717
|
+
tickLine={false}
|
|
718
|
+
minTickGap={50}
|
|
719
|
+
interval="preserveStartEnd"
|
|
720
|
+
dy={8}
|
|
721
|
+
/>
|
|
722
|
+
<YAxis
|
|
723
|
+
tick={{ fontSize: 11, fill: "#94a3b8" }}
|
|
724
|
+
axisLine={false}
|
|
725
|
+
tickLine={false}
|
|
726
|
+
width={52}
|
|
727
|
+
allowDecimals={false}
|
|
728
|
+
tickFormatter={formatYAxisTick}
|
|
729
|
+
/>
|
|
730
|
+
<Tooltip
|
|
731
|
+
content={<AnalyticsTooltip />}
|
|
732
|
+
cursor={{ fill: "rgba(99,102,241,0.04)" }}
|
|
733
|
+
/>
|
|
734
|
+
{seriesKeys.map((key: string, index: number) => {
|
|
735
|
+
return (
|
|
736
|
+
<Bar
|
|
737
|
+
key={key}
|
|
738
|
+
dataKey={key}
|
|
739
|
+
stackId="group"
|
|
740
|
+
fill={CHART_COLORS[index % CHART_COLORS.length]!}
|
|
741
|
+
radius={
|
|
742
|
+
index === seriesKeys.length - 1
|
|
743
|
+
? [3, 3, 0, 0]
|
|
744
|
+
: [0, 0, 0, 0]
|
|
745
|
+
}
|
|
746
|
+
isAnimationActive={false}
|
|
747
|
+
maxBarSize={40}
|
|
748
|
+
/>
|
|
749
|
+
);
|
|
750
|
+
})}
|
|
751
|
+
</BarChart>
|
|
752
|
+
)}
|
|
753
|
+
</ResponsiveContainer>
|
|
754
|
+
</div>
|
|
557
755
|
</div>
|
|
558
756
|
);
|
|
559
757
|
};
|
|
@@ -570,31 +768,61 @@ const LogsAnalyticsView: FunctionComponent<LogsAnalyticsViewProps> = (
|
|
|
570
768
|
1,
|
|
571
769
|
);
|
|
572
770
|
|
|
771
|
+
const totalCount: number = topListData.reduce(
|
|
772
|
+
(sum: number, item: AnalyticsTopItem) => {
|
|
773
|
+
return sum + item.count;
|
|
774
|
+
},
|
|
775
|
+
0,
|
|
776
|
+
);
|
|
777
|
+
|
|
573
778
|
return (
|
|
574
|
-
<div className="p-
|
|
575
|
-
<div className="space-y-
|
|
779
|
+
<div className="p-5">
|
|
780
|
+
<div className="space-y-1.5">
|
|
576
781
|
{topListData.map((item: AnalyticsTopItem, index: number) => {
|
|
577
782
|
const percentage: number = (item.count / maxCount) * 100;
|
|
783
|
+
const sharePercent: number =
|
|
784
|
+
totalCount > 0 ? Math.round((item.count / totalCount) * 100) : 0;
|
|
785
|
+
const color: string =
|
|
786
|
+
CHART_COLORS[index % CHART_COLORS.length] || CHART_COLORS[0]!;
|
|
787
|
+
const mutedColor: string =
|
|
788
|
+
CHART_COLORS_MUTED[index % CHART_COLORS_MUTED.length] ||
|
|
789
|
+
CHART_COLORS_MUTED[0]!;
|
|
790
|
+
|
|
578
791
|
return (
|
|
579
|
-
<div
|
|
580
|
-
|
|
792
|
+
<div
|
|
793
|
+
key={index}
|
|
794
|
+
className="group flex items-center gap-3 rounded-lg px-3 py-2 transition-colors hover:bg-gray-50/80"
|
|
795
|
+
>
|
|
796
|
+
<span className="w-5 text-right font-mono text-[11px] font-medium text-gray-300">
|
|
797
|
+
{index + 1}
|
|
798
|
+
</span>
|
|
799
|
+
<div
|
|
800
|
+
className="h-2 w-2 rounded-full flex-shrink-0"
|
|
801
|
+
style={{ backgroundColor: color }}
|
|
802
|
+
/>
|
|
803
|
+
<div className="w-44 min-w-0 truncate text-sm font-medium text-gray-700">
|
|
581
804
|
{item.value || "(empty)"}
|
|
582
805
|
</div>
|
|
583
806
|
<div className="flex-1">
|
|
584
|
-
<div className="relative h-
|
|
807
|
+
<div className="relative h-7 w-full overflow-hidden rounded-md bg-gray-50">
|
|
585
808
|
<div
|
|
586
|
-
className="absolute left-0 top-0 h-full rounded transition-all"
|
|
809
|
+
className="absolute left-0 top-0 h-full rounded-md transition-all duration-300"
|
|
587
810
|
style={{
|
|
588
811
|
width: `${percentage}%`,
|
|
589
|
-
backgroundColor:
|
|
590
|
-
|
|
812
|
+
backgroundColor: mutedColor,
|
|
813
|
+
borderLeft: `3px solid ${color}`,
|
|
591
814
|
}}
|
|
592
815
|
/>
|
|
593
|
-
<div className="absolute right-2 top-0 flex h-full items-center text-xs font-medium text-gray-600">
|
|
594
|
-
{item.count.toLocaleString()}
|
|
595
|
-
</div>
|
|
596
816
|
</div>
|
|
597
817
|
</div>
|
|
818
|
+
<div className="flex items-center gap-3 flex-shrink-0">
|
|
819
|
+
<span className="font-mono text-sm font-semibold tabular-nums text-gray-800">
|
|
820
|
+
{item.count.toLocaleString()}
|
|
821
|
+
</span>
|
|
822
|
+
<span className="w-10 text-right font-mono text-[11px] tabular-nums text-gray-400">
|
|
823
|
+
{sharePercent}%
|
|
824
|
+
</span>
|
|
825
|
+
</div>
|
|
598
826
|
</div>
|
|
599
827
|
);
|
|
600
828
|
})}
|
|
@@ -612,44 +840,80 @@ const LogsAnalyticsView: FunctionComponent<LogsAnalyticsViewProps> = (
|
|
|
612
840
|
tableData[0]?.groupValues || {},
|
|
613
841
|
);
|
|
614
842
|
|
|
843
|
+
const maxCount: number = Math.max(
|
|
844
|
+
...tableData.map((row: AnalyticsTableRow) => {
|
|
845
|
+
return row.count;
|
|
846
|
+
}),
|
|
847
|
+
1,
|
|
848
|
+
);
|
|
849
|
+
|
|
615
850
|
return (
|
|
616
|
-
<div className="p-
|
|
617
|
-
<div className="overflow-hidden rounded-lg border border-gray-200">
|
|
618
|
-
<table className="min-w-full
|
|
619
|
-
<thead
|
|
620
|
-
<tr>
|
|
851
|
+
<div className="p-5">
|
|
852
|
+
<div className="overflow-hidden rounded-lg border border-gray-200/80">
|
|
853
|
+
<table className="min-w-full">
|
|
854
|
+
<thead>
|
|
855
|
+
<tr className="border-b border-gray-200/80 bg-gray-50/80">
|
|
856
|
+
<th className="w-10 px-4 py-2.5 text-left text-[11px] font-semibold uppercase tracking-wider text-gray-400">
|
|
857
|
+
#
|
|
858
|
+
</th>
|
|
621
859
|
{groupKeys.map((key: string) => {
|
|
622
860
|
return (
|
|
623
861
|
<th
|
|
624
862
|
key={key}
|
|
625
|
-
className="px-4 py-2 text-left text-
|
|
863
|
+
className="px-4 py-2.5 text-left text-[11px] font-semibold uppercase tracking-wider text-gray-400"
|
|
626
864
|
>
|
|
627
865
|
{key}
|
|
628
866
|
</th>
|
|
629
867
|
);
|
|
630
868
|
})}
|
|
631
|
-
<th className="px-4 py-2 text-right text-
|
|
869
|
+
<th className="px-4 py-2.5 text-right text-[11px] font-semibold uppercase tracking-wider text-gray-400">
|
|
632
870
|
Count
|
|
633
871
|
</th>
|
|
872
|
+
<th className="w-48 px-4 py-2.5 text-[11px] font-semibold uppercase tracking-wider text-gray-400" />
|
|
634
873
|
</tr>
|
|
635
874
|
</thead>
|
|
636
|
-
<tbody
|
|
875
|
+
<tbody>
|
|
637
876
|
{tableData.map((row: AnalyticsTableRow, index: number) => {
|
|
877
|
+
const barWidth: number = (row.count / maxCount) * 100;
|
|
878
|
+
const color: string =
|
|
879
|
+
CHART_COLORS[index % CHART_COLORS.length] || CHART_COLORS[0]!;
|
|
880
|
+
const mutedColor: string =
|
|
881
|
+
CHART_COLORS_MUTED[index % CHART_COLORS_MUTED.length] ||
|
|
882
|
+
CHART_COLORS_MUTED[0]!;
|
|
883
|
+
|
|
638
884
|
return (
|
|
639
|
-
<tr
|
|
885
|
+
<tr
|
|
886
|
+
key={index}
|
|
887
|
+
className="border-b border-gray-100/80 transition-colors last:border-b-0 hover:bg-gray-50/50"
|
|
888
|
+
>
|
|
889
|
+
<td className="px-4 py-2.5 font-mono text-[11px] text-gray-300">
|
|
890
|
+
{index + 1}
|
|
891
|
+
</td>
|
|
640
892
|
{groupKeys.map((key: string) => {
|
|
641
893
|
return (
|
|
642
894
|
<td
|
|
643
895
|
key={key}
|
|
644
|
-
className="whitespace-nowrap px-4 py-2 text-
|
|
896
|
+
className="whitespace-nowrap px-4 py-2.5 text-sm text-gray-700"
|
|
645
897
|
>
|
|
646
898
|
{row.groupValues[key] || "(empty)"}
|
|
647
899
|
</td>
|
|
648
900
|
);
|
|
649
901
|
})}
|
|
650
|
-
<td className="whitespace-nowrap px-4 py-2 text-right text-
|
|
902
|
+
<td className="whitespace-nowrap px-4 py-2.5 text-right font-mono text-sm font-semibold tabular-nums text-gray-800">
|
|
651
903
|
{row.count.toLocaleString()}
|
|
652
904
|
</td>
|
|
905
|
+
<td className="px-4 py-2.5">
|
|
906
|
+
<div className="h-5 w-full overflow-hidden rounded bg-gray-50">
|
|
907
|
+
<div
|
|
908
|
+
className="h-full rounded transition-all duration-300"
|
|
909
|
+
style={{
|
|
910
|
+
width: `${barWidth}%`,
|
|
911
|
+
backgroundColor: mutedColor,
|
|
912
|
+
borderLeft: `2px solid ${color}`,
|
|
913
|
+
}}
|
|
914
|
+
/>
|
|
915
|
+
</div>
|
|
916
|
+
</td>
|
|
653
917
|
</tr>
|
|
654
918
|
);
|
|
655
919
|
})}
|
|
@@ -662,8 +926,23 @@ const LogsAnalyticsView: FunctionComponent<LogsAnalyticsViewProps> = (
|
|
|
662
926
|
|
|
663
927
|
const renderEmptyState: () => ReactElement = (): ReactElement => {
|
|
664
928
|
return (
|
|
665
|
-
<div className="flex h-
|
|
666
|
-
|
|
929
|
+
<div className="flex h-72 flex-col items-center justify-center gap-3">
|
|
930
|
+
<svg
|
|
931
|
+
className="h-10 w-10 text-gray-200"
|
|
932
|
+
fill="none"
|
|
933
|
+
viewBox="0 0 24 24"
|
|
934
|
+
stroke="currentColor"
|
|
935
|
+
strokeWidth={1.5}
|
|
936
|
+
>
|
|
937
|
+
<path
|
|
938
|
+
strokeLinecap="round"
|
|
939
|
+
strokeLinejoin="round"
|
|
940
|
+
d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z"
|
|
941
|
+
/>
|
|
942
|
+
</svg>
|
|
943
|
+
<p className="text-sm text-gray-400">
|
|
944
|
+
No data available for the selected query
|
|
945
|
+
</p>
|
|
667
946
|
</div>
|
|
668
947
|
);
|
|
669
948
|
};
|
|
@@ -671,7 +950,7 @@ const LogsAnalyticsView: FunctionComponent<LogsAnalyticsViewProps> = (
|
|
|
671
950
|
const renderChart: () => ReactElement = (): ReactElement => {
|
|
672
951
|
if (isLoading) {
|
|
673
952
|
return (
|
|
674
|
-
<div className="flex h-
|
|
953
|
+
<div className="flex h-72 items-center justify-center">
|
|
675
954
|
<ComponentLoader />
|
|
676
955
|
</div>
|
|
677
956
|
);
|
|
@@ -689,7 +968,7 @@ const LogsAnalyticsView: FunctionComponent<LogsAnalyticsViewProps> = (
|
|
|
689
968
|
};
|
|
690
969
|
|
|
691
970
|
return (
|
|
692
|
-
<div className="overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm">
|
|
971
|
+
<div className="overflow-hidden rounded-lg border border-gray-200/80 bg-white shadow-sm">
|
|
693
972
|
{renderQueryBuilder()}
|
|
694
973
|
{renderChart()}
|
|
695
974
|
</div>
|