querysub 0.395.0 → 0.397.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.
@@ -18,13 +18,18 @@ import { MachineThreadInfo } from "../../MachineThreadInfo";
18
18
  import { formatDateTime, formatTime } from "socket-function/src/formatting/format";
19
19
  import { getPathStr } from "../../../path";
20
20
  import { niceStringify } from "../../../niceStringify";
21
- import { createLifeCycleSearch, LifecycleInstance, limitURL, additionalSearchURL } from "./lifeCycleSearch";
21
+ import { createLifeCycleSearch, LifecycleInstance } from "./lifeCycleSearch";
22
22
  import { getLifecycleMatchesForDatum } from "./lifeCycleMatching";
23
23
  import { managementPageURL } from "../../managementPages";
24
24
  import { startTimeParam, endTimeParam } from "../TimeRangeSelector";
25
25
  import { formatValue } from "../../../5-diagnostics/GenericFormat";
26
26
  import { StartEllipsis } from "../../../library-components/StartEllipsis";
27
+ import { LifeCycleEntryReadMode } from "./LifeCycleEntryReadMode";
28
+ import { LifeCycleEntryEditor } from "./LifeCycleEntryEditor";
29
+ import { LifeCycleRenderer, LifeCycleInstanceRenderer } from "./LifeCycleRenderer";
27
30
  export let lifecycleIdURL = new URLParam("lifecycleid", "");
31
+ export let limitURL = new URLParam("lifecyclelimit", 100);
32
+ export let additionalSearchURL = new URLParam("lifecyclesearch", "");
28
33
 
29
34
  export class LifeCyclePage extends qreact.Component {
30
35
  controller = LifeCyclesController(SocketFunction.browserNodeId());
@@ -39,13 +44,14 @@ export class LifeCyclePage extends qreact.Component {
39
44
  phase2Results: t.atomic<LogDatum[]>([]),
40
45
  phase2Stats: t.atomic<IndexedLogResults | undefined>(undefined),
41
46
  phase2Searching: t.boolean(false),
47
+ phase2HitLimit: t.boolean(false),
42
48
  lifecycleInstances: t.atomic<LifecycleInstance[]>([]),
43
49
  });
44
50
 
45
51
  search = createLifeCycleSearch(this);
46
52
 
47
53
  searchLifeCycle = (lifeCycleId: string) => {
48
- return this.search.searchLifeCycle(lifeCycleId);
54
+ return this.search.searchLifeCycle({ lifeCycleId, additionalSearch: additionalSearchURL.value.trim(), limit: limitURL.value });
49
55
  };
50
56
 
