@datarails-ai/dr-cli 1.6.106 → 1.6.107
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/dist/cli.js +107 -157
- package/dist/scopes-map.json +1 -1
- package/dist/skills/dr-cli/SKILL.md +3 -2
- package/dist/skills/dr-cli/reference/datamappers.md +23 -12
- package/dist/skills/dr-cli/reference/integrations.md +33 -1
- package/dist/skills/dr-cli/reference/templates.md +3 -2
- package/dist/skills/fx-conversion-matrix/SKILL.md +10 -16
- package/dist/skills/validate-workato-migration/SKILL.md +5 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import { fileURLToPath as fileURLToPath3 } from "url";
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "@datarails-ai/dr-cli",
|
|
11
|
-
version: "1.6.
|
|
11
|
+
version: "1.6.107",
|
|
12
12
|
description: "Datarails CLI",
|
|
13
13
|
type: "module",
|
|
14
14
|
engines: {
|
|
@@ -69,7 +69,7 @@ var package_default = {
|
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
// src/buildInfo.ts
|
|
72
|
-
var BUILD_TIME = "2026-10-
|
|
72
|
+
var BUILD_TIME = "2026-10-07T16:21:03.565Z";
|
|
73
73
|
function formatBuildTime(iso) {
|
|
74
74
|
if (iso === "") return "";
|
|
75
75
|
const d = new Date(iso);
|
|
@@ -5338,7 +5338,7 @@ async function poll(host, path11, deviceCode, options, terminalHints, onSuccess)
|
|
|
5338
5338
|
const interval = options.interval ?? 2e3;
|
|
5339
5339
|
const maxWait = options.maxWait ?? 6e5;
|
|
5340
5340
|
const now = options.now ?? Date.now;
|
|
5341
|
-
const
|
|
5341
|
+
const sleep3 = options.sleep ?? defaultSleep;
|
|
5342
5342
|
const signal = options.signal;
|
|
5343
5343
|
const base = host.replace(/\/+$/, "");
|
|
5344
5344
|
const deadline = now() + maxWait;
|
|
@@ -5366,7 +5366,7 @@ async function poll(host, path11, deviceCode, options, terminalHints, onSuccess)
|
|
|
5366
5366
|
}
|
|
5367
5367
|
const errCode = tryParseErrorCode(text2);
|
|
5368
5368
|
if (errCode === "authorization_pending" || errCode === "slow_down") {
|
|
5369
|
-
await
|
|
5369
|
+
await sleep3(interval, signal);
|
|
5370
5370
|
continue;
|
|
5371
5371
|
}
|
|
5372
5372
|
if (errCode) {
|
|
@@ -12284,7 +12284,7 @@ var defaultSleep2 = (ms, signal) => new Promise((resolve3, reject) => {
|
|
|
12284
12284
|
async function pollTask(client, label, path11, options = {}) {
|
|
12285
12285
|
const interval = options.pollIntervalMs ?? AGGREGATE_POLL_INTERVAL_MS;
|
|
12286
12286
|
const timeout = options.timeoutMs ?? AGGREGATE_POLL_TIMEOUT_MS;
|
|
12287
|
-
const
|
|
12287
|
+
const sleep3 = options.sleep ?? defaultSleep2;
|
|
12288
12288
|
const now = options.now ?? Date.now;
|
|
12289
12289
|
const inner = new AbortController();
|
|
12290
12290
|
const onOuterAbort = () => inner.abort(options.signal?.reason);
|
|
@@ -12326,7 +12326,7 @@ async function pollTask(client, label, path11, options = {}) {
|
|
|
12326
12326
|
return result.env;
|
|
12327
12327
|
}
|
|
12328
12328
|
try {
|
|
12329
|
-
await
|
|
12329
|
+
await sleep3(interval, inner.signal);
|
|
12330
12330
|
} catch {
|
|
12331
12331
|
const reason = inner.signal.reason instanceof Error ? inner.signal.reason.message : String(inner.signal.reason ?? "aborted");
|
|
12332
12332
|
throw new Error(`${label} polling timed out: ${reason}`);
|
|
@@ -15542,7 +15542,7 @@ var abortableSleep = (ms, signal) => new Promise((resolve3, reject) => {
|
|
|
15542
15542
|
});
|
|
15543
15543
|
async function putWithCacheKeyPolling(client, path11, payload, options = {}) {
|
|
15544
15544
|
const interval = options.pollIntervalMs ?? CACHE_KEY_POLL_INTERVAL_MS;
|
|
15545
|
-
const
|
|
15545
|
+
const sleep3 = options.sleep ?? abortableSleep;
|
|
15546
15546
|
const label = options.label ?? "data";
|
|
15547
15547
|
const body = JSON.stringify(payload);
|
|
15548
15548
|
const first = await putOnce(client, path11, body, options);
|
|
@@ -15558,7 +15558,7 @@ async function putWithCacheKeyPolling(client, path11, payload, options = {}) {
|
|
|
15558
15558
|
const pollPath = withCacheKey(path11, cacheKey);
|
|
15559
15559
|
for (; ; ) {
|
|
15560
15560
|
try {
|
|
15561
|
-
await
|
|
15561
|
+
await sleep3(interval, options.signal);
|
|
15562
15562
|
} catch (err) {
|
|
15563
15563
|
const reason = err instanceof Error ? err.message : String(err);
|
|
15564
15564
|
throw new Error(`${label} polling timed out: ${reason}`);
|
|
@@ -15747,7 +15747,7 @@ function parseDrilldown(body) {
|
|
|
15747
15747
|
}
|
|
15748
15748
|
async function exportDrilldownCsv(client, id, body, options = {}) {
|
|
15749
15749
|
const interval = options.pollIntervalMs ?? CACHE_KEY_POLL_INTERVAL_MS;
|
|
15750
|
-
const
|
|
15750
|
+
const sleep3 = options.sleep ?? abortableSleep;
|
|
15751
15751
|
const path11 = `/api/functions/${id}/drilldown/export_to_csv`;
|
|
15752
15752
|
const payload = JSON.stringify(body);
|
|
15753
15753
|
let res = await putOnce(client, path11, payload, options);
|
|
@@ -15758,7 +15758,7 @@ async function exportDrilldownCsv(client, id, body, options = {}) {
|
|
|
15758
15758
|
}
|
|
15759
15759
|
for (; ; ) {
|
|
15760
15760
|
try {
|
|
15761
|
-
await
|
|
15761
|
+
await sleep3(interval, options.signal);
|
|
15762
15762
|
} catch (err) {
|
|
15763
15763
|
const reason = err instanceof Error ? err.message : String(err);
|
|
15764
15764
|
throw new Error(`drill-down CSV export polling timed out: ${reason}`);
|
|
@@ -18210,7 +18210,7 @@ var LUT_SAVE_POLL_TIMEOUT_MS = 3e5;
|
|
|
18210
18210
|
async function saveMappings(client, documentId, body, options = {}) {
|
|
18211
18211
|
const interval = options.pollIntervalMs ?? LUT_SAVE_POLL_INTERVAL_MS;
|
|
18212
18212
|
const timeout = options.timeoutMs ?? LUT_SAVE_POLL_TIMEOUT_MS;
|
|
18213
|
-
const
|
|
18213
|
+
const sleep3 = options.sleep ?? defaultPollSleep;
|
|
18214
18214
|
const now = options.now ?? Date.now;
|
|
18215
18215
|
const deadline = now() + timeout;
|
|
18216
18216
|
const path11 = `/api/documents/${documentId}/web_update_lut`;
|
|
@@ -18232,7 +18232,7 @@ async function saveMappings(client, documentId, body, options = {}) {
|
|
|
18232
18232
|
`lut save-mappings: the server is still writing after ${Math.round(timeout / 1e3)}s (task ${cacheKey}). The write is still running server-side \u2014 re-run the same command to rejoin it, or raise --timeout.`
|
|
18233
18233
|
);
|
|
18234
18234
|
}
|
|
18235
|
-
await
|
|
18235
|
+
await sleep3(interval, options.signal);
|
|
18236
18236
|
const tick = await postOnce(client, pollPath, payload, options.signal);
|
|
18237
18237
|
if (tick.status === 200) {
|
|
18238
18238
|
return toResult(tick.text);
|
|
@@ -18530,7 +18530,7 @@ async function exportDocument(client, id, destinationOrgIds, override, reqOption
|
|
|
18530
18530
|
async function pollDocumentJob(client, basePath, label, requestId, options = {}) {
|
|
18531
18531
|
const interval = options.pollIntervalMs ?? AUTO_CALC_POLL_INTERVAL_MS;
|
|
18532
18532
|
const timeout = options.timeoutMs ?? AUTO_CALC_POLL_TIMEOUT_MS;
|
|
18533
|
-
const
|
|
18533
|
+
const sleep3 = options.sleep ?? defaultSleep3;
|
|
18534
18534
|
const now = options.now ?? Date.now;
|
|
18535
18535
|
const deadline = now() + timeout;
|
|
18536
18536
|
for (; ; ) {
|
|
@@ -18558,7 +18558,7 @@ async function pollDocumentJob(client, basePath, label, requestId, options = {})
|
|
|
18558
18558
|
`${label} polling timed out after ${timeout}ms (request ${requestId} still processing)`
|
|
18559
18559
|
);
|
|
18560
18560
|
}
|
|
18561
|
-
await
|
|
18561
|
+
await sleep3(interval, options.signal);
|
|
18562
18562
|
}
|
|
18563
18563
|
}
|
|
18564
18564
|
|
|
@@ -21783,7 +21783,7 @@ async function waitForReAggregation(cfg) {
|
|
|
21783
21783
|
let interval = cfg.pollIntervalMs ?? REAGG_POLL_INTERVAL_MS;
|
|
21784
21784
|
const maxInterval = cfg.maxPollIntervalMs ?? REAGG_POLL_MAX_INTERVAL_MS;
|
|
21785
21785
|
const timeout = cfg.timeoutMs ?? REAGG_POLL_TIMEOUT_MS;
|
|
21786
|
-
const
|
|
21786
|
+
const sleep3 = cfg.sleep ?? defaultSleep4;
|
|
21787
21787
|
const now = cfg.now ?? Date.now;
|
|
21788
21788
|
const accepted = [...cfg.accepted];
|
|
21789
21789
|
const observedProcessing = /* @__PURE__ */ new Set();
|
|
@@ -21818,7 +21818,7 @@ async function waitForReAggregation(cfg) {
|
|
|
21818
21818
|
break;
|
|
21819
21819
|
}
|
|
21820
21820
|
try {
|
|
21821
|
-
await
|
|
21821
|
+
await sleep3(interval, cfg.signal);
|
|
21822
21822
|
} catch {
|
|
21823
21823
|
aborted = true;
|
|
21824
21824
|
break;
|
|
@@ -24425,7 +24425,7 @@ async function waitForSuggestionRun(r, cfg, runId) {
|
|
|
24425
24425
|
let interval = cfg.pollIntervalMs ?? SUGGESTIONS_POLL_INTERVAL_MS;
|
|
24426
24426
|
const maxInterval = cfg.maxPollIntervalMs ?? SUGGESTIONS_POLL_MAX_INTERVAL_MS;
|
|
24427
24427
|
const timeout = cfg.timeoutMs ?? SUGGESTIONS_POLL_TIMEOUT_MS;
|
|
24428
|
-
const
|
|
24428
|
+
const sleep3 = cfg.sleep ?? defaultSleep5;
|
|
24429
24429
|
const now = cfg.now ?? Date.now;
|
|
24430
24430
|
const errOut = cfg.errOut ?? process.stderr;
|
|
24431
24431
|
const start = now();
|
|
@@ -24455,7 +24455,7 @@ async function waitForSuggestionRun(r, cfg, runId) {
|
|
|
24455
24455
|
break;
|
|
24456
24456
|
}
|
|
24457
24457
|
try {
|
|
24458
|
-
await
|
|
24458
|
+
await sleep3(interval, cfg.signal);
|
|
24459
24459
|
} catch {
|
|
24460
24460
|
aborted = true;
|
|
24461
24461
|
break;
|
|
@@ -30451,9 +30451,6 @@ function registerSkillCommand(root) {
|
|
|
30451
30451
|
});
|
|
30452
30452
|
}
|
|
30453
30453
|
|
|
30454
|
-
// src/commands/datamappers.ts
|
|
30455
|
-
import { setTimeout as sleep3 } from "timers/promises";
|
|
30456
|
-
|
|
30457
30454
|
// src/api/datamappers.ts
|
|
30458
30455
|
async function list20(client, tableId, options) {
|
|
30459
30456
|
const out = await client.getJSON(`/api/templates/${tableId}/mappers`, options);
|
|
@@ -30487,14 +30484,6 @@ async function create12(client, tableId, body, options) {
|
|
|
30487
30484
|
}
|
|
30488
30485
|
return out;
|
|
30489
30486
|
}
|
|
30490
|
-
async function update8(client, id, body, options) {
|
|
30491
|
-
const path11 = `/api/templates/mappers/${id}`;
|
|
30492
|
-
const out = await client.putJSON(path11, body, options);
|
|
30493
|
-
if (out === null) {
|
|
30494
|
-
throw new Error(`PUT ${path11}: empty response from datamapper update`);
|
|
30495
|
-
}
|
|
30496
|
-
return out;
|
|
30497
|
-
}
|
|
30498
30487
|
async function deleteById12(client, tableId, id, options) {
|
|
30499
30488
|
await client.deleteJSON(`/api/templates/${tableId}/mappers/${id}`, options);
|
|
30500
30489
|
}
|
|
@@ -31218,34 +31207,69 @@ async function runDatamappersUpdate(cfg) {
|
|
|
31218
31207
|
"merge"
|
|
31219
31208
|
);
|
|
31220
31209
|
}
|
|
31210
|
+
const err = cfg.err ?? process.stderr;
|
|
31221
31211
|
const { decoded: decodedBody, changeCount } = decodeBodyEntitiesDeep(body);
|
|
31222
31212
|
if (changeCount > 0) {
|
|
31223
|
-
|
|
31224
|
-
`note: decoded ${changeCount} HTML entit${changeCount === 1 ? "y" : "ies"} (" \u2192 ", & \u2192 &, < \u2192 <, > \u2192 >) in the request body \u2014 mirrors the legacy webclient's ajaxService.success post-processing; the server's GET response carries the entities and the
|
|
31213
|
+
err.write(
|
|
31214
|
+
`note: decoded ${changeCount} HTML entit${changeCount === 1 ? "y" : "ies"} (" \u2192 ", & \u2192 &, < \u2192 <, > \u2192 >) in the request body \u2014 mirrors the legacy webclient's ajaxService.success post-processing; the server's GET response carries the entities and the mapper compiler doesn't decode them.
|
|
31225
31215
|
`
|
|
31226
31216
|
);
|
|
31227
31217
|
body = decodedBody;
|
|
31228
31218
|
}
|
|
31229
31219
|
validateCalcFields(extractCalcFields(body));
|
|
31230
|
-
const
|
|
31231
|
-
const
|
|
31232
|
-
|
|
31220
|
+
const details = await getDetails2(cfg.client, cfg.id, { signal: cfg.signal });
|
|
31221
|
+
const templateId = details.template.id;
|
|
31222
|
+
const payloadVersion = body["document_version_id"];
|
|
31223
|
+
if (payloadVersion != null && !(Number.isInteger(payloadVersion) && payloadVersion > 0)) {
|
|
31224
|
+
throw new Error(
|
|
31225
|
+
`payload document_version_id must be a positive integer, got ${JSON.stringify(payloadVersion)}`
|
|
31226
|
+
);
|
|
31227
|
+
}
|
|
31228
|
+
const versionId = payloadVersion ?? details.from_version;
|
|
31229
|
+
if (typeof versionId !== "number") {
|
|
31230
|
+
throw new Error(
|
|
31231
|
+
`mapper ${cfg.id} has no source document version (typical after an org import or template push). Add "document_version_id" to the payload: an ACTIVE version of one of its documents (\`dr filebox versions <filebox_id>\`), with a sheet_name that exists in it`
|
|
31232
|
+
);
|
|
31233
|
+
}
|
|
31234
|
+
const current = await get14(cfg.client, templateId, cfg.id, { signal: cfg.signal });
|
|
31235
|
+
for (const key of ["id", "mapped_documents_ids", "previous_version", "mapping_date"]) {
|
|
31236
|
+
delete body[key];
|
|
31237
|
+
}
|
|
31238
|
+
const boundDocs = current.mapped_documents_ids ?? [];
|
|
31239
|
+
const payloadDocs = body["selected_document_ids"];
|
|
31240
|
+
const hasPayloadDocs = Array.isArray(payloadDocs) && payloadDocs.length > 0;
|
|
31241
|
+
if (boundDocs.length === 0 && !hasPayloadDocs) {
|
|
31242
|
+
throw new Error(
|
|
31243
|
+
`mapper ${cfg.id} is bound to no documents, so it is most likely an older version that an update or a UI publish replaced. Find the current one with \`dr datamappers list --table ${templateId}\`, or name the documents in the payload with "selected_document_ids"`
|
|
31244
|
+
);
|
|
31245
|
+
}
|
|
31246
|
+
body = {
|
|
31247
|
+
// GET returns the name HTML-escaped; sent back as is, it would be stored escaped twice.
|
|
31248
|
+
name: decodeHtmlEntitiesDeep(details.name),
|
|
31249
|
+
ordinal: current.ordinal,
|
|
31250
|
+
...body,
|
|
31251
|
+
// Pinned after the spread: a null version or an empty doc list in the
|
|
31252
|
+
// payload would otherwise publish a version bound to nothing.
|
|
31253
|
+
document_version_id: versionId,
|
|
31254
|
+
selected_document_ids: hasPayloadDocs ? payloadDocs : boundDocs,
|
|
31255
|
+
datamapper_id: cfg.id
|
|
31256
|
+
};
|
|
31257
|
+
const publishPath = `/api/templates/${templateId}/mappers`;
|
|
31258
|
+
let published;
|
|
31233
31259
|
try {
|
|
31234
|
-
|
|
31235
|
-
signal: cfg.signal
|
|
31236
|
-
});
|
|
31260
|
+
published = await create12(cfg.client, templateId, body, { signal: cfg.signal });
|
|
31237
31261
|
} catch (err2) {
|
|
31238
31262
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
31239
31263
|
if (/HTTP 500/.test(msg)) {
|
|
31240
|
-
const hint = "hint: the server logs an `ETLError: Custom Columns: The creation of '<field>' field has failed due to incorrect formula syntax.` line for this case but the response body is a bare 500 HTML page. Common causes the client-side guard cannot detect: (1) a [field] reference in a calc formula that does not resolve to any header `map_to`, calc-field `name`, or system field (`[FileBox_ID]`, `[System_Year]`, `[Posting Date]`, \u2026), (2) an unsupported function name (try copying the function spelling from another working calc field via `dr datamappers get`), (3) a string literal that's a plain-English placeholder (e.g. `\"Customer or Client name\"`) which parses but the ETL refuses. Iterate on calc-field formulas one at a time.
|
|
31264
|
+
const hint = "hint: the server logs an `ETLError: Custom Columns: The creation of '<field>' field has failed due to incorrect formula syntax.` line for this case but the response body is a bare 500 HTML page. Common causes the client-side guard cannot detect: (1) a [field] reference in a calc formula that does not resolve to any header `map_to`, calc-field `name`, or system field (`[FileBox_ID]`, `[System_Year]`, `[Posting Date]`, \u2026), (2) an unsupported function name (try copying the function spelling from another working calc field via `dr datamappers get`), (3) a string literal that's a plain-English placeholder (e.g. `\"Customer or Client name\"`) which parses but the ETL refuses. Iterate on calc-field formulas one at a time.";
|
|
31241
31265
|
const responseBody = extractResponseBodyFromError(msg);
|
|
31242
31266
|
let logPath;
|
|
31243
31267
|
try {
|
|
31244
31268
|
logPath = await writeErrorLog({
|
|
31245
31269
|
command: `dr datamappers update ${cfg.id} --payload-file ${payloadFile}`,
|
|
31246
31270
|
server: cfg.serverFlag ?? "",
|
|
31247
|
-
method: "
|
|
31248
|
-
url:
|
|
31271
|
+
method: "POST",
|
|
31272
|
+
url: publishPath,
|
|
31249
31273
|
status: 500,
|
|
31250
31274
|
requestBody: body,
|
|
31251
31275
|
responseBody,
|
|
@@ -31264,103 +31288,26 @@ ${hint}${logSuffix}`);
|
|
|
31264
31288
|
throw err2;
|
|
31265
31289
|
}
|
|
31266
31290
|
if (cfg.asJson) {
|
|
31267
|
-
writeJSONIndented(cfg.out,
|
|
31291
|
+
writeJSONIndented(cfg.out, published);
|
|
31268
31292
|
} else {
|
|
31269
|
-
renderMappersTable(cfg.out, [
|
|
31270
|
-
}
|
|
31271
|
-
if (scanBaseline !== void 0) {
|
|
31272
|
-
const opts = cfg.verifyScanDispatch ?? {};
|
|
31273
|
-
const started2 = await waitForNewScan(cfg.client, scanBaseline, cfg.signal, opts);
|
|
31274
|
-
if (!started2) {
|
|
31275
|
-
const docIds = await mappedDocumentIds(
|
|
31276
|
-
cfg.client,
|
|
31277
|
-
scanBaseline.templateId,
|
|
31278
|
-
cfg.id,
|
|
31279
|
-
cfg.signal
|
|
31280
|
-
);
|
|
31281
|
-
err.write(
|
|
31282
|
-
formatNoScanWarning({
|
|
31283
|
-
mapperId: cfg.id,
|
|
31284
|
-
templateId: scanBaseline.templateId,
|
|
31285
|
-
timeoutMs: opts.timeoutMs ?? DEFAULT_SCAN_DISPATCH_TIMEOUT_MS,
|
|
31286
|
-
docIds
|
|
31287
|
-
})
|
|
31288
|
-
);
|
|
31289
|
-
}
|
|
31290
|
-
}
|
|
31291
|
-
}
|
|
31292
|
-
var DEFAULT_SCAN_DISPATCH_TIMEOUT_MS = 3e4;
|
|
31293
|
-
var DEFAULT_SCAN_DISPATCH_POLL_MS = 2e3;
|
|
31294
|
-
function scanFingerprint(report) {
|
|
31295
|
-
let maxScanId = 0;
|
|
31296
|
-
for (const doc of report.documents ?? []) {
|
|
31297
|
-
for (const v of doc.versions ?? []) {
|
|
31298
|
-
if (typeof v.scan_id === "number" && v.scan_id > maxScanId) maxScanId = v.scan_id;
|
|
31299
|
-
}
|
|
31300
|
-
}
|
|
31301
|
-
return `${report.scans_status?.last_updated ?? ""}|${maxScanId}`;
|
|
31302
|
-
}
|
|
31303
|
-
async function captureScanBaseline(client, mapperId, signal, err) {
|
|
31304
|
-
try {
|
|
31305
|
-
const details = await getDetails2(client, mapperId, { signal });
|
|
31306
|
-
const templateId = details.template.id;
|
|
31307
|
-
const report = await getStatus(client, templateId, { signal });
|
|
31308
|
-
return { templateId, fingerprint: scanFingerprint(report) };
|
|
31309
|
-
} catch (e) {
|
|
31310
|
-
if (signal.aborted) throw e;
|
|
31311
|
-
err.write(
|
|
31312
|
-
"note: could not read the scan status before the update, so dr cannot confirm a rescan starts. Check with `dr scans status <template_id>` afterwards.\n"
|
|
31313
|
-
);
|
|
31314
|
-
return void 0;
|
|
31315
|
-
}
|
|
31316
|
-
}
|
|
31317
|
-
async function waitForNewScan(client, baseline, signal, opts) {
|
|
31318
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_SCAN_DISPATCH_TIMEOUT_MS;
|
|
31319
|
-
const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_SCAN_DISPATCH_POLL_MS;
|
|
31320
|
-
const deadline = Date.now() + timeoutMs;
|
|
31321
|
-
for (; ; ) {
|
|
31322
|
-
try {
|
|
31323
|
-
const report = await getStatus(client, baseline.templateId, { signal });
|
|
31324
|
-
if (scanFingerprint(report) !== baseline.fingerprint) return true;
|
|
31325
|
-
} catch (e) {
|
|
31326
|
-
if (signal.aborted) throw e;
|
|
31327
|
-
}
|
|
31328
|
-
const remaining = deadline - Date.now();
|
|
31329
|
-
if (remaining <= 0) return false;
|
|
31330
|
-
await sleep3(Math.min(pollIntervalMs, remaining), void 0, { signal });
|
|
31293
|
+
renderMappersTable(cfg.out, [published]);
|
|
31331
31294
|
}
|
|
31332
|
-
|
|
31333
|
-
|
|
31334
|
-
|
|
31335
|
-
|
|
31336
|
-
|
|
31337
|
-
|
|
31338
|
-
|
|
31339
|
-
|
|
31340
|
-
|
|
31341
|
-
|
|
31342
|
-
|
|
31343
|
-
|
|
31344
|
-
const lines = [
|
|
31345
|
-
`warning: mapper ${p.mapperId} was saved, but no new scan showed up on template ${p.templateId} within ${secs}s.`,
|
|
31346
|
-
' Either the scan is still queued, or this server predates the DR-54200 fix and ignored rescan="latest", so existing data still has the old config.',
|
|
31347
|
-
` Check first: dr scans status ${p.templateId}`
|
|
31348
|
-
];
|
|
31349
|
-
if (p.docIds !== void 0 && p.docIds.length === 0) {
|
|
31350
|
-
lines.push(" The mapper has no bound documents, so there is nothing to scan.");
|
|
31351
|
-
} else {
|
|
31352
|
-
const docFlags = p.docIds !== void 0 ? p.docIds.map((d) => `--doc ${d}`).join(" ") : "--doc <doc_id> [--doc <doc_id> ...]";
|
|
31353
|
-
lines.push(
|
|
31354
|
-
" If no new scan appears there, re-bind the mapper to its current documents with a rescan:"
|
|
31295
|
+
err.write(
|
|
31296
|
+
`note: mapper ${cfg.id} was published as new version ${published.id} on template ${templateId}; use ${published.id} for later updates.
|
|
31297
|
+
`
|
|
31298
|
+
);
|
|
31299
|
+
const declaredCalcNames = extractCalcFields(body).map((c) => c.name).filter((n) => n !== "");
|
|
31300
|
+
if (declaredCalcNames.length > 0 && rescanValue !== "") {
|
|
31301
|
+
const result = await verifyCalcFieldsMaterialise(
|
|
31302
|
+
cfg.client,
|
|
31303
|
+
templateId,
|
|
31304
|
+
declaredCalcNames,
|
|
31305
|
+
cfg.signal,
|
|
31306
|
+
cfg.verifyCalcFieldsOpts
|
|
31355
31307
|
);
|
|
31356
|
-
|
|
31357
|
-
|
|
31358
|
-
` dr datamappers get ${p.mapperId} --table ${p.templateId} --json # read mapped_documents_ids`
|
|
31359
|
-
);
|
|
31360
|
-
}
|
|
31361
|
-
lines.push(` dr datamappers bind ${p.mapperId} ${docFlags} --rescan latest`);
|
|
31308
|
+
const timeoutMs = cfg.verifyCalcFieldsOpts?.timeoutMs ?? 3e4;
|
|
31309
|
+
err.write(formatVerifyResult(templateId, result, timeoutMs));
|
|
31362
31310
|
}
|
|
31363
|
-
return lines.join("\n") + "\n";
|
|
31364
31311
|
}
|
|
31365
31312
|
async function buildConveniencePayload(client, tableId, name, versionId, sheet, flags, signal) {
|
|
31366
31313
|
const fieldTypes = parseFieldOverrides(flags.fieldOverrides);
|
|
@@ -31973,8 +31920,12 @@ function registerDatamappersCommand(root) {
|
|
|
31973
31920
|
addServerFlag(fromFileCmd);
|
|
31974
31921
|
addAccountFlag(fromFileCmd);
|
|
31975
31922
|
const updateCmd = dm.command("update").description(
|
|
31976
|
-
|
|
31977
|
-
).argument("<mapper_id>", "mapper id (positive integer)").option(
|
|
31923
|
+
'Update a datamapper (payload-file only: config + sheet_name, optionally name). Publishes the payload as a new version of the mapper, the way the UI "Publish and scan" does: new columns are added to the table, the new version takes over the documents of the old one, and the old version is unbound. The new version has a NEW mapper id, which dr prints. `--rescan latest` (the default) re-extracts the LATEST version of each bound document, then dr checks that any calculated fields in the payload appear on the table. For a full historical rescan (every calculated field re-executed against every document version ever uploaded), use `dr rescan table <template_id>` instead \u2014 that is far more expensive and higher-blast.'
|
|
31924
|
+
).argument("<mapper_id>", "mapper id (positive integer)").option(
|
|
31925
|
+
"--payload-file <path>",
|
|
31926
|
+
"Path to a JSON file with the mapper body: config, sheet_name, optional name (required)",
|
|
31927
|
+
""
|
|
31928
|
+
).option(
|
|
31978
31929
|
"--field <NAME:TYPE>",
|
|
31979
31930
|
'Set a column type via fields_to_change (Text|Float|Date|Integer). Repeatable. Mirrors the UI "Publish and scan" so a calc column referencing a LUT-injected column materialises instead of 500ing on inferred types. Note: the server blocks a type change (HTTP 400) when the template has more than one mapper.',
|
|
31980
31931
|
collectArrayOpt,
|
|
@@ -31996,8 +31947,7 @@ function registerDatamappersCommand(root) {
|
|
|
31996
31947
|
serverFlag: opts.server ?? "",
|
|
31997
31948
|
asJson: opts.json === true,
|
|
31998
31949
|
signal: setup.signal,
|
|
31999
|
-
out: process.stdout
|
|
32000
|
-
verifyScanDispatch: {}
|
|
31950
|
+
out: process.stdout
|
|
32001
31951
|
});
|
|
32002
31952
|
} finally {
|
|
32003
31953
|
setup.cleanup();
|
|
@@ -33732,7 +33682,7 @@ async function create15(client, body, reqOptions) {
|
|
|
33732
33682
|
}
|
|
33733
33683
|
return out;
|
|
33734
33684
|
}
|
|
33735
|
-
async function
|
|
33685
|
+
async function update8(client, id, body, reqOptions) {
|
|
33736
33686
|
const path11 = `/api/v3/data_integrations/${id}/`;
|
|
33737
33687
|
const out = await client.putJSON(path11, body, reqOptions);
|
|
33738
33688
|
if (out === null) {
|
|
@@ -35510,7 +35460,7 @@ var defaultSleep6 = (ms, signal) => new Promise((resolve3, reject) => {
|
|
|
35510
35460
|
async function waitForDataQuerySync(cfg) {
|
|
35511
35461
|
let interval = cfg.pollIntervalMs ?? DQ_WAIT_POLL_INTERVAL_MS;
|
|
35512
35462
|
const maxInterval = cfg.maxPollIntervalMs ?? DQ_WAIT_POLL_MAX_INTERVAL_MS;
|
|
35513
|
-
const
|
|
35463
|
+
const sleep3 = cfg.sleep ?? defaultSleep6;
|
|
35514
35464
|
const now = cfg.now ?? Date.now;
|
|
35515
35465
|
const start = now();
|
|
35516
35466
|
const deadline = start + cfg.timeoutSec * 1e3;
|
|
@@ -35552,7 +35502,7 @@ async function waitForDataQuerySync(cfg) {
|
|
|
35552
35502
|
break;
|
|
35553
35503
|
}
|
|
35554
35504
|
try {
|
|
35555
|
-
await
|
|
35505
|
+
await sleep3(interval, cfg.signal);
|
|
35556
35506
|
} catch {
|
|
35557
35507
|
outcome = "cancelled";
|
|
35558
35508
|
break;
|
|
@@ -36863,7 +36813,7 @@ async function runFileboxSetVersionFileStatus(cfg) {
|
|
|
36863
36813
|
throw new Error("version id must be positive");
|
|
36864
36814
|
}
|
|
36865
36815
|
const err = cfg.err ?? process.stderr;
|
|
36866
|
-
const
|
|
36816
|
+
const sleep3 = cfg.sleep ?? defaultCycleTagSleep;
|
|
36867
36817
|
const settleMs = cfg.tagSettleDelayMs ?? CYCLE_TAG_SETTLE_DELAY_MS;
|
|
36868
36818
|
const details = await getDetails(cfg.client, cfg.docId, { signal: cfg.signal });
|
|
36869
36819
|
const tag = (details.tags_config ?? []).find(isFileStatusTag);
|
|
@@ -36956,7 +36906,7 @@ async function runFileboxSetVersionFileStatus(cfg) {
|
|
|
36956
36906
|
}
|
|
36957
36907
|
};
|
|
36958
36908
|
const readBack = async () => {
|
|
36959
|
-
await
|
|
36909
|
+
await sleep3(settleMs, cfg.signal);
|
|
36960
36910
|
return readVersionForConfirm(cfg.client, cfg.versionId, cfg.docId, cfg.signal);
|
|
36961
36911
|
};
|
|
36962
36912
|
await write();
|
|
@@ -39291,7 +39241,7 @@ async function runIntegrationsSync(cfg) {
|
|
|
39291
39241
|
throw new Error("integration id must be positive");
|
|
39292
39242
|
}
|
|
39293
39243
|
const interval = cfg.pollIntervalMs ?? INTEGRATIONS_SYNC_POLL_INTERVAL_MS;
|
|
39294
|
-
const
|
|
39244
|
+
const sleep3 = cfg.sleep ?? defaultSleep7;
|
|
39295
39245
|
const resp = await resync(cfg.client, cfg.id, {
|
|
39296
39246
|
signal: cfg.signal
|
|
39297
39247
|
});
|
|
@@ -39340,7 +39290,7 @@ async function runIntegrationsSync(cfg) {
|
|
|
39340
39290
|
return;
|
|
39341
39291
|
}
|
|
39342
39292
|
try {
|
|
39343
|
-
await
|
|
39293
|
+
await sleep3(interval, inner.signal);
|
|
39344
39294
|
} catch {
|
|
39345
39295
|
cfg.out.write(
|
|
39346
39296
|
`Timed out waiting for sync to finish \u2014 last status was ${orDash2(lastStatus)}
|
|
@@ -39430,7 +39380,7 @@ async function runIntegrationsUpdate(cfg) {
|
|
|
39430
39380
|
throw new Error("--payload-file is required");
|
|
39431
39381
|
}
|
|
39432
39382
|
const body = await readJsonPayload(pf);
|
|
39433
|
-
const updated = await
|
|
39383
|
+
const updated = await update8(cfg.client, cfg.id, body, {
|
|
39434
39384
|
signal: cfg.signal
|
|
39435
39385
|
});
|
|
39436
39386
|
if (cfg.asJson) {
|
|
@@ -57217,7 +57167,7 @@ async function create16(client, body, options) {
|
|
|
57217
57167
|
}
|
|
57218
57168
|
return out;
|
|
57219
57169
|
}
|
|
57220
|
-
async function
|
|
57170
|
+
async function update9(client, id, body, options) {
|
|
57221
57171
|
const out = await client.putJSON(`/api/templates/${id}/calcmodels`, body, options);
|
|
57222
57172
|
if (out === null) {
|
|
57223
57173
|
throw new Error(
|
|
@@ -57314,7 +57264,7 @@ async function runCalcmodelsUpdate(cfg) {
|
|
|
57314
57264
|
);
|
|
57315
57265
|
}
|
|
57316
57266
|
validateCalcmodelBody(body);
|
|
57317
|
-
const updated = await
|
|
57267
|
+
const updated = await update9(cfg.client, cfg.id, body, {
|
|
57318
57268
|
signal: cfg.signal
|
|
57319
57269
|
});
|
|
57320
57270
|
if (cfg.asJson) {
|
|
@@ -57361,7 +57311,7 @@ async function runCalcmodelsSetFlow(cfg) {
|
|
|
57361
57311
|
`new update_flow: ${flow}`
|
|
57362
57312
|
]),
|
|
57363
57313
|
execute: async () => {
|
|
57364
|
-
const updated = await
|
|
57314
|
+
const updated = await update9(
|
|
57365
57315
|
cfg.client,
|
|
57366
57316
|
cfg.id,
|
|
57367
57317
|
{ ...model, update_flow: flow },
|
|
@@ -57391,7 +57341,7 @@ async function runCalcmodelsRecalculate(cfg) {
|
|
|
57391
57341
|
`type: ${model.type}`
|
|
57392
57342
|
]),
|
|
57393
57343
|
execute: async () => {
|
|
57394
|
-
await
|
|
57344
|
+
await update9(cfg.client, cfg.id, model, { signal: cfg.signal });
|
|
57395
57345
|
cfg.out.write(`recalculation queued \u2014 check status via dr calcmodels get ${cfg.id}
|
|
57396
57346
|
`);
|
|
57397
57347
|
}
|
|
@@ -57657,7 +57607,7 @@ async function create17(client, templateId, body, options) {
|
|
|
57657
57607
|
}
|
|
57658
57608
|
return out;
|
|
57659
57609
|
}
|
|
57660
|
-
async function
|
|
57610
|
+
async function update10(client, templateId, calcId, body, options) {
|
|
57661
57611
|
const out = await client.putJSON(
|
|
57662
57612
|
`/api/templates/${templateId}/calc_vals/${calcId}`,
|
|
57663
57613
|
body,
|
|
@@ -57784,7 +57734,7 @@ async function runCalcValsUpdate(cfg) {
|
|
|
57784
57734
|
`--file body.id (${String(bodyId)}) does not match URL id (${cfg.id}) \u2014 refusing to overwrite calc value #${cfg.id} with another value's body. Either fix the body's id field or point --file at the right file.`
|
|
57785
57735
|
);
|
|
57786
57736
|
}
|
|
57787
|
-
updated = await
|
|
57737
|
+
updated = await update10(cfg.client, cfg.templateId, cfg.id, body, {
|
|
57788
57738
|
signal: cfg.signal
|
|
57789
57739
|
});
|
|
57790
57740
|
} else {
|
|
@@ -57799,7 +57749,7 @@ async function runCalcValsUpdate(cfg) {
|
|
|
57799
57749
|
// `--format ""` clears the mask (→ null); otherwise keep existing.
|
|
57800
57750
|
format: cfg.format !== void 0 ? cfg.format === "" ? null : cfg.format : existing.format
|
|
57801
57751
|
};
|
|
57802
|
-
updated = await
|
|
57752
|
+
updated = await update10(cfg.client, cfg.templateId, cfg.id, body, {
|
|
57803
57753
|
signal: cfg.signal
|
|
57804
57754
|
});
|
|
57805
57755
|
}
|
|
@@ -60805,7 +60755,7 @@ async function defaultSleep8(ms, signal) {
|
|
|
60805
60755
|
async function pollExtractionStatus(client, contractFileIds, options = {}) {
|
|
60806
60756
|
const interval = options.pollIntervalMs ?? EXTRACT_POLL_INTERVAL_MS;
|
|
60807
60757
|
const timeout = options.timeoutMs ?? EXTRACT_POLL_TIMEOUT_MS;
|
|
60808
|
-
const
|
|
60758
|
+
const sleep3 = options.sleep ?? defaultSleep8;
|
|
60809
60759
|
const now = options.now ?? Date.now;
|
|
60810
60760
|
const resolved = /* @__PURE__ */ new Map();
|
|
60811
60761
|
if (contractFileIds.length === 0) return { resolved, pending: [] };
|
|
@@ -60855,7 +60805,7 @@ async function pollExtractionStatus(client, contractFileIds, options = {}) {
|
|
|
60855
60805
|
options.onTick?.(resolved.size, contractFileIds.length);
|
|
60856
60806
|
if (resolved.size >= contractFileIds.length) break;
|
|
60857
60807
|
if (now() > deadline || inner.signal.aborted) break;
|
|
60858
|
-
await
|
|
60808
|
+
await sleep3(interval, inner.signal);
|
|
60859
60809
|
}
|
|
60860
60810
|
} finally {
|
|
60861
60811
|
clearTimeout(deadlineTimer);
|
|
@@ -63712,7 +63662,7 @@ function registerSpendControlCommand(root) {
|
|
|
63712
63662
|
);
|
|
63713
63663
|
addServerFlag(bulkCreate);
|
|
63714
63664
|
addAccountFlag(bulkCreate);
|
|
63715
|
-
const
|
|
63665
|
+
const update11 = contracts.command("update").description("Partially update a contract by id (PATCH /contracts/{id})").argument("<contract_id>", "Contract UUID").requiredOption("--from <path>", "Path to JSON body matching ContractPartialUpdateSchema").option("--json", "Output raw JSON of the updated contract", false).action(async (contractId, opts) => {
|
|
63716
63666
|
const setup = await setupCall(opts.server, opts.account);
|
|
63717
63667
|
try {
|
|
63718
63668
|
await runContractUpdate({
|
|
@@ -63727,8 +63677,8 @@ function registerSpendControlCommand(root) {
|
|
|
63727
63677
|
setup.cleanup();
|
|
63728
63678
|
}
|
|
63729
63679
|
});
|
|
63730
|
-
addServerFlag(
|
|
63731
|
-
addAccountFlag(
|
|
63680
|
+
addServerFlag(update11);
|
|
63681
|
+
addAccountFlag(update11);
|
|
63732
63682
|
for (const spec of [
|
|
63733
63683
|
{
|
|
63734
63684
|
name: "archive",
|
package/dist/scopes-map.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"cliVersion": "1.6.
|
|
3
|
+
"cliVersion": "1.6.107",
|
|
4
4
|
"_contract": "Resolve a command path through `aliases` (canonical prefix + alias segment -> canonical path) before any lookup. A path listed in `pendingCommands`, or whose first segment is a key of `pending`, is UNDECIDED, not unrestricted: fail closed or skip it, and never read a path's absence from `commands` as 'no requirement'. An entry carrying `denied` is UNAVAILABLE to any AGENT-SCOPED credential — one whose `scopes` claim carries at least one `resource:action` entry; a claim that is absent, empty, capability-only or wholly unparseable is UNRESTRICTED, and this mark does not apply to it — whatever else that entry says: it is not a ceiling and it is not always-available, its `denied.reason` says why and `denied.until` names the ticket that lifts it, and the absence of `requires` on it must NEVER be read as 'no requirement'. Only an entry in `commands` with an empty `requires` means always-available.",
|
|
5
5
|
"commands": {
|
|
6
6
|
"login": {
|
|
@@ -556,8 +556,9 @@ returns the source rows behind the number.
|
|
|
556
556
|
counts — and a token; re-run with `--confirm-token` to fire it) → poll `scans
|
|
557
557
|
status` again. This is a **full historical** rescan (every calc field × every
|
|
558
558
|
version). To only pick up a datamapper change on the live template, prefer
|
|
559
|
-
`datamappers update <id> --rescan latest` (latest version of
|
|
560
|
-
documents only) — far cheaper and lower-blast.
|
|
559
|
+
`datamappers update <id> --payload-file <p> --rescan latest` (latest version of
|
|
560
|
+
the mapper's bound documents only) — far cheaper and lower-blast. It publishes a
|
|
561
|
+
new mapper version, so the mapper gets a **new id**, which dr prints.
|
|
561
562
|
|
|
562
563
|
**Build a `create` body from plain-English intent** (calc models, datamappers,
|
|
563
564
|
metrics): you resolve names → integer IDs yourself against the tenant
|
|
@@ -67,12 +67,23 @@ value-inference behavior. `NAME` is the target **table field** name (the
|
|
|
67
67
|
## Update / preview / bind / delete
|
|
68
68
|
|
|
69
69
|
- `datamappers update`, `datamappers delete` (two-step confirm).
|
|
70
|
-
- `datamappers update --
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
- `datamappers update <id> --payload-file <p>` publishes the payload as a **new
|
|
71
|
+
version** of the mapper, the same request the UI's "Publish and scan" sends
|
|
72
|
+
(`POST /api/templates/<tid>/mappers` with `datamapper_id` = the old id). The
|
|
73
|
+
server adds any new columns to the table, binds the new version to the old
|
|
74
|
+
one's documents, unbinds the old one, and rescans (`--rescan latest` by
|
|
75
|
+
default). The payload needs `config` and `sheet_name`; dr fills in `name`,
|
|
76
|
+
`ordinal`, the bound documents and the source version from the old mapper,
|
|
77
|
+
and any of those keys in the payload wins.
|
|
78
|
+
- **The mapper gets a new id.** dr prints it (`note: mapper <old> was published
|
|
79
|
+
as new version <new>`). Use the new id for the next update, and keep an
|
|
80
|
+
old→new log if anything outside the table refers to the mapper id.
|
|
81
|
+
`update` refuses an old id that is bound to no documents, and points at
|
|
82
|
+
`datamappers list --table <tid>` for the current one.
|
|
83
|
+
- A mapper made by an org import or template push has no source version on
|
|
84
|
+
record, and `update` stops with an error. Add `"document_version_id"` to the
|
|
85
|
+
payload: an ACTIVE version of one of its documents, with a `sheet_name` that
|
|
86
|
+
exists in it.
|
|
76
87
|
- `datamappers preview --payload-file <p>` — renders mapper output for sample
|
|
77
88
|
input ("what would this extract?") before saving.
|
|
78
89
|
- `datamappers bind <mapper_id> --doc <id> [--rescan latest|all]` — change which
|
|
@@ -82,15 +93,15 @@ value-inference behavior. `NAME` is the target **table field** name (the
|
|
|
82
93
|
|
|
83
94
|
`datamappers update <id> --payload-file <p> [--field NAME:TYPE ...]`. Repeatable;
|
|
84
95
|
`TYPE` is `Text|Float|Date|Integer`. Each `--field` becomes a `fields_to_change`
|
|
85
|
-
entry
|
|
96
|
+
entry in the body, telling the server to set that column's type **explicitly**
|
|
86
97
|
instead of inferring it — this is exactly what the web UI's "Publish and scan"
|
|
87
|
-
does (same `
|
|
88
|
-
is `fields_to_change`).
|
|
98
|
+
does (same `POST api/templates/<tid>/mappers` endpoint; the only payload
|
|
99
|
+
difference is `fields_to_change`).
|
|
89
100
|
|
|
90
101
|
**When you need it:** adding a calc column that multiplies/divides by a
|
|
91
102
|
**LUT-injected column**. LUT columns land as **Text**; the server infers Text for
|
|
92
103
|
them, so a formula like `[Posting Amount] * [Sign]` fails to materialise and the
|
|
93
|
-
|
|
104
|
+
publish returns a bare `HTTP 500` (`ETLError: … incorrect formula syntax`) — even
|
|
94
105
|
though the formula is valid. Fixing the LUT column's type up-front lets it
|
|
95
106
|
materialise:
|
|
96
107
|
|
|
@@ -120,12 +131,12 @@ valid formula the ETL compiler can't resolve (unknown/wrong-cased function,
|
|
|
120
131
|
unresolvable `[field]` reference, a string-constant that happens to be quoted)
|
|
121
132
|
still returns a bare 500.
|
|
122
133
|
|
|
123
|
-
After `create`, the CLI polls the template's `fields[]` for up to 30s for every
|
|
134
|
+
After `create` and `update`, the CLI polls the template's `fields[]` for up to 30s for every
|
|
124
135
|
declared calc field to materialise, and warns (pointing at `mapper-recipes get`
|
|
125
136
|
for working formulas) if any are missing — this catches the EOMONTH failure
|
|
126
137
|
mode. Skipped when `--no-rescan` is set.
|
|
127
138
|
|
|
128
|
-
On a
|
|
139
|
+
On a create or update 500, the CLI writes a `dr-cli-error-<timestamp>.md` to the cwd capturing
|
|
129
140
|
URL + status + request/response bodies; the path is named in the stderr error.
|
|
130
141
|
|
|
131
142
|
Formula syntax:
|
|
@@ -12,7 +12,7 @@ Write:
|
|
|
12
12
|
|
|
13
13
|
- `integrations add` (payload-file), `integrations sync`,
|
|
14
14
|
`integrations test-connection`, `integrations update`,
|
|
15
|
-
`integrations delete` (two-step confirm).
|
|
15
|
+
`integrations delete` (two-step confirm, Tier A, see below).
|
|
16
16
|
- `integrations revoke <id>` (two-step confirm; Tier A on prod) — Disconnect a
|
|
17
17
|
`DatarailsConnector` OAuth data source: `POST /api/v2/data_integrations/<id>/revoke`.
|
|
18
18
|
The grant is cleared locally first (`oauth_tokens`/`oauth_pending` dropped,
|
|
@@ -23,6 +23,38 @@ Write:
|
|
|
23
23
|
`revoked` may leave the grant live at the vendor. Legacy vendors are refused.
|
|
24
24
|
- `integrations share users` / `integrations share groups`.
|
|
25
25
|
|
|
26
|
+
### Deleting a data source (`integrations delete <id>`)
|
|
27
|
+
|
|
28
|
+
`DELETE /api/v3/data_integrations/<id>/`. It cannot be undone and **cascades to queries and
|
|
29
|
+
documents**. Typical use: removing a validation or staging datasource that holds live customer
|
|
30
|
+
credentials once its bindings are gone.
|
|
31
|
+
|
|
32
|
+
1. **Plan:** run it without a token, as the identity that owns the data source (on a staging org,
|
|
33
|
+
the impersonated staging user, never the operator):
|
|
34
|
+
`dr integrations delete <id> --server <S> --account <user>`. It prints `name`, `type`, `status`,
|
|
35
|
+
`org` (name + id) and `shared` (user/group counts), plus the exact confirm command with a token
|
|
36
|
+
that expires in **10 minutes** and is bound to this id and verb. Check the org id and the name
|
|
37
|
+
before going on: the plan does **not** list the queries and documents the cascade will remove.
|
|
38
|
+
Make sure nothing you want to keep still points at this data source.
|
|
39
|
+
2. **Relay and get a "yes"**: name, org and why it is being deleted.
|
|
40
|
+
3. **Confirm:** re-run the printed command with `--confirm-token <tok>`. On a prod tenant (`US`,
|
|
41
|
+
`US2`, `UK`, `CA`, `EU`, `STUDIO`, `DEMO`) this is **Tier A**: the binary shows a fresh word in a
|
|
42
|
+
desktop dialog (or a TTY prompt) and a human must type it. With no interactive channel it exits
|
|
43
|
+
non-zero; hand the printed line to the user rather than retrying. Success prints
|
|
44
|
+
`Deleted integration <id> — "<name>".`
|
|
45
|
+
4. **Verify:** `dr integrations get <id> --server <S> --account <user>` returns `HTTP 404`.
|
|
46
|
+
|
|
47
|
+
Notes:
|
|
48
|
+
|
|
49
|
+
- `dr connectors` `Connector` rows that reference the data source (`--secret-ref`) are **not**
|
|
50
|
+
deleted, and dr-cli has no connector delete. Delete the `dr connectors` bindings first
|
|
51
|
+
(`dr connectors query delete`), then record the orphaned connector id.
|
|
52
|
+
- A `403` (`delete refused: …`) means the identity can't delete it: switch to the owner's account,
|
|
53
|
+
not to the operator.
|
|
54
|
+
- Deleting a validation data source on the Workato-migration staging orgs left the staging fileboxes
|
|
55
|
+
in place (their data-query rows pointed at the data source). Check one with `dr filebox get` after
|
|
56
|
+
the delete if you need them as evidence.
|
|
57
|
+
|
|
26
58
|
## IT-admin file-sync (`integrations it`)
|
|
27
59
|
|
|
28
60
|
File-sync connectors only — OneDrive / SharePoint / Egnyte. Output redacts
|
|
@@ -149,8 +149,9 @@ except on `update`, which resolves the range's own table automatically.
|
|
|
149
149
|
the blast radius (document + version counts) and get a token, then re-run with
|
|
150
150
|
`--confirm-token` — token-only, no word gate even on prod (a rescan re-derives
|
|
151
151
|
data, it doesn't delete it). To pick up a datamapper change on the live template
|
|
152
|
-
**without** touching history, use `datamappers update <id> --
|
|
153
|
-
instead (re-extracts the latest version of the mapper's bound
|
|
152
|
+
**without** touching history, use `datamappers update <id> --payload-file <p>
|
|
153
|
+
--rescan latest` instead (re-extracts the latest version of the mapper's bound
|
|
154
|
+
documents; it publishes a new mapper version, so the mapper gets a new id); a full rescan will re-run pre-existing
|
|
154
155
|
calc fields against old rows, so a latent divide-by-null there fails the whole
|
|
155
156
|
scan. Refuses `UsageStats` on every tenant (system data, not customer-owned).
|
|
156
157
|
- `dataflow get` — the dependency graph: inputs → mappers → LUTs → table →
|
|
@@ -431,27 +431,20 @@ this path that advice does not hold.) The generator omits it.
|
|
|
431
431
|
`document_version_id` is required and is **not** in `dr datamappers get` output —
|
|
432
432
|
read it from the upload response or `dr filebox versions <filebox_id>`.
|
|
433
433
|
|
|
434
|
-
### Changing a mapper that already exists: remap
|
|
434
|
+
### Changing a mapper that already exists: remap
|
|
435
435
|
|
|
436
436
|
Widening an existing feed mapper, or adding reporting-amount columns to a business
|
|
437
|
-
table's mappers, means editing mappers that already exist. `dr datamappers update
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
fails with a bare HTTP 500. The server log shows `AttributeError: 'NoneType' object has
|
|
442
|
-
no attribute 'get_document_data'`. No request body fixes it, because the endpoint never
|
|
443
|
-
reads a version from the body.
|
|
437
|
+
table's mappers, means editing mappers that already exist. `dr datamappers update
|
|
438
|
+
<id> --payload-file <p>` does this as a remap: it publishes the edited config as a new
|
|
439
|
+
version of the mapper, the way the UI's "Publish and scan" does, and fills in `name`,
|
|
440
|
+
`ordinal`, the bound documents and the source version from the old mapper.
|
|
444
441
|
|
|
445
|
-
|
|
446
|
-
|
|
442
|
+
A mapper created by an org import or template push has no source version on record,
|
|
443
|
+
and `update` stops with an error saying so. Put a version in the payload:
|
|
447
444
|
|
|
448
445
|
```json
|
|
449
446
|
{
|
|
450
|
-
"name": "<existing name>",
|
|
451
|
-
"datamapper_id": <existing mapper id>,
|
|
452
|
-
"ordinal": <existing ordinal>,
|
|
453
447
|
"document_version_id": <an ACTIVE version of one of its documents>,
|
|
454
|
-
"selected_document_ids": [<its mapped_documents_ids>],
|
|
455
448
|
"sheet_name": "<a sheet that exists in THAT version>",
|
|
456
449
|
"config": { ... the edited config ... }
|
|
457
450
|
}
|
|
@@ -459,7 +452,8 @@ mapper's id replaces it in place and records a real source version:
|
|
|
459
452
|
|
|
460
453
|
- **The mapper gets a new id.** The old id stays behind bound to no documents, and
|
|
461
454
|
lookups, which live on the table, are unaffected. Keep an old→new id log for rollback.
|
|
462
|
-
- **`ordinal` must be the existing one.**
|
|
455
|
+
- **`ordinal` must be the existing one.** `update` sends it for you; a lower one in the
|
|
456
|
+
payload is refused as a duplicate mapper.
|
|
463
457
|
- **`sheet_name` must exist in the version you pass**, and a mapper's
|
|
464
458
|
`preview_sheet_name` is often stale. Check with `dr filebox sheets <version_id>`; when
|
|
465
459
|
the preview sheet is gone and the version has one sheet, use that one.
|
|
@@ -754,7 +748,7 @@ the package layout reaches every producer.
|
|
|
754
748
|
| `#NAME?` on the label cell | `TEXT()` wrapped around `DR.MONTH()` | Step 4 |
|
|
755
749
|
| Self-rate is not exactly 1 | ratio not anchored to the target row | Step 4 |
|
|
756
750
|
| Custom code rejected by the Tables dialog | never registered as an org currency | Step 1 |
|
|
757
|
-
| `datamappers update
|
|
751
|
+
| `datamappers update`: mapper has no source document version | mapper made by an org import | `document_version_id` in the payload, Step 6 |
|
|
758
752
|
| Remap 500s: `Cannot read sheet with name …` | chosen version is in `ERROR` status | use the newest ACTIVE version, Step 6 |
|
|
759
753
|
| New lookup columns empty on every target row | lookup built while the FX table was still scanning | rebuild the lookup, Step 7 |
|
|
760
754
|
| Opening balances have no currency amounts | calc-model rows skip the mappers | **Step 7c** |
|
|
@@ -152,8 +152,11 @@ goes merge → new vN → §3 → `dr connectors query repoint` → re-sync. Cut
|
|
|
152
152
|
|
|
153
153
|
The staging datasource holds **live customer credentials**, so remove it right away:
|
|
154
154
|
1. `dr connectors query list` → `dr connectors query delete <queryId>` for each binding.
|
|
155
|
-
2. `dr integrations delete <ds
|
|
156
|
-
|
|
155
|
+
2. `dr integrations delete <ds> $STG` prints a plan and a confirm token. Re-run it with
|
|
156
|
+
`--confirm-token <tok>`. Prod is Tier A, so a human types the word shown in the desktop dialog; with
|
|
157
|
+
no interactive channel, hand the line to the user. Verify with `dr integrations get <ds> $STG` → 404.
|
|
158
|
+
dr-cli has no connector delete, so the orphaned `Connector` row points at a deleted datasource.
|
|
159
|
+
Record its id. (dr-cli skill, `reference/integrations.md`, "Deleting a data source".)
|
|
157
160
|
3. Keep the staging fileboxes as evidence until cut-over + ticket close, then delete the `<Customer>` tree.
|
|
158
161
|
4. Delete the local `wk/` and `st/` downloads: they are customer data. Keep only `report.md`.
|
|
159
162
|
5. Record the report, what was deleted and what was kept in the migration ticket.
|