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
package/package.json
CHANGED
|
@@ -118,7 +118,7 @@ class ArchivesDisk {
|
|
|
118
118
|
if (dir.endsWith(":")) break;
|
|
119
119
|
let files = await fs.promises.readdir(this.LOCAL_ARCHIVE_FOLDER + dir);
|
|
120
120
|
if (files.length > 0) break;
|
|
121
|
-
await fs.promises.
|
|
121
|
+
await fs.promises.rm(this.LOCAL_ARCHIVE_FOLDER + dir, { recursive: true });
|
|
122
122
|
}
|
|
123
123
|
} catch { }
|
|
124
124
|
}
|
|
@@ -176,7 +176,7 @@ class ArchivesDisk {
|
|
|
176
176
|
} catch { }
|
|
177
177
|
if (files.length === 0) {
|
|
178
178
|
try {
|
|
179
|
-
await fs.promises.
|
|
179
|
+
await fs.promises.rm(dir, { recursive: true });
|
|
180
180
|
} catch { }
|
|
181
181
|
}
|
|
182
182
|
}
|
|
@@ -17,6 +17,7 @@ import { delay } from "socket-function/src/batching";
|
|
|
17
17
|
import { formatTime } from "socket-function/src/formatting/format";
|
|
18
18
|
import { waitForFirstTimeSync } from "socket-function/time/trueTimeShim";
|
|
19
19
|
import { red } from "socket-function/src/formatting/logColors";
|
|
20
|
+
import { isNode } from "typesafecss";
|
|
20
21
|
|
|
21
22
|
let callerInfo = new Map<CallerContext, {
|
|
22
23
|
reconnectNodeId: string | undefined;
|
|
@@ -108,6 +109,7 @@ export interface ChangeIdentityPayload {
|
|
|
108
109
|
certIssuer: string;
|
|
109
110
|
serverId: string;
|
|
110
111
|
mountedPort: number | undefined;
|
|
112
|
+
debugEntryPoint: string | undefined;
|
|
111
113
|
}
|
|
112
114
|
class IdentityControllerBase {
|
|
113
115
|
// IMPORTANT! We HAVE to call changeIdentity NOT JUST because we can't use peer certificates in the browser, BUT, also
|
|
@@ -158,7 +160,22 @@ class IdentityControllerBase {
|
|
|
158
160
|
pubKeyShort: getShortNumber(pubKey),
|
|
159
161
|
});
|
|
160
162
|
|
|
161
|
-
|
|
163
|
+
let duration = Date.now() - time;
|
|
164
|
+
console.log(`Authenticated identity for ${caller.nodeId} in ${formatTime(duration)}, at ${Date.now()}`, {
|
|
165
|
+
clientId: caller.nodeId,
|
|
166
|
+
reconnectNodeId,
|
|
167
|
+
duration,
|
|
168
|
+
mountedPort: payload.mountedPort,
|
|
169
|
+
debugEntryPoint: payload.debugEntryPoint,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
SocketFunction.onNextDisconnect(caller.nodeId, () => {
|
|
173
|
+
// NOTE: I don't really see any purpose of deleting from caller info. I don't think we're going to run out of memory because of too many callers authenticating.
|
|
174
|
+
// However, logging here is useful as it allows us to complete the life cycle so we know how long a client was connected for.
|
|
175
|
+
console.log(`Disconnected client`, {
|
|
176
|
+
clientId: caller.nodeId,
|
|
177
|
+
});
|
|
178
|
+
});
|
|
162
179
|
}
|
|
163
180
|
}
|
|
164
181
|
|
|
@@ -188,6 +205,7 @@ const changeIdentityOnce = cacheWeak(async function changeIdentityOnce(connectio
|
|
|
188
205
|
cert: threadKeyCert.cert.toString(),
|
|
189
206
|
certIssuer: issuer.cert.toString(),
|
|
190
207
|
mountedPort: getNodeIdLocation(SocketFunction.mountedNodeId)?.port,
|
|
208
|
+
debugEntryPoint: isNode() ? process.argv[1] : "browser",
|
|
191
209
|
};
|
|
192
210
|
let signature = sign(threadKeyCert, payload);
|
|
193
211
|
await timeoutToError(
|
|
@@ -1249,14 +1249,14 @@ class PathWatcher {
|
|
|
1249
1249
|
if (newPathsWatched.size > 0 || newParentsWatched.size > 0) {
|
|
1250
1250
|
if (isOwnNodeId(config.callback)) {
|
|
1251
1251
|
for (let path of newPathsWatched) {
|
|
1252
|
-
auditLog("new local WATCH", { path });
|
|
1252
|
+
auditLog("new local WATCH VALUE", { path });
|
|
1253
1253
|
}
|
|
1254
1254
|
for (let path of newParentsWatched) {
|
|
1255
1255
|
auditLog("new local WATCH PARENT", { path });
|
|
1256
1256
|
}
|
|
1257
1257
|
} else {
|
|
1258
1258
|
for (let path of newPathsWatched) {
|
|
1259
|
-
auditLog("new non-local WATCH", { path, watcher: config.callback });
|
|
1259
|
+
auditLog("new non-local WATCH VALUE", { path, watcher: config.callback });
|
|
1260
1260
|
}
|
|
1261
1261
|
for (let path of newParentsWatched) {
|
|
1262
1262
|
auditLog("new non-local WATCH PARENT", { path, watcher: config.callback });
|
|
@@ -1381,9 +1381,9 @@ class PathWatcher {
|
|
|
1381
1381
|
watchers.watchers.delete(callback);
|
|
1382
1382
|
|
|
1383
1383
|
if (isOwnNodeId(callback)) {
|
|
1384
|
-
auditLog("local UNWATCH", { path });
|
|
1384
|
+
auditLog("local UNWATCH VALUE", { path });
|
|
1385
1385
|
} else {
|
|
1386
|
-
auditLog("non-local UNWATCH", { path, watcher: callback });
|
|
1386
|
+
auditLog("non-local UNWATCH VALUE", { path, watcher: callback });
|
|
1387
1387
|
}
|
|
1388
1388
|
|
|
1389
1389
|
if (watchers.watchers.size === 0) {
|
|
@@ -1781,9 +1781,9 @@ class WriteValidStorage {
|
|
|
1781
1781
|
}
|
|
1782
1782
|
public setWriteValidState(write: WriteState, lockless?: boolean): boolean {
|
|
1783
1783
|
if (write.isValid) {
|
|
1784
|
-
auditLog(
|
|
1784
|
+
auditLog("ACCEPTING VALUE", { path: write.path, time: write.time.time, reason: write.reason });
|
|
1785
1785
|
} else {
|
|
1786
|
-
auditLog(
|
|
1786
|
+
auditLog("REJECTING VALUE", { path: write.path, time: write.time.time, reason: write.reason });
|
|
1787
1787
|
}
|
|
1788
1788
|
this.ensureGarbageCollectOldState();
|
|
1789
1789
|
|
|
@@ -2359,8 +2359,8 @@ export const lockWatchDeduper = new LockWatchDeduper();
|
|
|
2359
2359
|
// can trigger the import of memoryValueAudit for all clients as consistently
|
|
2360
2360
|
// as the core.
|
|
2361
2361
|
setImmediate(() => {
|
|
2362
|
-
logErrors(import("../5-diagnostics/memoryValueAudit").then(x => x.startMemoryAuditLoop()));
|
|
2363
|
-
logErrors(import("../5-diagnostics/diskValueAudit").then(x => x.startDiskAuditLoop()));
|
|
2362
|
+
//logErrors(import("../5-diagnostics/memoryValueAudit").then(x => x.startMemoryAuditLoop()));
|
|
2363
|
+
//logErrors(import("../5-diagnostics/diskValueAudit").then(x => x.startDiskAuditLoop()));
|
|
2364
2364
|
logErrors(import("../5-diagnostics/synchronousLagTracking").then(x => x.trackSynchronousLag()));
|
|
2365
2365
|
});
|
|
2366
2366
|
|
|
@@ -170,7 +170,7 @@ let moduleResolver = async (spec: {
|
|
|
170
170
|
|
|
171
171
|
// Remove any previous attempt to sync it
|
|
172
172
|
if (fs.existsSync(repoPath)) {
|
|
173
|
-
await fs.promises.
|
|
173
|
+
await fs.promises.rm(repoPath, { recursive: true });
|
|
174
174
|
}
|
|
175
175
|
// Clone it
|
|
176
176
|
await executeCommand("git", ["clone", gitURL, repoPath]);
|
|
@@ -182,7 +182,7 @@ let moduleResolver = async (spec: {
|
|
|
182
182
|
// Delete querysub, and replace it with a symlink. Otherwise the synchronization code
|
|
183
183
|
// will run again, and a lot of setup code will run again, etc, and nothing will work correctly.
|
|
184
184
|
let querysubPath = repoPath + "node_modules/querysub";
|
|
185
|
-
await fs.promises.
|
|
185
|
+
await fs.promises.rm(querysubPath, { recursive: true });
|
|
186
186
|
|
|
187
187
|
let actualQuerysubPath = path.resolve("./node_modules/querysub");
|
|
188
188
|
await fs.promises.symlink(actualQuerysubPath, querysubPath, "junction");
|
|
@@ -600,7 +600,7 @@ const ensureGitSynced = measureWrap(async function ensureGitSynced(config: {
|
|
|
600
600
|
await setGitRef(config);
|
|
601
601
|
} catch (e: any) {
|
|
602
602
|
console.warn(`Failed to set git ref, trying to delete and clone again (${config.gitFolder}): ${e.stack}`);
|
|
603
|
-
await fs.promises.
|
|
603
|
+
await fs.promises.rm(config.gitFolder, { recursive: true });
|
|
604
604
|
await runPromise(`git clone ${config.repoUrl} ${config.gitFolder}`);
|
|
605
605
|
await setGitRef(config);
|
|
606
606
|
}
|
|
@@ -30,13 +30,12 @@ import { readProductionLogsURL, searchTextURL } from "./LogViewerParams";
|
|
|
30
30
|
import { RenderSearchStats } from "./RenderSearchStats";
|
|
31
31
|
import { LifeCyclesController, LifeCycle, LifeCycleEntry } from "../lifeCycleAnalysis/lifeCycles";
|
|
32
32
|
import { getLifecycleMatchesForDatum } from "../lifeCycleAnalysis/lifeCycleMatching";
|
|
33
|
-
import { lifecycleIdURL } from "../lifeCycleAnalysis/LifeCyclePage";
|
|
33
|
+
import { lifecycleIdURL, additionalSearchURL } from "../lifeCycleAnalysis/LifeCyclePage";
|
|
34
34
|
import { ATag } from "../../../library-components/ATag";
|
|
35
35
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
36
36
|
import { managementPageURL } from "../../managementPages";
|
|
37
37
|
import { startTimeParam, endTimeParam } from "../TimeRangeSelector";
|
|
38
38
|
import { formatSearchString } from "./LogViewerParams";
|
|
39
|
-
import { additionalSearchURL } from "../lifeCycleAnalysis/lifeCycleSearch";
|
|
40
39
|
|
|
41
40
|
let excludePendingResults = new URLParam("excludePendingResults", false);
|
|
42
41
|
let limitURL = new URLParam("limit", 100);
|
|
@@ -564,7 +563,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
564
563
|
startTimeParam.getOverride(datum.time - timeInHour),
|
|
565
564
|
endTimeParam.getOverride(datum.time + timeInHour),
|
|
566
565
|
]}>
|
|
567
|
-
{match.lifecycle.title}
|
|
566
|
+
View {JSON.stringify(match.lifecycle.title)}
|
|
568
567
|
</ATag>
|
|
569
568
|
</div>
|
|
570
569
|
))}
|
|
@@ -576,7 +575,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
576
575
|
endTimeParam.getOverride(maxTime + timeInHour),
|
|
577
576
|
]}>
|
|
578
577
|
<Button hue={280} className={css.boldStyle}>
|
|
579
|
-
View All
|
|
578
|
+
View All
|
|
580
579
|
</Button>
|
|
581
580
|
</ATag>
|
|
582
581
|
</div>;
|
|
@@ -9,6 +9,7 @@ const SEARCH_OR_CHAR = "|";
|
|
|
9
9
|
const SEARCH_AND_BYTE = 38;
|
|
10
10
|
const SEARCH_AND_CHAR = "&";
|
|
11
11
|
|
|
12
|
+
// IMPORTANT! While technically THIS structure can support negation, the index structure above us cannot, as it only stores information at the block level (multiple buffers per block). So... if we supported negation, it couldn't, so it would give us a lot of false positives (cases where negation removes the results). So... never use negation, just turn everything into a positive signal.
|
|
12
13
|
export type MatchStructure = {
|
|
13
14
|
type: "or";
|
|
14
15
|
parts: MatchStructure[];
|
|
@@ -12,7 +12,7 @@ import { ErrorWarning } from "./ErrorWarning";
|
|
|
12
12
|
import { getErrorNotificationsManager } from "./errorWatcher";
|
|
13
13
|
import { createMatchesPattern } from "../IndexedLogs/bufferSearchFindMatcher";
|
|
14
14
|
import { cacheLimited } from "socket-function/src/caching";
|
|
15
|
-
import { managementPageURL } from "../../managementPages";
|
|
15
|
+
import { managementPageURL, showingManagementURL } from "../../managementPages";
|
|
16
16
|
import { searchTextURL, readProductionLogsURL } from "../IndexedLogs/LogViewerParams";
|
|
17
17
|
import { startTimeParam, endTimeParam } from "../TimeRangeSelector";
|
|
18
18
|
import { timeInHour } from "socket-function/src/misc";
|
|
@@ -56,6 +56,7 @@ export class LogDatumRenderer extends qreact.Component<{
|
|
|
56
56
|
let logViewerValues: URLOverride[] | undefined;
|
|
57
57
|
{
|
|
58
58
|
logViewerValues = [
|
|
59
|
+
showingManagementURL.getOverride(true),
|
|
59
60
|
managementPageURL.getOverride("LogViewer3"),
|
|
60
61
|
readProductionLogsURL.getOverride(isPublic()),
|
|
61
62
|
startTimeParam.getOverride(datum.time - timeInHour),
|
|
@@ -382,6 +383,7 @@ class SuppressionItem extends qreact.Component<{
|
|
|
382
383
|
<div className={css.hbox(12)}>
|
|
383
384
|
<ATag
|
|
384
385
|
values={[
|
|
386
|
+
showingManagementURL.getOverride(true),
|
|
385
387
|
managementPageURL.getOverride("LogViewer3"),
|
|
386
388
|
searchTextURL.getOverride(suppression.pattern),
|
|
387
389
|
readProductionLogsURL.getOverride(true),
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
+
import { deepCloneJSON } from "socket-function/src/misc";
|
|
3
|
+
import { css } from "typesafecss";
|
|
4
|
+
import { t } from "../../../2-proxy/schema2";
|
|
5
|
+
import { qreact } from "../../../4-dom/qreact";
|
|
6
|
+
import { Querysub } from "../../../4-querysub/QuerysubController";
|
|
7
|
+
import { Button } from "../../../library-components/Button";
|
|
8
|
+
import { InputLabel } from "../../../library-components/InputLabel";
|
|
9
|
+
import { LogDatum } from "../diskLogger";
|
|
10
|
+
import { LifeCycleEntryReadMode } from "./LifeCycleEntryReadMode";
|
|
11
|
+
import { LifeCycle, LifeCycleEntry, LifeCyclesController } from "./lifeCycles";
|
|
12
|
+
|
|
13
|
+
export class LifeCycleEntryEditor extends qreact.Component<{
|
|
14
|
+
lifeCycle: LifeCycle;
|
|
15
|
+
entry: LifeCycleEntry;
|
|
16
|
+
entryIndex: number;
|
|
17
|
+
defaultEditMode?: boolean;
|
|
18
|
+
datum?: LogDatum;
|
|
19
|
+
}> {
|
|
20
|
+
state = t.state({
|
|
21
|
+
editMode: t.atomic<boolean>(false),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
controller = LifeCyclesController(SocketFunction.browserNodeId());
|
|
25
|
+
|
|
26
|
+
componentDidMount() {
|
|
27
|
+
this.state.editMode = this.props.defaultEditMode || false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
render() {
|
|
31
|
+
let { lifeCycle, entry, entryIndex } = this.props;
|
|
32
|
+
let isConfigured = entry.groupByKeys && entry.groupByKeys.length > 0 && entry.groupByKeys.every(k => k.ourKey.trim() !== "");
|
|
33
|
+
let bgClass = isConfigured && css.hsl(150, 30, 95) || css.hsl(30, 50, 90);
|
|
34
|
+
|
|
35
|
+
return <div className={
|
|
36
|
+
css.pad2(4).bord2(150, 30, 70)
|
|
37
|
+
.vbox(4)
|
|
38
|
+
.cursor("auto")
|
|
39
|
+
+ bgClass
|
|
40
|
+
+ " LifeCycleEntryEditor"
|
|
41
|
+
}>
|
|
42
|
+
<div className={css.hbox(12)}>
|
|
43
|
+
<span className={css.boldStyle}>{entry.description || entry.matchPattern}</span>
|
|
44
|
+
{entry.description && <span className={css.colorhsl(0, 0, 50)}>({entry.matchPattern})</span>}
|
|
45
|
+
<span className={css.colorhsl(220, 70, 50)}>[{entry.sourceType}]</span>
|
|
46
|
+
{entry.isStart && <span className={css.colorhsl(120, 80, 30).boldStyle}>START</span>}
|
|
47
|
+
{entry.isEnd && <span className={css.colorhsl(0, 80, 30).boldStyle}>END</span>}
|
|
48
|
+
{!isConfigured && !this.state.editMode && <span className={css.colorhsl(30, 80, 40).boldStyle}>(not fully configured)</span>}
|
|
49
|
+
<Button
|
|
50
|
+
hue={150}
|
|
51
|
+
onClick={() => {
|
|
52
|
+
this.state.editMode = !this.state.editMode;
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
{this.state.editMode && "Read Mode" || "Edit Mode"}
|
|
56
|
+
</Button>
|
|
57
|
+
<Button
|
|
58
|
+
hue={0}
|
|
59
|
+
onClick={() => {
|
|
60
|
+
if (!confirm("Delete this entry?")) return;
|
|
61
|
+
|
|
62
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
63
|
+
updatedLifeCycle.entries.splice(entryIndex, 1);
|
|
64
|
+
Querysub.onCommitFinished(async () => {
|
|
65
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
66
|
+
});
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
Delete
|
|
70
|
+
</Button>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{this.state.editMode && (
|
|
74
|
+
<div className={css.vbox(12)}>
|
|
75
|
+
<div className={css.hbox(10)}>
|
|
76
|
+
<InputLabel
|
|
77
|
+
label="Match Pattern"
|
|
78
|
+
value={entry.matchPattern}
|
|
79
|
+
onChangeValue={(value) => {
|
|
80
|
+
let matchPattern = value.trim();
|
|
81
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
82
|
+
updatedLifeCycle.entries[entryIndex].matchPattern = matchPattern;
|
|
83
|
+
Querysub.onCommitFinished(async () => {
|
|
84
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
85
|
+
});
|
|
86
|
+
}}
|
|
87
|
+
className={css.width(500)}
|
|
88
|
+
/>
|
|
89
|
+
<InputLabel
|
|
90
|
+
label="Description"
|
|
91
|
+
value={entry.description || ""}
|
|
92
|
+
onChangeValue={(value) => {
|
|
93
|
+
let description = value.trim() || undefined;
|
|
94
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
95
|
+
updatedLifeCycle.entries[entryIndex].description = description;
|
|
96
|
+
Querysub.onCommitFinished(async () => {
|
|
97
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
98
|
+
});
|
|
99
|
+
}}
|
|
100
|
+
className={css.width(500)}
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div className={css.hbox(12)}>
|
|
105
|
+
<div className={css.vbox(4)}>
|
|
106
|
+
<span>Source Type</span>
|
|
107
|
+
<select
|
|
108
|
+
value={entry.sourceType}
|
|
109
|
+
onChange={(e) => {
|
|
110
|
+
let sourceType = e.currentTarget.value as "log" | "error" | "info" | "warning";
|
|
111
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
112
|
+
updatedLifeCycle.entries[entryIndex].sourceType = sourceType;
|
|
113
|
+
Querysub.onCommitFinished(async () => {
|
|
114
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
115
|
+
});
|
|
116
|
+
}}
|
|
117
|
+
className={css.pad2(4, 2)}
|
|
118
|
+
>
|
|
119
|
+
<option value="log">log</option>
|
|
120
|
+
<option value="info">info</option>
|
|
121
|
+
<option value="warning">warning</option>
|
|
122
|
+
<option value="error">error</option>
|
|
123
|
+
</select>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div className={css.hbox(12)}>
|
|
128
|
+
<InputLabel
|
|
129
|
+
label="Is Start"
|
|
130
|
+
checkbox
|
|
131
|
+
checked={entry.isStart}
|
|
132
|
+
onChange={(e) => {
|
|
133
|
+
let isStart = e.currentTarget.checked || undefined;
|
|
134
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
135
|
+
updatedLifeCycle.entries[entryIndex].isStart = isStart;
|
|
136
|
+
Querysub.onCommitFinished(async () => {
|
|
137
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
138
|
+
});
|
|
139
|
+
}}
|
|
140
|
+
/>
|
|
141
|
+
<InputLabel
|
|
142
|
+
label="Is End"
|
|
143
|
+
checkbox
|
|
144
|
+
checked={entry.isEnd}
|
|
145
|
+
onChange={(e) => {
|
|
146
|
+
let isEnd = e.currentTarget.checked || undefined;
|
|
147
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
148
|
+
updatedLifeCycle.entries[entryIndex].isEnd = isEnd;
|
|
149
|
+
Querysub.onCommitFinished(async () => {
|
|
150
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
151
|
+
});
|
|
152
|
+
}}
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<div className={css.vbox(8)}>
|
|
157
|
+
<div className={!isConfigured && css.colorhsl(30, 80, 40).boldStyle || ""}>
|
|
158
|
+
Group By Keys{!isConfigured && " (at least one required)" || ""}
|
|
159
|
+
</div>
|
|
160
|
+
{Array.isArray(entry.groupByKeys) && entry.groupByKeys.map((keyConfig, keyIdx) => {
|
|
161
|
+
let keyExistsInAllOtherEntries = lifeCycle.entries.every((e, idx) => {
|
|
162
|
+
if (idx === entryIndex) return true;
|
|
163
|
+
return e.groupByKeys && e.groupByKeys.some(k => k.ourKey === keyConfig.ourKey);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return <div key={keyIdx} className={css.hbox(8).pad2(4).bord2(200, 30, 80).hsl(200, 30, 97)}>
|
|
167
|
+
<Button
|
|
168
|
+
hue={160}
|
|
169
|
+
disabled={keyExistsInAllOtherEntries}
|
|
170
|
+
onClick={() => {
|
|
171
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
172
|
+
updatedLifeCycle.entries.forEach((e, idx) => {
|
|
173
|
+
if (idx === entryIndex) return;
|
|
174
|
+
if (!e.groupByKeys.some(k => k.ourKey === keyConfig.ourKey)) {
|
|
175
|
+
e.groupByKeys.push({ ourKey: keyConfig.ourKey });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
Querysub.onCommitFinished(async () => {
|
|
179
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
180
|
+
});
|
|
181
|
+
}}
|
|
182
|
+
>
|
|
183
|
+
Copy to All
|
|
184
|
+
</Button>
|
|
185
|
+
<InputLabel
|
|
186
|
+
label="Key"
|
|
187
|
+
value={keyConfig.ourKey}
|
|
188
|
+
onChangeValue={(value) => {
|
|
189
|
+
let ourKey = value.trim();
|
|
190
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
191
|
+
updatedLifeCycle.entries[entryIndex].groupByKeys[keyIdx].ourKey = ourKey;
|
|
192
|
+
Querysub.onCommitFinished(async () => {
|
|
193
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
194
|
+
});
|
|
195
|
+
}}
|
|
196
|
+
className={css.width(250)}
|
|
197
|
+
/>
|
|
198
|
+
<InputLabel
|
|
199
|
+
label="Different key in start entry (not usually needed)"
|
|
200
|
+
value={keyConfig.startKey || ""}
|
|
201
|
+
onChangeValue={(value) => {
|
|
202
|
+
let startKey = value.trim() || undefined;
|
|
203
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
204
|
+
updatedLifeCycle.entries[entryIndex].groupByKeys[keyIdx].startKey = startKey;
|
|
205
|
+
Querysub.onCommitFinished(async () => {
|
|
206
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
207
|
+
});
|
|
208
|
+
}}
|
|
209
|
+
className={css.width(250)}
|
|
210
|
+
/>
|
|
211
|
+
<InputLabel
|
|
212
|
+
label="Aliases (comma-separated)"
|
|
213
|
+
value={keyConfig.aliases && keyConfig.aliases.join(", ") || ""}
|
|
214
|
+
onChangeValue={(value) => {
|
|
215
|
+
let aliases = value.trim() && value.split(",").map(a => a.trim()).filter(a => a) || undefined;
|
|
216
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
217
|
+
updatedLifeCycle.entries.forEach((e) => {
|
|
218
|
+
let matchingKey = e.groupByKeys.find(k => k.ourKey === keyConfig.ourKey);
|
|
219
|
+
if (matchingKey) {
|
|
220
|
+
matchingKey.aliases = aliases;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
Querysub.onCommitFinished(async () => {
|
|
224
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
225
|
+
});
|
|
226
|
+
}}
|
|
227
|
+
className={css.width(250)}
|
|
228
|
+
/>
|
|
229
|
+
<Button
|
|
230
|
+
hue={0}
|
|
231
|
+
onClick={() => {
|
|
232
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
233
|
+
updatedLifeCycle.entries[entryIndex].groupByKeys.splice(keyIdx, 1);
|
|
234
|
+
Querysub.onCommitFinished(async () => {
|
|
235
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
236
|
+
});
|
|
237
|
+
}}
|
|
238
|
+
>
|
|
239
|
+
Delete
|
|
240
|
+
</Button>
|
|
241
|
+
</div>;
|
|
242
|
+
})}
|
|
243
|
+
<InputLabel
|
|
244
|
+
label="Add new key"
|
|
245
|
+
onChangeValue={(value) => {
|
|
246
|
+
let ourKey = value.trim();
|
|
247
|
+
if (ourKey) {
|
|
248
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
249
|
+
updatedLifeCycle.entries[entryIndex].groupByKeys.push({ ourKey });
|
|
250
|
+
Querysub.onCommitFinished(async () => {
|
|
251
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}}
|
|
255
|
+
className={css.width(250)}
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
<div className={css.vbox(4)}>
|
|
260
|
+
{Object.entries(entry.variables).map(([key, varData]) => {
|
|
261
|
+
let keyExistsInAllOtherEntries = lifeCycle.entries.every((e, idx) => {
|
|
262
|
+
if (idx === entryIndex) return true;
|
|
263
|
+
return key in e.variables;
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
return <div key={key} className={css.hbox(8).pad2(4).bord2(0, 0, 80).hsl(0, 0, 97)}>
|
|
267
|
+
<Button
|
|
268
|
+
hue={160}
|
|
269
|
+
disabled={keyExistsInAllOtherEntries}
|
|
270
|
+
onClick={() => {
|
|
271
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
272
|
+
updatedLifeCycle.entries.forEach((e, idx) => {
|
|
273
|
+
if (idx === entryIndex) return;
|
|
274
|
+
if (!(key in e.variables)) {
|
|
275
|
+
e.variables[key] = {};
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
Querysub.onCommitFinished(async () => {
|
|
279
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
280
|
+
});
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
Copy to All
|
|
284
|
+
</Button>
|
|
285
|
+
<span className={css.minWidth(150).boldStyle}>{key}</span>
|
|
286
|
+
<InputLabel
|
|
287
|
+
placeholder="Variable title"
|
|
288
|
+
value={varData.title || ""}
|
|
289
|
+
onChangeValue={(value) => {
|
|
290
|
+
let title = value.trim() || undefined;
|
|
291
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
292
|
+
updatedLifeCycle.entries[entryIndex].variables[key].title = title;
|
|
293
|
+
Querysub.onCommitFinished(async () => {
|
|
294
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
295
|
+
});
|
|
296
|
+
}}
|
|
297
|
+
className={css.width(300)}
|
|
298
|
+
/>
|
|
299
|
+
<InputLabel
|
|
300
|
+
placeholder="Aliases (comma-separated)"
|
|
301
|
+
value={varData.aliases && varData.aliases.join(", ") || ""}
|
|
302
|
+
onChangeValue={(value) => {
|
|
303
|
+
let aliases = value.trim() && value.split(",").map(a => a.trim()).filter(a => a) || undefined;
|
|
304
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
305
|
+
updatedLifeCycle.entries.forEach((e) => {
|
|
306
|
+
if (key in e.variables) {
|
|
307
|
+
e.variables[key].aliases = aliases;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
Querysub.onCommitFinished(async () => {
|
|
311
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
312
|
+
});
|
|
313
|
+
}}
|
|
314
|
+
className={css.width(300)}
|
|
315
|
+
/>
|
|
316
|
+
<Button
|
|
317
|
+
hue={0}
|
|
318
|
+
onClick={() => {
|
|
319
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
320
|
+
delete updatedLifeCycle.entries[entryIndex].variables[key];
|
|
321
|
+
Querysub.onCommitFinished(async () => {
|
|
322
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
323
|
+
});
|
|
324
|
+
}}
|
|
325
|
+
>
|
|
326
|
+
Delete
|
|
327
|
+
</Button>
|
|
328
|
+
</div>;
|
|
329
|
+
})}
|
|
330
|
+
|
|
331
|
+
<InputLabel
|
|
332
|
+
label="Add new variable key"
|
|
333
|
+
onChangeValue={(value) => {
|
|
334
|
+
let key = value.trim();
|
|
335
|
+
if (key) {
|
|
336
|
+
let updatedLifeCycle = deepCloneJSON(lifeCycle);
|
|
337
|
+
updatedLifeCycle.entries[entryIndex].variables[key] = {};
|
|
338
|
+
Querysub.onCommitFinished(async () => {
|
|
339
|
+
await this.controller.setLifeCycle.promise(updatedLifeCycle);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}}
|
|
343
|
+
className={css.width(300)}
|
|
344
|
+
/>
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
)}
|
|
348
|
+
|
|
349
|
+
{!this.state.editMode && <LifeCycleEntryReadMode
|
|
350
|
+
lifeCycle={lifeCycle}
|
|
351
|
+
entry={entry}
|
|
352
|
+
entryIndex={entryIndex}
|
|
353
|
+
datum={this.props.datum}
|
|
354
|
+
/>}
|
|
355
|
+
</div>;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|