querysub 0.357.0 → 0.358.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.
@@ -1,10 +1,10 @@
1
- import { formatDateTime, formatNumber } from "socket-function/src/formatting/format";
1
+ import { formatDate, formatDateTime, formatNumber } from "socket-function/src/formatting/format";
2
2
  import { css } from "typesafecss";
3
3
  import { t } from "../../../2-proxy/schema2";
4
4
  import { qreact } from "../../../4-dom/qreact";
5
5
  import { Button } from "../../../library-components/Button";
6
6
  import { TimeFilePathWithSize } from "./IndexedLogs";
7
- import { keyByArray } from "socket-function/src/misc";
7
+ import { keyBy, keyByArray } from "socket-function/src/misc";
8
8
  import { MachineThreadInfo } from "../../MachineThreadInfo";
9
9
 
10
10
  export type FilePathsByThread = {
@@ -12,6 +12,8 @@ export type FilePathsByThread = {
12
12
  files: TimeFilePathWithSize[];
13
13
  totalSize: number;
14
14
  totalLogCount: number;
15
+ pendingCount: number;
16
+ errorCount: number;
15
17
  };
16
18
 
17
19
  export type FilePathsByMachine = {
@@ -19,12 +21,15 @@ export type FilePathsByMachine = {
19
21
  threads: FilePathsByThread[];
20
22
  totalSize: number;
21
23
  totalLogCount: number;
24
+ pendingCount: number;
25
+ errorCount: number;
22
26
  };
23
27
 
24
28
 
25
29
  export class FilePathSelector extends qreact.Component<{
26
30
  paths: TimeFilePathWithSize[];
27
31
  onChange: (paths: TimeFilePathWithSize[]) => void;
32
+ fileErrors?: { error: string; path: string; }[];
28
33
  }> {
29
34
  state = t.state({
30
35
  showFullScreenModal: t.boolean,
@@ -32,6 +37,7 @@ export class FilePathSelector extends qreact.Component<{
32
37
 
33
38
  getGroupedByMachine(): FilePathsByMachine[] {
34
39
  let byMachine = keyByArray(this.props.paths, (path) => path.machineId || "unknown");
40
+ let errorsByPath = keyBy(this.props.fileErrors || [], x => x.path);
35
41
 
36
42
  let result: FilePathsByMachine[] = [];
37
43
  for (let [machineId, files] of byMachine) {
@@ -40,27 +46,39 @@ export class FilePathSelector extends qreact.Component<{
40
46
  let threads: FilePathsByThread[] = [];
41
47
  let totalSize = 0;
42
48
  let totalLogCount = 0;
49
+ let machinePendingCount = 0;
50
+ let machineErrorCount = 0;
43
51
 
44
52
  for (let [threadId, threadFiles] of byThread) {
45
53
  let threadSize = 0;
46
54
  let threadLogCount = 0;
55
+ let threadPendingCount = 0;
56
+ let threadErrorCount = 0;
47
57
  for (let file of threadFiles) {
48
58
  threadSize += file.size;
49
59
  threadLogCount += file.logCount || 0;
60
+ if (file.logCount === undefined) {
61
+ threadPendingCount++;
62
+ }
63
+ if (errorsByPath.get(file.fullPath)) {
64
+ threadErrorCount++;
65
+ }
50
66
  }
51
- threads.push({ threadId, files: threadFiles, totalSize: threadSize, totalLogCount: threadLogCount });
67
+ threads.push({ threadId, files: threadFiles, totalSize: threadSize, totalLogCount: threadLogCount, pendingCount: threadPendingCount, errorCount: threadErrorCount });
52
68
  totalSize += threadSize;
53
69
  totalLogCount += threadLogCount;
70
+ machinePendingCount += threadPendingCount;
71
+ machineErrorCount += threadErrorCount;
54
72
  }
55
73
 
56
- result.push({ machineId, threads, totalSize, totalLogCount });
74
+ result.push({ machineId, threads, totalSize, totalLogCount, pendingCount: machinePendingCount, errorCount: machineErrorCount });
57
75
  }
58
76
 
59
77
  return result;
60
78
  }
61
79
 
62
80
  getPendingCount(): number {
63
- return this.props.paths.filter(p => p.threadId !== undefined).length;
81
+ return this.props.paths.filter(p => p.logCount === undefined).length;
64
82
  }
65
83
 
66
84
  getUniqueThreadCount(): number {
@@ -76,11 +94,28 @@ export class FilePathSelector extends qreact.Component<{
76
94
  getTotals() {
77
95
  let totalSize = 0;
78
96
  let totalLogCount = 0;
97
+ let minTime = Infinity;
98
+ let maxTime = -Infinity;
79
99
  for (let path of this.props.paths) {
80
100
  totalSize += path.size;
81
101
  totalLogCount += path.logCount || 0;
102
+ if (path.startTime < minTime) {
103
+ minTime = path.startTime;
104
+ }
105
+ if (path.startTime > maxTime) {
106
+ maxTime = path.startTime;
107
+ }
82
108
  }
83
- return { totalSize, totalLogCount, totalPending: this.getPendingCount(), uniqueThreads: this.getUniqueThreadCount() };
109
+ const errorCount = this.props.fileErrors?.length || 0;
110
+ return {
111
+ totalSize,
112
+ totalLogCount,
113
+ totalPending: this.getPendingCount(),
114
+ uniqueThreads: this.getUniqueThreadCount(),
115
+ minTime: minTime === Infinity ? undefined : minTime,
116
+ maxTime: maxTime === -Infinity ? undefined : maxTime,
117
+ errorCount
118
+ };
84
119
  }
85
120
 
86
121
  formatBytes(bytes: number): string {
@@ -94,15 +129,34 @@ export class FilePathSelector extends qreact.Component<{
94
129
  return (
95
130
  <div className={css.vbox(10)}>
96
131
  <div
97
- className={css.hbox(10).pad2(10).bord(1, { h: 0, s: 0, l: 80 }).hsl(240, 20, 95).hslhover(240, 30, 90).cursor("pointer")}
132
+ className={css.hbox(10).pad2(10).bord(1, { h: 0, s: 0, l: 80 }).hsl(240, 20, 95).hslhover(240, 30, 90).cursor("pointer").wrap}
98
133
  onClick={() => this.state.showFullScreenModal = true}
99
134
  >
100
- <div>Machines: {formatNumber(grouped.length)} |</div>
101
- <div>Threads: {formatNumber(totals.uniqueThreads)} |</div>
102
- <div>Files: {formatNumber(this.props.paths.length)} |</div>
103
- <div>Pending: {formatNumber(totals.totalPending)} |</div>
104
- <div>Size: {this.formatBytes(totals.totalSize)} |</div>
135
+ <Button hue={220}>Edit</Button>
136
+ <div>|</div>
137
+ <div>Machines: {formatNumber(grouped.length)}</div>
138
+ <div>|</div>
139
+ <div>Threads: {formatNumber(totals.uniqueThreads)}</div>
140
+ <div>|</div>
141
+ <div>Files: {formatNumber(this.props.paths.length)}</div>
142
+ <div>|</div>
143
+ <div>Pending: {formatNumber(totals.totalPending)}</div>
144
+ <div>|</div>
145
+ <div>Size: {this.formatBytes(totals.totalSize)}</div>
146
+ <div>|</div>
105
147
  <div>Log Count: {formatNumber(totals.totalLogCount)}</div>
148
+ {totals.errorCount > 0 && (
149
+ <>
150
+ <div>|</div>
151
+ <div className={css.colorhsl(0, 80, 40)}>Errors: {formatNumber(totals.errorCount)}</div>
152
+ </>
153
+ )}
154
+ {totals.minTime !== undefined && totals.maxTime !== undefined && (
155
+ <>
156
+ <div>|</div>
157
+ <div>{formatDateTime(totals.minTime)} - {formatDateTime(totals.maxTime)}</div>
158
+ </>
159
+ )}
106
160
  </div>
107
161
 
108
162
  {this.state.showFullScreenModal && (
@@ -119,6 +173,7 @@ export class FilePathSelector extends qreact.Component<{
119
173
  }}
120
174
  onCancel={() => this.state.showFullScreenModal = false}
121
175
  formatBytes={(bytes) => this.formatBytes(bytes)}
176
+ fileErrors={this.props.fileErrors}
122
177
  />
123
178
  </div>
124
179
  )}
@@ -134,6 +189,7 @@ export class FilePathSelectorModal extends qreact.Component<{
134
189
  onSave: (selectedPaths: TimeFilePathWithSize[]) => void;
135
190
  onCancel: () => void;
136
191
  formatBytes: (bytes: number) => string;
192
+ fileErrors?: { error: string; path: string; }[];
137
193
  }> {
138
194
  state = t.state({
139
195
  expandedMachines: t.lookup({
@@ -223,22 +279,50 @@ export class FilePathSelectorModal extends qreact.Component<{
223
279
  this.props.onSave(selectedPaths);
224
280
  }
225
281
 
282
+ expandAll() {
283
+ for (let machine of this.props.grouped) {
284
+ this.state.expandedMachines[machine.machineId] = { expanded: true };
285
+ for (let thread of machine.threads) {
286
+ let threadKey = `${machine.machineId}:${thread.threadId}`;
287
+ this.state.expandedThreads[threadKey] = { expanded: true };
288
+ }
289
+ }
290
+ }
291
+
292
+ collapseAll() {
293
+ for (let machineId in this.state.expandedMachines) {
294
+ delete this.state.expandedMachines[machineId];
295
+ }
296
+ for (let threadKey in this.state.expandedThreads) {
297
+ delete this.state.expandedThreads[threadKey];
298
+ }
299
+ }
300
+
226
301
  getSelectedSummary() {
227
302
  let selectedFiles = this.props.allPaths.filter(p => this.state.selectedPaths.has(p.fullPath));
303
+ let errorsByPath = keyBy(this.props.fileErrors || [], x => x.path);
228
304
  let totalSize = 0;
229
305
  let totalLogCount = 0;
306
+ let pendingCount = 0;
307
+ let errorCount = 0;
230
308
  let uniqueMachines = new Set<string>();
231
309
  let uniqueThreads = new Set<string>();
232
310
 
233
311
  for (let file of selectedFiles) {
234
312
  totalSize += file.size;
235
313
  totalLogCount += file.logCount || 0;
314
+ if (file.logCount === undefined) {
315
+ pendingCount++;
316
+ }
236
317
  if (file.machineId) {
237
318
  uniqueMachines.add(file.machineId);
238
319
  }
239
320
  if (file.threadId) {
240
321
  uniqueThreads.add(file.threadId);
241
322
  }
323
+ if (errorsByPath.get(file.fullPath)) {
324
+ errorCount++;
325
+ }
242
326
  }
243
327
 
244
328
  return {
@@ -246,14 +330,44 @@ export class FilePathSelectorModal extends qreact.Component<{
246
330
  machineCount: uniqueMachines.size,
247
331
  threadCount: uniqueThreads.size,
248
332
  totalSize,
249
- totalLogCount
333
+ totalLogCount,
334
+ pendingCount,
335
+ errorCount
250
336
  };
251
337
  }
252
338
 
253
- renderFile(file: TimeFilePathWithSize, machineId: string) {
339
+ getSelectedSummaryBySource(): Map<string, { fileCount: number; size: number; logCount: number; pendingCount: number; errorCount: number; }> {
340
+ let selectedFiles = this.props.allPaths.filter(p => this.state.selectedPaths.has(p.fullPath));
341
+ let errorsByPath = keyBy(this.props.fileErrors || [], x => x.path);
342
+ let bySource = new Map<string, { fileCount: number; size: number; logCount: number; pendingCount: number; errorCount: number; }>();
343
+ for (let file of selectedFiles) {
344
+ let sourceName = file.sourceName || "unknown";
345
+ let existing = bySource.get(sourceName);
346
+ if (!existing) {
347
+ existing = { fileCount: 0, size: 0, logCount: 0, pendingCount: 0, errorCount: 0 };
348
+ bySource.set(sourceName, existing);
349
+ }
350
+ existing.fileCount++;
351
+ existing.size += file.size;
352
+ existing.logCount += file.logCount || 0;
353
+ if (file.logCount === undefined) {
354
+ existing.pendingCount++;
355
+ }
356
+ if (errorsByPath.get(file.fullPath)) {
357
+ existing.errorCount++;
358
+ }
359
+ }
360
+ return bySource;
361
+ }
362
+
363
+ renderFile(file: TimeFilePathWithSize, machineId: string, errorsByPath: Map<string, { error: string; path: string; }>) {
254
364
  let isSelected = this.state.selectedPaths.has(file.fullPath);
365
+ let isPending = file.logCount === undefined;
366
+ const errorObj = errorsByPath.get(file.fullPath);
367
+ const error = errorObj?.error;
368
+ let rowClassName = error && css.hsl(0, 30, 95) || isPending && css.hsl(40, 30, 95) || undefined;
255
369
  return (
256
- <tr key={file.fullPath}>
370
+ <tr key={file.fullPath} className={rowClassName}>
257
371
  <td className={css.pad2(2)}>
258
372
  <Button
259
373
  onClick={() => this.toggleFile(file.fullPath)}
@@ -264,35 +378,74 @@ export class FilePathSelectorModal extends qreact.Component<{
264
378
  </Button>
265
379
  </td>
266
380
  <td className={css.pad2(2)}>{this.props.formatBytes(file.size)}</td>
267
- <td className={css.pad2(2)}>{file.logCount !== undefined ? formatNumber(file.logCount) : ""}</td>
381
+ <td className={css.pad2(2)}>{file.logCount !== undefined ? formatNumber(file.logCount) : <span className={css.colorhsl(40, 60, 40)}>pending</span>}</td>
382
+ <td className={css.pad2(2)}>{file.sourceName}</td>
268
383
  <td className={css.pad2(2)}>{file.dedupe}</td>
269
384
  <td className={css.pad2(2)}>{formatDateTime(file.startTime)}</td>
385
+ <td className={css.pad2(2)}>
386
+ {error && (
387
+ <div className={css.colorhsl(0, 80, 40).fontWeight(600)} title={error}>ERROR</div>
388
+ )}
389
+ </td>
270
390
  </tr>
271
391
  );
272
392
  }
273
393
 
274
394
  render() {
275
395
  let summary = this.getSelectedSummary();
396
+ let bySource = this.getSelectedSummaryBySource();
397
+ let errorsByPath = keyBy(this.props.fileErrors || [], x => x.path);
276
398
 
277
399
  return (
278
400
  <div
279
401
  className={css.vbox(10).pad2(10).hsl(0, 0, 100).overflowAuto.width("75vw").height("75vh").marginAuto}
280
402
  onClick={(e) => e.stopPropagation()}
281
403
  >
282
- <div className={css.hbox(10)}>
404
+ <div className={css.hbox(10).alignItems("start")}>
283
405
  <Button onClick={() => this.state.selectedPaths = new Set()}>
284
406
  Unselect All
285
407
  </Button>
286
408
  <Button onClick={() => this.state.selectedPaths = new Set(this.props.allPaths.map(p => p.fullPath))}>
287
409
  Select All
288
410
  </Button>
289
- <div>Machines: {formatNumber(summary.machineCount)}</div>
290
- <div>Threads: {formatNumber(summary.threadCount)}</div>
291
- <div>Files: {formatNumber(summary.fileCount)}</div>
292
- <div>Size: {this.props.formatBytes(summary.totalSize)}</div>
293
- <div>Logs: {formatNumber(summary.totalLogCount)}</div>
411
+ <Button onClick={() => this.expandAll()}>
412
+ Expand All
413
+ </Button>
414
+ <Button onClick={() => this.collapseAll()}>
415
+ Collapse All
416
+ </Button>
417
+
418
+ <div className={css.vbox(5)}>
419
+ <div className={css.hbox(10)}>
420
+ <div className={css.minWidth(120)}>Machines: {formatNumber(summary.machineCount)}</div>
421
+ <div className={css.minWidth(110)}>Threads: {formatNumber(summary.threadCount)}</div>
422
+ <div className={css.minWidth(140)}>Files: {formatNumber(summary.fileCount)} ({formatNumber(summary.pendingCount)} pending)</div>
423
+ <div className={css.minWidth(120)}>Size: {this.props.formatBytes(summary.totalSize)}</div>
424
+ <div className={css.minWidth(100)}>Logs: {formatNumber(summary.totalLogCount)}</div>
425
+ {summary.errorCount > 0 && (
426
+ <div className={css.minWidth(100) + css.colorhsl(0, 80, 40)}>Errors: {formatNumber(summary.errorCount)}</div>
427
+ )}
428
+ </div>
429
+
430
+ {bySource.size > 0 && (
431
+ <div className={css.vbox(5)}>
432
+ {Array.from(bySource.entries()).map(([sourceName, stats]) => (
433
+ <div key={sourceName} className={css.hbox(10)}>
434
+ <div className={css.minWidth(240)}>{sourceName}:</div>
435
+ <div className={css.minWidth(140)}>Files: {formatNumber(stats.fileCount)} ({formatNumber(stats.pendingCount)} pending)</div>
436
+ <div className={css.minWidth(120)}>Size: {this.props.formatBytes(stats.size)}</div>
437
+ <div className={css.minWidth(100)}>Logs: {formatNumber(stats.logCount)}</div>
438
+ {stats.errorCount > 0 && (
439
+ <div className={css.minWidth(100) + css.colorhsl(0, 80, 40)}>Errors: {formatNumber(stats.errorCount)}</div>
440
+ )}
441
+ </div>
442
+ ))}
443
+ </div>
444
+ )}
445
+ </div>
294
446
  </div>
295
447
 
448
+
296
449
  {this.props.grouped.map((machine) => {
297
450
  let isMachineExpanded = machine.machineId in this.state.expandedMachines;
298
451
  let allFiles = machine.threads.flatMap(t => t.files);
@@ -328,9 +481,12 @@ export class FilePathSelectorModal extends qreact.Component<{
328
481
  </Button>
329
482
  <MachineThreadInfo machineId={machine.machineId} />
330
483
  <div>Threads: {formatNumber(machine.threads.length)}</div>
331
- <div className={css.minWidth(80)}>Files: {formatNumber(totalFileCount)}</div>
484
+ <div className={css.minWidth(120)}>Files: {formatNumber(totalFileCount)} ({formatNumber(machine.pendingCount)} pending)</div>
332
485
  <div className={css.minWidth(100)}>Size: {this.props.formatBytes(machine.totalSize)}</div>
333
486
  <div className={css.minWidth(100)}>Logs: {formatNumber(machine.totalLogCount)}</div>
487
+ {machine.errorCount > 0 && (
488
+ <div className={css.minWidth(100) + css.colorhsl(0, 80, 40)}>Errors: {formatNumber(machine.errorCount)}</div>
489
+ )}
334
490
  </div>
335
491
 
336
492
  {isMachineExpanded && machine.threads.map((thread) => {
@@ -365,9 +521,12 @@ export class FilePathSelectorModal extends qreact.Component<{
365
521
  >
366
522
  {allThreadSelected ? "Selected" : someThreadSelected ? "Partial" : "Not Selected"}
367
523
  </Button>
368
- <div className={css.minWidth(80)}>Files: {formatNumber(thread.files.length)}</div>
524
+ <div className={css.minWidth(120)}>Files: {formatNumber(thread.files.length)} ({formatNumber(thread.pendingCount)} pending)</div>
369
525
  <div className={css.minWidth(100)}>Size: {this.props.formatBytes(thread.totalSize)}</div>
370
526
  <div className={css.minWidth(100)}>Logs: {formatNumber(thread.totalLogCount)}</div>
527
+ {thread.errorCount > 0 && (
528
+ <div className={css.minWidth(100) + css.colorhsl(0, 80, 40)}>Errors: {formatNumber(thread.errorCount)}</div>
529
+ )}
371
530
  <MachineThreadInfo machineId={machine.machineId} threadId={thread.threadId} onlyShowThread />
372
531
  </div>
373
532
 
@@ -378,12 +537,14 @@ export class FilePathSelectorModal extends qreact.Component<{
378
537
  <th className={css.pad2(2).textAlign("left")}>Selection</th>
379
538
  <th className={css.pad2(2).textAlign("left")}>Size</th>
380
539
  <th className={css.pad2(2).textAlign("left")}>Logs</th>
540
+ <th className={css.pad2(2).textAlign("left")}>Source</th>
381
541
  <th className={css.pad2(2).textAlign("left")}>Dedupe</th>
382
542
  <th className={css.pad2(2).textAlign("left")}>Start Time</th>
543
+ <th className={css.pad2(2).textAlign("left")}>Status</th>
383
544
  </tr>
384
545
  </thead>
385
546
  <tbody>
386
- {thread.files.map((file) => this.renderFile(file, machine.machineId))}
547
+ {thread.files.map((file) => this.renderFile(file, machine.machineId, errorsByPath))}
387
548
  </tbody>
388
549
  </table>
389
550
  )}