51
57
  componentDidMount() {
@@ -96,18 +102,29 @@ export class LifeCyclePage extends qreact.Component {
96
102
  placeholder="Additional search filter..."
97
103
  url={additionalSearchURL}
98
104
  fillWidth
105
+ onKeyDown={(e) => {
106
+ if (e.key === "Enter" && lifecycleIdURL.value) {
107
+ void this.searchLifeCycle(lifecycleIdURL.value);
108
+ }
109
+ }}
99
110
  />
100
111
 
101
112
  <div className={css.hbox(12).wrap}>
102
113
  <InputLabel
103
114
  placeholder="Add new life cycle"
104
115
  onChangeValue={(value) => {
105
- let title = value.trim();
106
- if (title) {
116
+ value = value.trim();
117
+ if (value) {
107
118
  let newLifeCycle: LifeCycle = {
108
119
  id: nextId(),
109
- title,
110
- entries: [],
120
+ title: value,
121
+ entries: [{
122
+ matchPattern: value,
123
+ groupByKeys: [],
124
+ isStart: true,
125
+ sourceType: "log",
126
+ variables: {},
127
+ }],
111
128
  };
112
129
  Querysub.onCommitFinished(async () => {
113
130
  await this.controller.setLifeCycle.promise(newLifeCycle);
@@ -129,13 +146,8 @@ export class LifeCyclePage extends qreact.Component {
129
146
  <Button
130
147
  hue={120}
131
148
  onClick={() => {
132
- this.state.searchingLifeCycleId = undefined;
133
- this.state.phase1Results = [];
134
- this.state.phase1InvalidResults = [];
135
- this.state.phase1Stats = undefined;
136
- this.state.phase2Results = [];
137
- this.state.phase2Stats = undefined;
138
- this.state.lifecycleInstances = [];
149
+ this.search.resetAllSearchState(undefined);
150
+ lifecycleIdURL.value = "";
139
151
  }}
140
152
  >
141
153
  Show All Life Cycles
@@ -225,9 +237,16 @@ export class LifeCyclePage extends qreact.Component {
225
237
  <RenderSearchStats
226
238
  stats={this.state.phase2Stats}
227
239
  searching={this.state.phase2Searching}
228
- limit={limitURL.value * (this.state.searchingLifeCycleId && lifeCycles?.find(lc => lc.id === this.state.searchingLifeCycleId)?.entries.length || 1)}
240
+ limit={0}
229
241
  />
230
242
  )}
243
+ {this.state.phase2HitLimit && (
244
+ <div className={css.pad2(12).bord2(30, 60, 70).hsl(30, 50, 95).vbox(4)}>
245
+ <div className={css.boldStyle.colorhsl(30, 80, 40)}>
246
+ ⚠ BUG: Phase 2 search hit the limit, increase the constant factor at the top of life cycle search.
247
+ </div>
248
+ </div>
249
+ )}
231
250
  </div>
232
251
  )}
233
252
 
@@ -253,694 +272,3 @@ export class LifeCyclePage extends qreact.Component {
253
272
  </div>;
254
273
  }
255
274
  }
256
-
257
- export class LifeCycleRenderer extends qreact.Component<{
258
- lifeCycle: LifeCycle;
259
- defaultEditMode?: boolean;
260
- onSearch?: (lifeCycleId: string) => void;
261
- }> {
262
- state = t.state({
263
- expanded: t.atomic<boolean>(false),
264
- });
265
-
266
- controller = LifeCyclesController(SocketFunction.browserNodeId());
267
-
268
- render() {
269
- let lifeCycle = this.props.lifeCycle;
270
- let isSelected = lifecycleIdURL.value === lifeCycle.id;
271
- let bgClass = isSelected && css.hsl(280, 50, 90) || css.hsl(200, 30, 95);
272
- let borderHue = isSelected && 280 || 200;
273
-
274
- return <div className={css.pad2(12).bord2(borderHue, 30, 70).vbox(8) + bgClass}>
275
- <div className={css.hbox(12)}>
276
- <Button
277
- hue={borderHue}
278
- onClick={() => {
279
- this.state.expanded = !this.state.expanded;
280
- }}
281
- >
282
- {this.state.expanded && "▼" || "▶"} {lifeCycle.title}
283
- </Button>
284
- <span>{lifeCycle.entries.length} entries</span>
285
- {this.props.onSearch && (
286
- <Button
287
- hue={280}
288
- onClick={() => {
289
- if (this.props.onSearch) {
290
- this.props.onSearch(lifeCycle.id);
291
- }
292
- }}
293
- >
294
- Search
295
- </Button>
296
- )}
297
- {isSelected && (
298
- <Button
299
- hue={200}
300
- onClick={() => {
301
- lifecycleIdURL.value = "";
302
- }}
303
- >
304
- Clear Selection
305
- </Button>
306
- ) || (
307
- <Button
308
- hue={280}
309
- onClick={() => {
310
- lifecycleIdURL.value = lifeCycle.id;
311
- }}
312
- >
313
- Pin
314
- </Button>
315
- )}
316
- <Button
317
- hue={0}
318
- onClick={() => {
319
- if (!confirm(`Delete life cycle "${lifeCycle.title}"?`)) return;
320
-
321
- Querysub.onCommitFinished(async () => {
322
- await this.controller.deleteLifeCycle.promise(lifeCycle.id);
323
- });
324
- }}
325
- >
326
- Delete
327
- </Button>
328
- </div>
329
-
330
- {this.state.expanded && (
331
- <div className={css.vbox(8)}>
332
- <InputLabel
333
- label="Title"
334
- value={lifeCycle.title}
335
- onChangeValue={(value) => {
336
- let title = value.trim();
337
- if (!title) return;
338
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
339
- updatedLifeCycle.title = title;
340
- Querysub.onCommitFinished(async () => {
341
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
342
- });
343
- }}
344
- className={css.width(500)}
345
- />
346
-
347
- <div className={css.vbox(4)}>
348
- {lifeCycle.entries.map((entry, idx) => (
349
- <LifeCycleEntryEditor
350
- key={entry.matchPattern + idx}
351
- lifeCycle={lifeCycle}
352
- entry={entry}
353
- entryIndex={idx}
354
- defaultEditMode={this.props.defaultEditMode}
355
- />
356
- ))}
357
-
358
- <InputLabel
359
- placeholder="Add new entry (match pattern)"
360
- onChangeValue={(value) => {
361
- let matchPattern = value.trim();
362
- if (matchPattern) {
363
- let defaultGroupByKeys = lifeCycle.entries.length > 0 && lifeCycle.entries[0].groupByKeys && deepCloneJSON(lifeCycle.entries[0].groupByKeys) || [];
364
- let newEntry: LifeCycleEntry = {
365
- matchPattern,
366
- sourceType: "info",
367
- groupByKeys: defaultGroupByKeys,
368
- variables: {},
369
- };
370
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
371
- let firstEndIndex = updatedLifeCycle.entries.findIndex(e => e.isEnd);
372
- if (firstEndIndex !== -1) {
373
- updatedLifeCycle.entries.splice(firstEndIndex, 0, newEntry);
374
- } else {
375
- updatedLifeCycle.entries.push(newEntry);
376
- }
377
- Querysub.onCommitFinished(async () => {
378
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
379
- });
380
- }
381
- }}
382
- className={css.width(500)}
383
- />
384
- </div>
385
- </div>
386
- )}
387
- </div>;
388
- }
389
- }
390
-
391
-
392
- class LifeCycleInstanceRenderer extends qreact.Component<{
393
- lifeCycle: LifeCycle;
394
- instance: LifecycleInstance;
395
- }> {
396
- state = t.state({
397
- expanded: t.atomic<boolean>(false),
398
- });
399
-
400
- render() {
401
- let { lifeCycle, instance } = this.props;
402
-
403
- let bgClass = instance.isWarning && css.hsl(30, 60, 90) ||
404
- instance.isTruncated && css.hsl(40, 50, 92) ||
405
- instance.isComplete && css.hsl(120, 30, 95) ||
406
- css.hsl(200, 30, 95);
407
-
408
- let borderHue = instance.isWarning && 30 || instance.isTruncated && 40 || instance.isComplete && 120 || 200;
409
-
410
- let firstDatum = instance.entries[0]?.datum;
411
-
412
- let statusTitle = instance.isWarning && `⚠ ${instance.warningMessage}` ||
413
- instance.isTruncated && "⚠ Truncated (ended by new start)" ||
414
- instance.isComplete && "✓ Complete" ||
415
- "Incomplete";
416
-
417
- return <div
418
- className={
419
- css.pad2(4).bord2(borderHue, 40, 70)
420
- .vbox(8)
421
- .button.userSelect("auto", "important")
422
- + " LifeCycleInstanceRenderer"
423
- + bgClass
424
- }
425
- title={statusTitle}
426
- onClick={(e) => {
427
- if ((e.target as HTMLElement).closest(".LifeCycleEntryEditor")) {
428
- return;
429
- }
430
- this.state.expanded = !this.state.expanded;
431
- }}
432
- >
433
- <div className={css.hbox(12, 4)}>
434
- <Button
435
- hue={borderHue}
436
- >
437
- {this.state.expanded && "▼" || "▶"}
438
- </Button>
439
- <span className={css.minWidth(200).hbox(12)}>
440
- <span className={css.colorhsl(220, 60, 50)}>
441
- {formatDateTime(instance.startTime)}
442
- </span>
443
- {instance.endTime !== undefined && (
444
- <span className={css.colorhsl(220, 60, 50)}>
445
- ({formatTime(instance.endTime - instance.startTime)})
446
- </span>
447
- )}
448
- </span>
449
- <span>{instance.entries.length} entries</span>
450
- {firstDatum && firstDatum.__machineId && (
451
- <MachineThreadInfo
452
- machineId={firstDatum.__machineId}
453
- threadId={firstDatum.__threadId}
454
- />
455
- )}
456
- <div className={css.hbox(8, 4).wrap.flexFillWidth}>
457
- {instance.keys.filter(k => !["__threadId", "__machineId"].includes(k.key)).map((kv, idx, list) => (
458
- <div key={idx} className={css.hbox(4)}>
459
- <span className={css.colorhsl(200, 50, 60).boldStyle}>{kv.key}</span>
460
- <span className={css.colorhsl(0, 0, 50)} title={String(kv.value)}>
461
- <StartEllipsis maxWidth={Math.ceil(600 / list.length)}>
462
- {niceStringify(kv.value)}
463
- </StartEllipsis>
464
- </span>
465
- </div>
466
- ))}
467
- {(() => {
468
- let uniqueVars = new Map<string, { key: string; title: string | undefined; value: unknown }>();
469
- for (let entryData of instance.entries) {
470
- let entry = lifeCycle.entries.find(e => e.matchPattern === entryData.matchPattern);
471
- if (!entry) continue;
472
- for (let [key, config] of Object.entries(entry.variables)) {
473
- let value = entryData.datum[key];
474
- if (value === undefined) continue;
475
- let uniqueId = getPathStr([key, JSON.stringify(value)]);
476
- if (!uniqueVars.has(uniqueId)) {
477
- uniqueVars.set(uniqueId, {
478
- key: key,
479
- title: config?.title || undefined,
480
- value: value,
481
- });
482
- }
483
- }
484
- }
485
- return Array.from(uniqueVars.values()).map((varData, idx) => (
486
- <div key={idx} className={css.hbox(4)}>
487
- <span className={css.colorhsl(0, 0, 0).boldStyle}>{varData.title || varData.key}</span>
488
- <span className={css.colorhsl(0, 0, 50)} title={niceStringify(varData.value)}>{formatValue(varData.value)}</span>
489
- </div>
490
- ));
491
- })()}
492
- </div>
493
- </div>
494
-
495
- {this.state.expanded && (
496
- <div className={css.vbox(8)}>
497
- {instance.entries.map((entryData, idx) => {
498
- let entryIndex = lifeCycle.entries.findIndex(e => e.matchPattern === entryData.matchPattern);
499
- let entry = lifeCycle.entries[entryIndex];
500
- return <LifeCycleEntryEditor
501
- key={idx}
502
- lifeCycle={lifeCycle}
503
- entry={entry}
504
- entryIndex={entryIndex}
505
- defaultEditMode={false}
506
- datum={entryData.datum}
507
- />;
508
- })}
509
- </div>
510
- )}
511
- </div>;
512
- }
513
- }
514
-
515
-
516
- class LifeCycleEntryEditor extends qreact.Component<{
517
- lifeCycle: LifeCycle;
518
- entry: LifeCycleEntry;
519
- entryIndex: number;
520
- defaultEditMode?: boolean;
521
- datum?: LogDatum;
522
- }> {
523
- state = t.state({
524
- editMode: t.atomic<boolean>(false),
525
- });
526
-
527
- controller = LifeCyclesController(SocketFunction.browserNodeId());
528
-
529
- componentDidMount() {
530
- this.state.editMode = this.props.defaultEditMode || false;
531
- }
532
-
533
- render() {
534
- let { lifeCycle, entry, entryIndex } = this.props;
535
- let isConfigured = entry.groupByKeys && entry.groupByKeys.length > 0 && entry.groupByKeys.every(k => k.ourKey.trim() !== "");
536
- let bgClass = isConfigured && css.hsl(150, 30, 95) || css.hsl(30, 50, 90);
537
-
538
- return <div className={
539
- css.pad2(4).bord2(150, 30, 70)
540
- .vbox(4)
541
- .cursor("auto")
542
- + bgClass
543
- + " LifeCycleEntryEditor"
544
- }>
545
- <div className={css.hbox(12)}>
546
- <span className={css.boldStyle}>{entry.description || entry.matchPattern}</span>
547
- {entry.description && <span className={css.colorhsl(0, 0, 50)}>({entry.matchPattern})</span>}
548
- <span className={css.colorhsl(220, 70, 50)}>[{entry.sourceType}]</span>
549
- {entry.isStart && <span className={css.colorhsl(120, 80, 30).boldStyle}>START</span>}
550
- {entry.isEnd && <span className={css.colorhsl(0, 80, 30).boldStyle}>END</span>}
551
- {!isConfigured && !this.state.editMode && <span className={css.colorhsl(30, 80, 40).boldStyle}>(not fully configured)</span>}
552
- <Button
553
- hue={150}
554
- onClick={() => {
555
- this.state.editMode = !this.state.editMode;
556
- }}
557
- >
558
- {this.state.editMode && "Read Mode" || "Edit Mode"}
559
- </Button>
560
- <Button
561
- hue={0}
562
- onClick={() => {
563
- if (!confirm("Delete this entry?")) return;
564
-
565
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
566
- updatedLifeCycle.entries.splice(entryIndex, 1);
567
- Querysub.onCommitFinished(async () => {
568
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
569
- });
570
- }}
571
- >
572
- Delete
573
- </Button>
574
- </div>
575
-
576
- {this.state.editMode && (
577
- <div className={css.vbox(12)}>
578
- <div className={css.hbox(10)}>
579
- <InputLabel
580
- label="Match Pattern"
581
- value={entry.matchPattern}
582
- onChangeValue={(value) => {
583
- let matchPattern = value.trim();
584
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
585
- updatedLifeCycle.entries[entryIndex].matchPattern = matchPattern;
586
- Querysub.onCommitFinished(async () => {
587
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
588
- });
589
- }}
590
- className={css.width(500)}
591
- />
592
- <InputLabel
593
- label="Description"
594
- value={entry.description || ""}
595
- onChangeValue={(value) => {
596
- let description = value.trim() || undefined;
597
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
598
- updatedLifeCycle.entries[entryIndex].description = description;
599
- Querysub.onCommitFinished(async () => {
600
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
601
- });
602
- }}
603
- className={css.width(500)}
604
- />
605
- </div>
606
-
607
- <div className={css.hbox(12)}>
608
- <div className={css.vbox(4)}>
609
- <span>Source Type</span>
610
- <select
611
- value={entry.sourceType}
612
- onChange={(e) => {
613
- let sourceType = e.currentTarget.value as "log" | "error" | "info" | "warning";
614
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
615
- updatedLifeCycle.entries[entryIndex].sourceType = sourceType;
616
- Querysub.onCommitFinished(async () => {
617
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
618
- });
619
- }}
620
- className={css.pad2(4, 2)}
621
- >
622
- <option value="log">log</option>
623
- <option value="info">info</option>
624
- <option value="warning">warning</option>
625
- <option value="error">error</option>
626
- </select>
627
- </div>
628
- </div>
629
-
630
- <div className={css.hbox(12)}>
631
- <InputLabel
632
- label="Is Start"
633
- checkbox
634
- checked={entry.isStart}
635
- onChange={(e) => {
636
- let isStart = e.currentTarget.checked || undefined;
637
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
638
- updatedLifeCycle.entries[entryIndex].isStart = isStart;
639
- Querysub.onCommitFinished(async () => {
640
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
641
- });
642
- }}
643
- />
644
- <InputLabel
645
- label="Is End"
646
- checkbox
647
- checked={entry.isEnd}
648
- onChange={(e) => {
649
- let isEnd = e.currentTarget.checked || undefined;
650
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
651
- updatedLifeCycle.entries[entryIndex].isEnd = isEnd;
652
- Querysub.onCommitFinished(async () => {
653
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
654
- });
655
- }}
656
- />
657
- </div>
658
-
659
- <div className={css.vbox(8)}>
660
- <div className={!isConfigured && css.colorhsl(30, 80, 40).boldStyle || ""}>
661
- Group By Keys{!isConfigured && " (at least one required)" || ""}
662
- </div>
663
- {Array.isArray(entry.groupByKeys) && entry.groupByKeys.map((keyConfig, keyIdx) => {
664
- let keyExistsInAllOtherEntries = lifeCycle.entries.every((e, idx) => {
665
- if (idx === entryIndex) return true;
666
- return e.groupByKeys && e.groupByKeys.some(k => k.ourKey === keyConfig.ourKey);
667
- });
668
-
669
- return <div key={keyIdx} className={css.hbox(8).pad2(4).bord2(200, 30, 80).hsl(200, 30, 97)}>
670
- <Button
671
- hue={160}
672
- disabled={keyExistsInAllOtherEntries}
673
- onClick={() => {
674
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
675
- updatedLifeCycle.entries.forEach((e, idx) => {
676
- if (idx === entryIndex) return;
677
- if (!e.groupByKeys.some(k => k.ourKey === keyConfig.ourKey)) {
678
- e.groupByKeys.push({ ourKey: keyConfig.ourKey });
679
- }
680
- });
681
- Querysub.onCommitFinished(async () => {
682
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
683
- });
684
- }}
685
- >
686
- Copy to All
687
- </Button>
688
- <InputLabel
689
- label="Key"
690
- value={keyConfig.ourKey}
691
- onChangeValue={(value) => {
692
- let ourKey = value.trim();
693
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
694
- updatedLifeCycle.entries[entryIndex].groupByKeys[keyIdx].ourKey = ourKey;
695
- Querysub.onCommitFinished(async () => {
696
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
697
- });
698
- }}
699
- className={css.width(250)}
700
- />
701
- <InputLabel
702
- label="Different key in start entry (not usually needed)"
703
- value={keyConfig.startKey || ""}
704
- onChangeValue={(value) => {
705
- let startKey = value.trim() || undefined;
706
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
707
- updatedLifeCycle.entries[entryIndex].groupByKeys[keyIdx].startKey = startKey;
708
- Querysub.onCommitFinished(async () => {
709
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
710
- });
711
- }}
712
- className={css.width(250)}
713
- />
714
- <Button
715
- hue={0}
716
- onClick={() => {
717
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
718
- updatedLifeCycle.entries[entryIndex].groupByKeys.splice(keyIdx, 1);
719
- Querysub.onCommitFinished(async () => {
720
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
721
- });
722
- }}
723
- >
724
- Delete
725
- </Button>
726
- </div>;
727
- })}
728
- <InputLabel
729
- label="Add new key"
730
- onChangeValue={(value) => {
731
- let ourKey = value.trim();
732
- if (ourKey) {
733
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
734
- updatedLifeCycle.entries[entryIndex].groupByKeys.push({ ourKey });
735
- Querysub.onCommitFinished(async () => {
736
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
737
- });
738
- }
739
- }}
740
- className={css.width(250)}
741
- />
742
- </div>
743
-
744
- <div className={css.vbox(4)}>
745
- {Object.entries(entry.variables).map(([key, varData]) => {
746
- let keyExistsInAllOtherEntries = lifeCycle.entries.every((e, idx) => {
747
- if (idx === entryIndex) return true;
748
- return key in e.variables;
749
- });
750
-
751
- return <div key={key} className={css.hbox(8).pad2(4).bord2(0, 0, 80).hsl(0, 0, 97)}>
752
- <Button
753
- hue={160}
754
- disabled={keyExistsInAllOtherEntries}
755
- onClick={() => {
756
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
757
- updatedLifeCycle.entries.forEach((e, idx) => {
758
- if (idx === entryIndex) return;
759
- if (!(key in e.variables)) {
760
- e.variables[key] = {};
761
- }
762
- });
763
- Querysub.onCommitFinished(async () => {
764
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
765
- });
766
- }}
767
- >
768
- Copy to All
769
- </Button>
770
- <span className={css.minWidth(150).boldStyle}>{key}</span>
771
- <InputLabel
772
- placeholder="Variable title"
773
- value={varData.title || ""}
774
- onChangeValue={(value) => {
775
- let title = value.trim() || undefined;
776
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
777
- updatedLifeCycle.entries[entryIndex].variables[key].title = title;
778
- Querysub.onCommitFinished(async () => {
779
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
780
- });
781
- }}
782
- className={css.width(300)}
783
- />
784
- <Button
785
- hue={0}
786
- onClick={() => {
787
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
788
- delete updatedLifeCycle.entries[entryIndex].variables[key];
789
- Querysub.onCommitFinished(async () => {
790
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
791
- });
792
- }}
793
- >
794
- Delete
795
- </Button>
796
- </div>;
797
- })}
798
-
799
- <InputLabel
800
- label="Add new variable key"
801
- onChangeValue={(value) => {
802
- let key = value.trim();
803
- if (key) {
804
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
805
- updatedLifeCycle.entries[entryIndex].variables[key] = {};
806
- Querysub.onCommitFinished(async () => {
807
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
808
- });
809
- }
810
- }}
811
- className={css.width(300)}
812
- />
813
- </div>
814
- </div>
815
- )}
816
-
817
- {!this.state.editMode && <LifeCycleEntryReadMode
818
- lifeCycle={lifeCycle}
819
- entry={entry}
820
- entryIndex={entryIndex}
821
- datum={this.props.datum}
822
- />}
823
- </div>;
824
- }
825
- }
826
-
827
- class LifeCycleEntryReadMode extends qreact.Component<{
828
- lifeCycle: LifeCycle;
829
- entry: LifeCycleEntry;
830
- entryIndex: number;
831
- datum: LogDatum | undefined;
832
- }> {
833
- state = t.state({
834
- showAllVariables: t.atomic<boolean>(false),
835
- });
836
-
837
- controller = LifeCyclesController(SocketFunction.browserNodeId());
838
-
839
- render() {
840
- let { lifeCycle, entry, entryIndex } = this.props;
841
- const datum = this.props.datum;
842
-
843
- let variables = getVariables(entry);
844
- let renderEditUI = (key: string) => {
845
- let updateLifeCycle = (fnc: (updatedLifeCycle: LifeCycle) => void) => {
846
- let updatedLifeCycle = deepCloneJSON(lifeCycle);
847
- fnc(updatedLifeCycle);
848
- Querysub.onCommitFinished(async () => {
849
- await this.controller.setLifeCycle.promise(updatedLifeCycle);
850
- });
851
- };
852
-
853
- let isVariable = key in entry.variables;
854
- let isGroupByKey = Array.isArray(entry.groupByKeys) && entry.groupByKeys.some(k => k.ourKey === key);
855
-
856
- return <>
857
- <span
858
- className={css.button.pad2(4).bord2(0, 0, 60).hsl(220, 50, 60) + (!isGroupByKey && css.opacity(0.4))}
859
- onClick={() => {
860
- updateLifeCycle((updated) => {
861
- if (isGroupByKey) {
862
- updated.entries.forEach((e) => {
863
- e.groupByKeys = e.groupByKeys.filter(k => k.ourKey !== key);
864
- });
865
- } else {
866
- updated.entries.forEach((e) => {
867
- if (!e.groupByKeys.some(k => k.ourKey === key)) {
868
- e.groupByKeys.push({ ourKey: key });
869
- }
870
- });
871
- }
872
- });
873
- }}
874
- title={isGroupByKey && "Remove group by key from all entries" || "Add group by key to all entries"}
875
- >
876
- 🔑
877
- </span>
878
- <span
879
- className={css.button.pad2(4).bord2(0, 0, 60).hsl(120, 50, 60) + (!isVariable && css.opacity(0.4))}
880
- onClick={() => {
881
- updateLifeCycle((updated) => {
882
- if (isVariable) {
883
- updated.entries.forEach((e) => {
884
- delete e.variables[key];
885
- });
886
- } else {
887
- updated.entries.forEach((e) => {
888
- if (!(key in e.variables)) {
889
- e.variables[key] = {};
890
- }
891
- });
892
- }
893
- });
894
- }}
895
- title={isVariable && "Remove variable from all entries" || "Add variable to all entries"}
896
- >
897
- 📌
898
- </span>
899
- </>;
900
- };
901
-
902
- return <div className={css.vbox(8)}>
903
- {Array.isArray(entry.groupByKeys) && entry.groupByKeys.some(k => k.startKey) && (
904
- <div className={css.vbox(2)}>
905
- {entry.groupByKeys.filter(k => k.startKey).map((k, idx) => (
906
- <div key={idx}>{k.ourKey} start override: {k.startKey}</div>
907
- ))}
908
- </div>
909
- )}
910
- {datum && <div className={css.vbox(4)}>
911
- {variables.map(({ key, title }) => {
912
- let value = datum[key];
913
- return <div key={key} className={css.hbox(8)}>
914
- {renderEditUI(key)}
915
- <span className={css.minWidth(150).colorhsl(0, 0, 50)}>
916
- {title || key}{title && ` (${key})` || ""}:
917
- </span>
918
- <span className={css.whiteSpace("pre-wrap")}>{value !== undefined && niceStringify(value) || "(not found)"}</span>
919
- </div>;
920
- })}
921
- {this.state.showAllVariables && (() => {
922
- let configuredKeys = new Set(getVariables(entry).map(v => v.key));
923
- let allKeys = Object.keys(datum).filter(key => !configuredKeys.has(key));
924
- return allKeys.map((key) => {
925
- let value = datum[key];
926
- return <div key={key} className={css.hbox(8)}>
927
- {renderEditUI(key)}
928
- <span className={css.minWidth(150).colorhsl(0, 0, 50)}>
929
- {key}
930
- </span>
931
- <span className={css.whiteSpace("pre-wrap")}>{value !== undefined && niceStringify(value) || "(not found)"}</span>
932
- </div>;
933
- });
934
- })()}
935
- <Button
936
- hue={220}
937
- onClick={() => {
938
- this.state.showAllVariables = !this.state.showAllVariables;
939
- }}
940
- >
941
- {this.state.showAllVariables && "Hide Additional Variables" || "Show All Variables"}
942
- </Button>
943
- </div>}
944
- </div>;
945
- }
946
- }