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.
- package/package.json +1 -1
- package/src/-a-archives/archivesDisk.ts +2 -2
- package/src/-c-identity/IdentityController.ts +19 -1
- package/src/0-path-value-core/pathValueCore.ts +8 -8
- package/src/3-path-functions/pathFunctionLoader.ts +2 -2
- package/src/deployManager/machineApplyMainCode.ts +1 -1
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +3 -4
- package/src/diagnostics/logs/IndexedLogs/bufferSearchFindMatcher.ts +1 -0
- package/src/diagnostics/logs/errorNotifications2/ErrorNotificationPage.tsx +3 -1
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryEditor.tsx +358 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryReadMode.tsx +149 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePage.tsx +33 -705
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleRenderer.tsx +291 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/NestedLifeCycleInfo.tsx +151 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleSearch.tsx +61 -34
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +3 -0
- package/src/diagnostics/misc-pages/ArchiveViewer.tsx +4 -3
- package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +6 -3
- package/src/library-components/StartEllipsis.tsx +1 -1
- package/tempnotes.txt +0 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
+
import { formatDateTime, formatTime } from "socket-function/src/formatting/format";
|
|
3
|
+
import { deepCloneJSON } from "socket-function/src/misc";
|
|
4
|
+
import { css } from "typesafecss";
|
|
5
|
+
import { t } from "../../../2-proxy/schema2";
|
|
6
|
+
import { qreact } from "../../../4-dom/qreact";
|
|
7
|
+
import { Querysub } from "../../../4-querysub/QuerysubController";
|
|
8
|
+
import { formatValue } from "../../../5-diagnostics/GenericFormat";
|
|
9
|
+
import { Button } from "../../../library-components/Button";
|
|
10
|
+
import { InputLabel } from "../../../library-components/InputLabel";
|
|
11
|
+
import { StartEllipsis } from "../../../library-components/StartEllipsis";
|
|
12
|
+
import { niceStringify } from "../../../niceStringify";
|
|
13
|
+
import { getPathStr } from "../../../path";
|
|
14
|
+
import { MachineThreadInfo } from "../../MachineThreadInfo";
|
|
15
|
+
import { LifeCycleEntryEditor } from "./LifeCycleEntryEditor";
|
|
16
|
+
import { lifecycleIdURL } from "./LifeCyclePage";
|
|
17
|
+
import { LifecycleInstance } from "./lifeCycleSearch";
|
|
18
|
+
import { LifeCycle, LifeCyclesController, LifeCycleEntry } from "./lifeCycles";
|
|
19
|
+
|
|
20
|
+
export class LifeCycleRenderer extends qreact.Component<{
|
|
21
|
+
lifeCycle: LifeCycle;
|
|
22
|
+
defaultEditMode?: boolean;
|
|
23
|
+
onSearch?: (lifeCycleId: string) => void;
|
|
24
|
+
}> {
|
|
25
|
+
state = t.state({
|
|
26
|
+
expanded: t.atomic<boolean>(false),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
controller = LifeCyclesController(SocketFunction.browserNodeId());
|
|
30
|
+
|
|
31
|
+
render() {
|
|
32
|
+
let lifeCycle = this.props.lifeCycle;
|
|
33
|
+
let isSelected = lifecycleIdURL.value === lifeCycle.id;
|
|
34
|
+
let bgClass = isSelected && css.hsl(280, 50, 90) || css.hsl(200, 30, 95);
|
|
35
|
+
let borderHue = isSelected && 280 || 200;
|
|
36
|
+
|
|
37
|
+
return <div className={css.pad2(12).bord2(borderHue, 30, 70).vbox(8) + bgClass}>
|
|
38
|
+
<div className={css.hbox(12)}>
|
|
39
|
+
<Button
|
|
40
|
+
hue={borderHue}
|
|
41
|
+
onClick={() => {
|
|
42
|
+
this.state.expanded = !this.state.expanded;
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
{this.state.expanded && "▼" || "▶"} {lifeCycle.title}
|
|
46
|
+
</Button>
|
|
47
|
+
<span>{lifeCycle.entries.length} entries</span>
|
|
48
|
+
{this.props.onSearch && (
|
|
49
|
+
<Button
|
|
50
|
+
hue={280}
|
|
51
|
+
onClick={() => {
|
|
52
|
+
if (this.props.onSearch) {
|
|
53
|
+
this.props.onSearch(lifeCycle.id);
|
|
54
|
+
}
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
Search
|
|
58
|
+
</Button>
|
|
59
|
+
)}
|
|
60
|
+
{isSelected && (
|
|
61
|
+
<Button
|
|
62
|
+
hue={200}
|
|
63
|
+
onClick={() => {
|
|
64
|
+
lifecycleIdURL.value = "";
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
Clear Selection
|
|
68
|
+
</Button>
|
|
69
|
+
) || (
|
|
70
|
+
<Button
|
|
71
|
+
hue={280}
|
|
72
|
+
onClick={() => {
|
|
73
|
+
lifecycleIdURL.value = lifeCycle.id;
|
|
74
|
+
void this.props.onSearch?.(lifeCycle.id);
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
Pin
|
|
78
|
+
</Button>
|
|
79
|
+
)}
|
|
80
|
+
<Button
|
|
81
|
+
hue={0}
|
|
82
|
+
onClick={() => {
|
|
83
|
+
if (!confirm(`Delete life cycle "${lifeCycle.title}"?`)) return;
|
|
84
|
+
|
|
85
|
+
Querysub.onCommitFinished(async () => {
|
|
86
|
+
await this.controller.deleteLifeCycle.promise(lifeCycle.id);
|
|
87
|
+
});
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
Delete
|
|
91
|
+
</Button>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{this.state.expanded && (
|
|
95
|
+
<div className={css.vbox(8)}>
|
|
96
|
+
<InputLabel
|
|
97
|
+
label="Title"
|
|
98
|
+
value={lifeCycle.title}
|
|
99
|
+
onChangeValue={(value) => {
|
|
100
|
+
let title = value.trim();
|
|
101
|
+
if (!title) return;
|
|
102
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
103
|
+
updatedLifeCycle.title = title;
|
|
104
|
+
Querysub.onCommitFinished(async () => {
|
|
105
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
106
|
+
});
|
|
107
|
+
}}
|
|
108
|
+
className={css.width(500)}
|
|
109
|
+
/>
|
|
110
|
+
|
|
111
|
+
<div className={css.vbox(4)}>
|
|
112
|
+
{lifeCycle.entries.map((entry, idx) => (
|
|
113
|
+
<LifeCycleEntryEditor
|
|
114
|
+
key={entry.matchPattern + idx}
|
|
115
|
+
lifeCycle={lifeCycle}
|
|
116
|
+
entry={entry}
|
|
117
|
+
entryIndex={idx}
|
|
118
|
+
defaultEditMode={this.props.defaultEditMode}
|
|
119
|
+
/>
|
|
120
|
+
))}
|
|
121
|
+
|
|
122
|
+
<InputLabel
|
|
123
|
+
placeholder="Add new entry (match pattern)"
|
|
124
|
+
onChangeValue={(value) => {
|
|
125
|
+
let matchPattern = value.trim();
|
|
126
|
+
if (matchPattern) {
|
|
127
|
+
let defaultGroupByKeys = lifeCycle.entries.length > 0 && lifeCycle.entries[0].groupByKeys && deepCloneJSON(lifeCycle.entries[0].groupByKeys) || [];
|
|
128
|
+
let newEntry: LifeCycleEntry = {
|
|
129
|
+
matchPattern,
|
|
130
|
+
sourceType: "info",
|
|
131
|
+
groupByKeys: defaultGroupByKeys,
|
|
132
|
+
variables: {},
|
|
133
|
+
};
|
|
134
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
135
|
+
let firstEndIndex = updatedLifeCycle.entries.findIndex(e => e.isEnd);
|
|
136
|
+
if (firstEndIndex !== -1) {
|
|
137
|
+
updatedLifeCycle.entries.splice(firstEndIndex, 0, newEntry);
|
|
138
|
+
} else {
|
|
139
|
+
updatedLifeCycle.entries.push(newEntry);
|
|
140
|
+
}
|
|
141
|
+
Querysub.onCommitFinished(async () => {
|
|
142
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}}
|
|
146
|
+
className={css.width(500)}
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
</div>;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
export class LifeCycleInstanceRenderer extends qreact.Component<{
|
|
157
|
+
lifeCycle: LifeCycle;
|
|
158
|
+
instance: LifecycleInstance;
|
|
159
|
+
}> {
|
|
160
|
+
state = t.state({
|
|
161
|
+
expanded: t.atomic<boolean>(false),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
render() {
|
|
165
|
+
let { lifeCycle, instance } = this.props;
|
|
166
|
+
|
|
167
|
+
let bgClass = (
|
|
168
|
+
instance.isMissingStart && css.hsl(60, 60, 90) ||
|
|
169
|
+
instance.isMissingEnd && css.hsl(200, 60, 90) ||
|
|
170
|
+
instance.isComplete && css.hsl(120, 30, 95) ||
|
|
171
|
+
instance.isTruncated && css.hsl(0, 60, 90) ||
|
|
172
|
+
css.hsl(280, 30, 95)
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
let borderHue = (
|
|
176
|
+
instance.isMissingStart && 60
|
|
177
|
+
|| instance.isMissingEnd && 200
|
|
178
|
+
|| instance.isComplete && 120
|
|
179
|
+
|| instance.isTruncated && 1
|
|
180
|
+
|| 280
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
let firstDatum = instance.entries[0]?.datum;
|
|
184
|
+
|
|
185
|
+
let statusTitle = (
|
|
186
|
+
instance.isMissingStart && "⚠ Missing start entry" ||
|
|
187
|
+
instance.isMissingEnd && "⚠ Missing end entry" ||
|
|
188
|
+
instance.isComplete && "✓ Complete" ||
|
|
189
|
+
instance.isTruncated && "⚠ Truncated (ended by new start)" ||
|
|
190
|
+
"Incomplete"
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
return <div
|
|
194
|
+
className={
|
|
195
|
+
css.pad2(4).bord2(borderHue, 40, 70)
|
|
196
|
+
.vbox(8)
|
|
197
|
+
.button.userSelect("auto", "important")
|
|
198
|
+
+ " LifeCycleInstanceRenderer"
|
|
199
|
+
+ bgClass
|
|
200
|
+
}
|
|
201
|
+
title={statusTitle}
|
|
202
|
+
onClick={(e) => {
|
|
203
|
+
if ((e.target as HTMLElement).closest(".LifeCycleEntryEditor")) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
this.state.expanded = !this.state.expanded;
|
|
207
|
+
}}
|
|
208
|
+
>
|
|
209
|
+
<div className={css.hbox(12, 4)}>
|
|
210
|
+
<Button
|
|
211
|
+
hue={borderHue}
|
|
212
|
+
>
|
|
213
|
+
{this.state.expanded && "▼" || "▶"}
|
|
214
|
+
</Button>
|
|
215
|
+
<span className={css.minWidth(200).hbox(12)}>
|
|
216
|
+
<span className={css.colorhsl(220, 60, 50)}>
|
|
217
|
+
{formatDateTime(instance.startTime)}
|
|
218
|
+
</span>
|
|
219
|
+
{instance.endTime !== undefined && (
|
|
220
|
+
<span className={css.colorhsl(220, 60, 50)}>
|
|
221
|
+
({formatTime(instance.endTime - instance.startTime)})
|
|
222
|
+
</span>
|
|
223
|
+
)}
|
|
224
|
+
</span>
|
|
225
|
+
<span>{instance.entries.length} entries</span>
|
|
226
|
+
{firstDatum && firstDatum.__machineId && (
|
|
227
|
+
<MachineThreadInfo
|
|
228
|
+
machineId={firstDatum.__machineId}
|
|
229
|
+
threadId={firstDatum.__threadId}
|
|
230
|
+
/>
|
|
231
|
+
)}
|
|
232
|
+
<div className={css.hbox(8, 4).wrap.flexFillWidth}>
|
|
233
|
+
{instance.keys.filter(k => !["__threadId", "__machineId"].includes(k.key)).map((kv, idx, list) => (
|
|
234
|
+
<div key={idx} className={css.hbox(4)}>
|
|
235
|
+
<span className={css.colorhsl(200, 50, 60).boldStyle}>{kv.key}</span>
|
|
236
|
+
<span className={css.colorhsl(0, 0, 50)} title={String(kv.value)}>
|
|
237
|
+
<StartEllipsis maxWidth={Math.ceil(600 / list.length)}>
|
|
238
|
+
{niceStringify(kv.value)}
|
|
239
|
+
</StartEllipsis>
|
|
240
|
+
</span>
|
|
241
|
+
</div>
|
|
242
|
+
))}
|
|
243
|
+
{(() => {
|
|
244
|
+
let uniqueVars = new Map<string, { key: string; title: string | undefined; value: unknown }>();
|
|
245
|
+
for (let entryData of instance.entries) {
|
|
246
|
+
let entry = lifeCycle.entries.find(e => e.matchPattern === entryData.matchPattern);
|
|
247
|
+
if (!entry) continue;
|
|
248
|
+
for (let [key, config] of Object.entries(entry.variables)) {
|
|
249
|
+
let value = entryData.datum[key];
|
|
250
|
+
if (value === undefined) continue;
|
|
251
|
+
// Now just the key, so the preview is easier to read
|
|
252
|
+
let uniqueId = key;
|
|
253
|
+
if (!uniqueVars.has(uniqueId)) {
|
|
254
|
+
uniqueVars.set(uniqueId, {
|
|
255
|
+
key: key,
|
|
256
|
+
title: config?.title || undefined,
|
|
257
|
+
value: value,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return Array.from(uniqueVars.values()).map((varData, idx) => (
|
|
263
|
+
<div key={idx} className={css.hbox(4)}>
|
|
264
|
+
<span className={css.colorhsl(0, 0, 0).boldStyle}>{varData.title || varData.key}</span>
|
|
265
|
+
<span className={css.colorhsl(0, 0, 50)} title={niceStringify(varData.value)}>{formatValue(varData.value)}</span>
|
|
266
|
+
</div>
|
|
267
|
+
));
|
|
268
|
+
})()}
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
{this.state.expanded && (
|
|
273
|
+
<div className={css.vbox(8)}>
|
|
274
|
+
{instance.entries.map((entryData, idx) => {
|
|
275
|
+
let entryIndex = lifeCycle.entries.findIndex(e => e.matchPattern === entryData.matchPattern);
|
|
276
|
+
let entry = lifeCycle.entries[entryIndex];
|
|
277
|
+
return <LifeCycleEntryEditor
|
|
278
|
+
key={idx}
|
|
279
|
+
lifeCycle={lifeCycle}
|
|
280
|
+
entry={entry}
|
|
281
|
+
entryIndex={entryIndex}
|
|
282
|
+
defaultEditMode={false}
|
|
283
|
+
datum={entryData.datum}
|
|
284
|
+
/>;
|
|
285
|
+
})}
|
|
286
|
+
</div>
|
|
287
|
+
)}
|
|
288
|
+
</div>;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
+
import { css } from "typesafecss";
|
|
3
|
+
import { t } from "../../../2-proxy/schema2";
|
|
4
|
+
import { qreact } from "../../../4-dom/qreact";
|
|
5
|
+
import { formatValue } from "../../../5-diagnostics/GenericFormat";
|
|
6
|
+
import { Button } from "../../../library-components/Button";
|
|
7
|
+
import { niceStringify } from "../../../niceStringify";
|
|
8
|
+
import { LogDatum } from "../diskLogger";
|
|
9
|
+
import { LifeCycle, LifeCyclesController, getVariables } from "./lifeCycles";
|
|
10
|
+
import { createLifeCycleSearch, LifecycleInstance } from "./lifeCycleSearch";
|
|
11
|
+
import { IndexedLogResults } from "../IndexedLogs/BufferIndexHelpers";
|
|
12
|
+
import { formatSearchString } from "../IndexedLogs/LogViewerParams";
|
|
13
|
+
import { getPathStr, getPathStr2 } from "../../../path";
|
|
14
|
+
|
|
15
|
+
export class NestedLifeCycleInfo extends qreact.Component<{
|
|
16
|
+
keyName: string;
|
|
17
|
+
value: unknown;
|
|
18
|
+
aliases: string[];
|
|
19
|
+
currentLifeCycleId: string;
|
|
20
|
+
}> {
|
|
21
|
+
state = t.state({
|
|
22
|
+
searchingLifeCycleId: t.atomic<string | undefined>(undefined),
|
|
23
|
+
phase1Results: t.atomic<LogDatum[]>([]),
|
|
24
|
+
phase1InvalidResults: t.atomic<Array<{ datum: LogDatum; missingKeys: string[] }>>([]),
|
|
25
|
+
phase1Stats: t.atomic<IndexedLogResults | undefined>(undefined),
|
|
26
|
+
phase1Searching: t.boolean(false),
|
|
27
|
+
phase2Results: t.atomic<LogDatum[]>([]),
|
|
28
|
+
phase2Stats: t.atomic<IndexedLogResults | undefined>(undefined),
|
|
29
|
+
phase2Searching: t.boolean(false),
|
|
30
|
+
phase2HitLimit: t.boolean(false),
|
|
31
|
+
lifecycleInstances: t.atomic<LifecycleInstance[]>([]),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
controller = LifeCyclesController(SocketFunction.browserNodeId());
|
|
35
|
+
search = createLifeCycleSearch(this);
|
|
36
|
+
|
|
37
|
+
render() {
|
|
38
|
+
let lifeCycles = this.controller.getLifeCycles();
|
|
39
|
+
if (!lifeCycles) return null;
|
|
40
|
+
|
|
41
|
+
let { keyName, value, aliases, currentLifeCycleId } = this.props;
|
|
42
|
+
|
|
43
|
+
let allKeysToMatch = [keyName, ...aliases].filter(x => !["__threadId", "__machineId"].includes(x));
|
|
44
|
+
let matchingLifeCycles: Array<{ lifecycle: LifeCycle; matchedKey: string }> = [];
|
|
45
|
+
|
|
46
|
+
for (let lifecycle of lifeCycles) {
|
|
47
|
+
if (lifecycle.id === currentLifeCycleId) continue;
|
|
48
|
+
|
|
49
|
+
let matchedKey: string | undefined = undefined;
|
|
50
|
+
for (let entry of lifecycle.entries) {
|
|
51
|
+
for (let groupByKey of entry.groupByKeys) {
|
|
52
|
+
if (allKeysToMatch.includes(groupByKey.ourKey)) {
|
|
53
|
+
matchedKey = groupByKey.ourKey;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (matchedKey !== undefined) break;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (matchedKey !== undefined) {
|
|
61
|
+
matchingLifeCycles.push({ lifecycle, matchedKey });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (matchingLifeCycles.length === 0) return null;
|
|
66
|
+
|
|
67
|
+
return <>
|
|
68
|
+
{matchingLifeCycles.map(({ lifecycle, matchedKey }) => {
|
|
69
|
+
let isSearchingThis = this.state.searchingLifeCycleId === lifecycle.id;
|
|
70
|
+
let isSearching = isSearchingThis && (this.state.phase1Searching || this.state.phase2Searching);
|
|
71
|
+
let hasSearched = isSearchingThis && !isSearching;
|
|
72
|
+
|
|
73
|
+
let instances = hasSearched && this.state.lifecycleInstances.filter(inst =>
|
|
74
|
+
inst.keys.some(k => k.key === matchedKey && k.value === value)
|
|
75
|
+
) || [];
|
|
76
|
+
let hasResults = instances.length > 0;
|
|
77
|
+
|
|
78
|
+
let progressText = "";
|
|
79
|
+
if (isSearching && isSearchingThis) {
|
|
80
|
+
let parts: string[] = [];
|
|
81
|
+
|
|
82
|
+
if (this.state.phase1Searching || this.state.phase1Stats) {
|
|
83
|
+
let stats = this.state.phase1Stats;
|
|
84
|
+
let blocksScanned = stats && (stats.localFilesSearched + stats.backblazeFilesSearched) || 0;
|
|
85
|
+
let resultsCount = this.state.phase1Results.length;
|
|
86
|
+
parts.push(`${blocksScanned} > ${resultsCount}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (this.state.phase2Searching) {
|
|
90
|
+
let stats = this.state.phase2Stats;
|
|
91
|
+
let blocksScanned = stats && (stats.localFilesSearched + stats.backblazeFilesSearched) || 0;
|
|
92
|
+
let resultsCount = this.state.phase2Results.length;
|
|
93
|
+
parts.push(`${blocksScanned} > ${resultsCount}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
progressText = parts.length > 0 && `(${parts.join(" | ")})` || "(...)";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return <>
|
|
100
|
+
<Button
|
|
101
|
+
hue={180}
|
|
102
|
+
onClick={() => {
|
|
103
|
+
void this.search.searchLifeCycle({
|
|
104
|
+
lifeCycleId: lifecycle.id,
|
|
105
|
+
limit: 10,
|
|
106
|
+
additionalSearch: formatSearchString({ [matchedKey]: value })
|
|
107
|
+
});
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
{lifecycle.title} {isSearching && "⏳" || ""} {progressText} {hasSearched && `(${instances.length})` || ""}
|
|
111
|
+
</Button>
|
|
112
|
+
{hasSearched && !hasResults && (
|
|
113
|
+
<div className={css.pad2(4).colorhsl(0, 0, 50)}>No results found</div>
|
|
114
|
+
)}
|
|
115
|
+
{hasResults && (() => {
|
|
116
|
+
let firstInstance = instances[0];
|
|
117
|
+
let totalCount = instances.length;
|
|
118
|
+
let hitLimit = totalCount >= 10;
|
|
119
|
+
|
|
120
|
+
let keys = lifecycle.entries.map(x => x.groupByKeys.map(y => y.ourKey).concat(Object.keys(x.variables))).flat();
|
|
121
|
+
|
|
122
|
+
function valueHash(key: string, value: unknown) {
|
|
123
|
+
return getPathStr2(key, String(value));
|
|
124
|
+
}
|
|
125
|
+
let values = new Map<string, { key: string; value: unknown }>();
|
|
126
|
+
for (let entry of firstInstance.entries) {
|
|
127
|
+
for (let [key, value] of Object.entries(entry.datum)) {
|
|
128
|
+
if (!keys.includes(key)) continue;
|
|
129
|
+
if (key === matchedKey) continue;
|
|
130
|
+
if (["__threadId", "__machineId"].includes(key)) continue;
|
|
131
|
+
values.set(valueHash(key, value), { key, value });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return <>
|
|
136
|
+
<div className={css.hbox(4)}>
|
|
137
|
+
{hitLimit && <span className={css.colorhsl(30, 80, 40).boldStyle}>(hit limit!)</span>}
|
|
138
|
+
</div>
|
|
139
|
+
{Array.from(values.values()).map(({ key, value }, idx) => (
|
|
140
|
+
<div key={idx} className={css.hbox(4)}>
|
|
141
|
+
<span className={css.colorhsl(200, 50, 60).boldStyle}>{key}:</span>
|
|
142
|
+
<span className={css.colorhsl(0, 0, 50)}>{niceStringify(value)}</span>
|
|
143
|
+
</div>
|
|
144
|
+
))}
|
|
145
|
+
</>;
|
|
146
|
+
})()}
|
|
147
|
+
</>;
|
|
148
|
+
})}
|
|
149
|
+
</>;
|
|
150
|
+
}
|
|
151
|
+
}
|