@datarails-ai/dr-cli 1.6.105 → 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 +1001 -361
- package/dist/scopes-map.json +21 -1
- package/dist/skills/dr-cli/SKILL.md +8 -3
- 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/metrics.md +73 -3
- 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 +2 -2
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: {
|
|
@@ -40,7 +40,7 @@ var package_default = {
|
|
|
40
40
|
postinstall: "node scripts/postinstall.mjs"
|
|
41
41
|
},
|
|
42
42
|
dependencies: {
|
|
43
|
-
"@datarails-ai/dr-appspace-cli": "0.11.
|
|
43
|
+
"@datarails-ai/dr-appspace-cli": "0.11.3",
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
45
45
|
"@napi-rs/keyring": "^1.3.0",
|
|
46
46
|
commander: "^12.1.0",
|
|
@@ -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) {
|
|
@@ -5395,18 +5395,18 @@ async function openBrowser(url) {
|
|
|
5395
5395
|
// src/commands/_serverPicker.ts
|
|
5396
5396
|
async function readLine(stdin) {
|
|
5397
5397
|
return new Promise((resolve3, reject) => {
|
|
5398
|
-
let
|
|
5398
|
+
let data4 = "";
|
|
5399
5399
|
const onData = (chunk) => {
|
|
5400
|
-
|
|
5401
|
-
const i =
|
|
5400
|
+
data4 += chunk.toString();
|
|
5401
|
+
const i = data4.indexOf("\n");
|
|
5402
5402
|
if (i >= 0) {
|
|
5403
5403
|
cleanup();
|
|
5404
|
-
resolve3(
|
|
5404
|
+
resolve3(data4.slice(0, i + 1));
|
|
5405
5405
|
}
|
|
5406
5406
|
};
|
|
5407
5407
|
const onEnd = () => {
|
|
5408
5408
|
cleanup();
|
|
5409
|
-
resolve3(
|
|
5409
|
+
resolve3(data4);
|
|
5410
5410
|
};
|
|
5411
5411
|
const onError = (err) => {
|
|
5412
5412
|
cleanup();
|
|
@@ -5530,7 +5530,7 @@ async function readFirstLineFromPipe(stdin) {
|
|
|
5530
5530
|
stdin.setEncoding?.("utf8");
|
|
5531
5531
|
stdin.resume?.();
|
|
5532
5532
|
return new Promise((resolve3, reject) => {
|
|
5533
|
-
let
|
|
5533
|
+
let data4 = "";
|
|
5534
5534
|
let done = false;
|
|
5535
5535
|
const finish = (line) => {
|
|
5536
5536
|
if (done) return;
|
|
@@ -5539,11 +5539,11 @@ async function readFirstLineFromPipe(stdin) {
|
|
|
5539
5539
|
resolve3(line.endsWith("\r") ? line.slice(0, -1) : line);
|
|
5540
5540
|
};
|
|
5541
5541
|
const onData = (chunk) => {
|
|
5542
|
-
|
|
5543
|
-
const i =
|
|
5544
|
-
if (i >= 0) finish(
|
|
5542
|
+
data4 += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
5543
|
+
const i = data4.indexOf("\n");
|
|
5544
|
+
if (i >= 0) finish(data4.slice(0, i));
|
|
5545
5545
|
};
|
|
5546
|
-
const onEnd = () => finish(
|
|
5546
|
+
const onEnd = () => finish(data4);
|
|
5547
5547
|
const onError = (err) => {
|
|
5548
5548
|
if (done) return;
|
|
5549
5549
|
done = true;
|
|
@@ -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}`);
|
|
@@ -12711,25 +12711,25 @@ function buildAggregateBody(dimensions, metrics, filters) {
|
|
|
12711
12711
|
}
|
|
12712
12712
|
return body;
|
|
12713
12713
|
}
|
|
12714
|
-
function groupRows(
|
|
12715
|
-
if (!Array.isArray(
|
|
12716
|
-
return
|
|
12714
|
+
function groupRows(data4) {
|
|
12715
|
+
if (!Array.isArray(data4)) return data4;
|
|
12716
|
+
return data4.filter((r) => !(isPlainObject(r) && ("col_keys" in r || "row_keys" in r)));
|
|
12717
12717
|
}
|
|
12718
12718
|
function readTotals(metadata) {
|
|
12719
12719
|
if (!isPlainObject(metadata)) return null;
|
|
12720
12720
|
const totals = metadata["totals"];
|
|
12721
12721
|
return isPlainObject(totals) && Object.keys(totals).length > 0 ? totals : null;
|
|
12722
12722
|
}
|
|
12723
|
-
function asRowList(
|
|
12724
|
-
if (Array.isArray(
|
|
12725
|
-
if (
|
|
12726
|
-
return
|
|
12723
|
+
function asRowList(data4) {
|
|
12724
|
+
if (Array.isArray(data4)) {
|
|
12725
|
+
if (data4.every((x) => isPlainObject(x))) {
|
|
12726
|
+
return data4;
|
|
12727
12727
|
}
|
|
12728
12728
|
return null;
|
|
12729
12729
|
}
|
|
12730
|
-
if (isPlainObject(
|
|
12730
|
+
if (isPlainObject(data4)) {
|
|
12731
12731
|
for (const key of ["rows", "data", "results", "items"]) {
|
|
12732
|
-
const v =
|
|
12732
|
+
const v = data4[key];
|
|
12733
12733
|
if (Array.isArray(v) && v.every((x) => isPlainObject(x))) {
|
|
12734
12734
|
return v;
|
|
12735
12735
|
}
|
|
@@ -12762,10 +12762,10 @@ function writeTable5(out, rows) {
|
|
|
12762
12762
|
out.write(parts.join("") + "\n");
|
|
12763
12763
|
}
|
|
12764
12764
|
}
|
|
12765
|
-
function renderAggregateResult(out,
|
|
12766
|
-
const rows = asRowList(
|
|
12765
|
+
function renderAggregateResult(out, data4) {
|
|
12766
|
+
const rows = asRowList(data4);
|
|
12767
12767
|
if (rows === null) {
|
|
12768
|
-
out.write(JSON.stringify(
|
|
12768
|
+
out.write(JSON.stringify(data4, null, 2) + "\n");
|
|
12769
12769
|
return;
|
|
12770
12770
|
}
|
|
12771
12771
|
if (rows.length === 0) {
|
|
@@ -15481,6 +15481,108 @@ function registerDynamicRangesCommand(root) {
|
|
|
15481
15481
|
// src/commands/functions.ts
|
|
15482
15482
|
import { promises as fs11 } from "fs";
|
|
15483
15483
|
|
|
15484
|
+
// src/api/_cacheKeyPolling.ts
|
|
15485
|
+
var CacheKeyHttpError = class extends Error {
|
|
15486
|
+
status;
|
|
15487
|
+
path;
|
|
15488
|
+
bodyText;
|
|
15489
|
+
constructor(method, path11, status2, bodyText) {
|
|
15490
|
+
super(`${method} ${path11}: HTTP ${status2}: ${truncate5(bodyText, 200)}`);
|
|
15491
|
+
this.name = "CacheKeyHttpError";
|
|
15492
|
+
this.status = status2;
|
|
15493
|
+
this.path = path11;
|
|
15494
|
+
this.bodyText = bodyText;
|
|
15495
|
+
}
|
|
15496
|
+
};
|
|
15497
|
+
async function putOnce(client, path11, body, options) {
|
|
15498
|
+
const res = await client.do("PUT", path11, body, options);
|
|
15499
|
+
const text2 = await res.text();
|
|
15500
|
+
if (res.status !== 409 && (res.status < 200 || res.status >= 300)) {
|
|
15501
|
+
throw new CacheKeyHttpError("PUT", path11, res.status, text2);
|
|
15502
|
+
}
|
|
15503
|
+
if (text2 === "" || text2 === '""') {
|
|
15504
|
+
return { status: res.status, parsed: null };
|
|
15505
|
+
}
|
|
15506
|
+
try {
|
|
15507
|
+
return { status: res.status, parsed: JSON.parse(text2) };
|
|
15508
|
+
} catch (err) {
|
|
15509
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
15510
|
+
throw new Error(`PUT ${path11}: decode: ${msg}`);
|
|
15511
|
+
}
|
|
15512
|
+
}
|
|
15513
|
+
function extractCacheKey(body) {
|
|
15514
|
+
if (body === null || typeof body !== "object") return "";
|
|
15515
|
+
const top = body["cache_key"];
|
|
15516
|
+
if (typeof top === "string" && top !== "") return top;
|
|
15517
|
+
const detail = body["detail"];
|
|
15518
|
+
if (detail !== null && typeof detail === "object") {
|
|
15519
|
+
const nested = detail["cache_key"];
|
|
15520
|
+
if (typeof nested === "string" && nested !== "") return nested;
|
|
15521
|
+
}
|
|
15522
|
+
return "";
|
|
15523
|
+
}
|
|
15524
|
+
function withCacheKey(path11, key) {
|
|
15525
|
+
return `${path11}?cache_key=${encodeURIComponent(key)}`;
|
|
15526
|
+
}
|
|
15527
|
+
var CACHE_KEY_POLL_INTERVAL_MS = 500;
|
|
15528
|
+
var abortableSleep = (ms, signal) => new Promise((resolve3, reject) => {
|
|
15529
|
+
if (signal?.aborted) {
|
|
15530
|
+
reject(signal.reason ?? new Error("aborted"));
|
|
15531
|
+
return;
|
|
15532
|
+
}
|
|
15533
|
+
const onAbort = () => {
|
|
15534
|
+
clearTimeout(t);
|
|
15535
|
+
reject(signal?.reason ?? new Error("aborted"));
|
|
15536
|
+
};
|
|
15537
|
+
const t = setTimeout(() => {
|
|
15538
|
+
signal?.removeEventListener("abort", onAbort);
|
|
15539
|
+
resolve3();
|
|
15540
|
+
}, ms);
|
|
15541
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
15542
|
+
});
|
|
15543
|
+
async function putWithCacheKeyPolling(client, path11, payload, options = {}) {
|
|
15544
|
+
const interval = options.pollIntervalMs ?? CACHE_KEY_POLL_INTERVAL_MS;
|
|
15545
|
+
const sleep3 = options.sleep ?? abortableSleep;
|
|
15546
|
+
const label = options.label ?? "data";
|
|
15547
|
+
const body = JSON.stringify(payload);
|
|
15548
|
+
const first = await putOnce(client, path11, body, options);
|
|
15549
|
+
if (first.status !== 409) {
|
|
15550
|
+
return first.parsed;
|
|
15551
|
+
}
|
|
15552
|
+
const cacheKey = extractCacheKey(first.parsed);
|
|
15553
|
+
if (cacheKey === "") {
|
|
15554
|
+
throw new Error(
|
|
15555
|
+
`PUT ${path11}: 409 without cache_key: ${truncate5(JSON.stringify(first.parsed ?? ""), 200)}`
|
|
15556
|
+
);
|
|
15557
|
+
}
|
|
15558
|
+
const pollPath = withCacheKey(path11, cacheKey);
|
|
15559
|
+
for (; ; ) {
|
|
15560
|
+
try {
|
|
15561
|
+
await sleep3(interval, options.signal);
|
|
15562
|
+
} catch (err) {
|
|
15563
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
15564
|
+
throw new Error(`${label} polling timed out: ${reason}`);
|
|
15565
|
+
}
|
|
15566
|
+
let result;
|
|
15567
|
+
try {
|
|
15568
|
+
result = await putOnce(client, pollPath, body, options);
|
|
15569
|
+
} catch (err) {
|
|
15570
|
+
if (options.signal?.aborted) {
|
|
15571
|
+
const reason = options.signal.reason instanceof Error ? options.signal.reason.message : String(options.signal.reason ?? "aborted");
|
|
15572
|
+
throw new Error(`${label} polling timed out: ${reason}`);
|
|
15573
|
+
}
|
|
15574
|
+
throw err;
|
|
15575
|
+
}
|
|
15576
|
+
if (result.status !== 409) {
|
|
15577
|
+
return result.parsed;
|
|
15578
|
+
}
|
|
15579
|
+
}
|
|
15580
|
+
}
|
|
15581
|
+
function truncate5(s, n) {
|
|
15582
|
+
if (s.length <= n) return s;
|
|
15583
|
+
return s.slice(0, n) + "\u2026";
|
|
15584
|
+
}
|
|
15585
|
+
|
|
15484
15586
|
// src/api/functions.ts
|
|
15485
15587
|
function normalize(raw) {
|
|
15486
15588
|
return {
|
|
@@ -15524,6 +15626,16 @@ async function getDetail(client, id, options) {
|
|
|
15524
15626
|
default_agg_config: out.default_agg_config ?? null
|
|
15525
15627
|
};
|
|
15526
15628
|
}
|
|
15629
|
+
var DEFAULT_AGG_CONFIG = {
|
|
15630
|
+
YTD: { aggregate_by: "month", fnc: "sum", is_end_of_period: true },
|
|
15631
|
+
QTD: { aggregate_by: "month", fnc: "sum", is_end_of_period: true },
|
|
15632
|
+
RM: { rolling_month_before: 0, is_end_of_period: true },
|
|
15633
|
+
SA: { aggregate_by: "month", fnc: "sum", is_end_of_period: true },
|
|
15634
|
+
YTG: { aggregate_by: "month", fnc: "sum", is_end_of_period: true },
|
|
15635
|
+
QTG: { aggregate_by: "month", fnc: "sum", is_end_of_period: true },
|
|
15636
|
+
MTD: { aggregate_by: "month", fnc: "sum", is_end_of_period: true },
|
|
15637
|
+
MTG: { aggregate_by: "month", fnc: "sum", is_end_of_period: true }
|
|
15638
|
+
};
|
|
15527
15639
|
async function create6(client, args, options) {
|
|
15528
15640
|
const body = {
|
|
15529
15641
|
id: -1,
|
|
@@ -15534,6 +15646,10 @@ async function create6(client, args, options) {
|
|
|
15534
15646
|
calculated_values: [],
|
|
15535
15647
|
template: args.template
|
|
15536
15648
|
};
|
|
15649
|
+
if (args.dateField !== void 0) {
|
|
15650
|
+
body.default_agg_datefield = args.dateField;
|
|
15651
|
+
body.default_agg_config = DEFAULT_AGG_CONFIG;
|
|
15652
|
+
}
|
|
15537
15653
|
const out = await client.postJSON("/api/functions/", body, options);
|
|
15538
15654
|
if (out === null) {
|
|
15539
15655
|
return { id: 0, name: "", aggfun: "", value_field: 0, template: 0 };
|
|
@@ -15550,7 +15666,9 @@ async function getFull(client, id, options) {
|
|
|
15550
15666
|
value_field: 0,
|
|
15551
15667
|
template: 0,
|
|
15552
15668
|
calculated_values: [],
|
|
15553
|
-
is_fx_aware: false
|
|
15669
|
+
is_fx_aware: false,
|
|
15670
|
+
default_agg_datefield: null,
|
|
15671
|
+
default_agg_config: null
|
|
15554
15672
|
};
|
|
15555
15673
|
}
|
|
15556
15674
|
const links = (out.calculated_values ?? []).map((cv) => ({
|
|
@@ -15558,7 +15676,13 @@ async function getFull(client, id, options) {
|
|
|
15558
15676
|
field: cv.field ?? null,
|
|
15559
15677
|
side: cv.side ?? null
|
|
15560
15678
|
}));
|
|
15561
|
-
return {
|
|
15679
|
+
return {
|
|
15680
|
+
...normalize(out),
|
|
15681
|
+
calculated_values: links,
|
|
15682
|
+
is_fx_aware: out.is_fx_aware === true,
|
|
15683
|
+
default_agg_datefield: out.default_agg_datefield ?? null,
|
|
15684
|
+
default_agg_config: out.default_agg_config ?? null
|
|
15685
|
+
};
|
|
15562
15686
|
}
|
|
15563
15687
|
async function update2(client, id, body, options) {
|
|
15564
15688
|
const out = await client.putJSON(
|
|
@@ -15577,6 +15701,140 @@ async function rename2(client, id, name, options) {
|
|
|
15577
15701
|
async function deleteById3(client, id, options) {
|
|
15578
15702
|
await client.deleteJSON(`/api/functions/${id}`, options);
|
|
15579
15703
|
}
|
|
15704
|
+
async function data2(client, id, body, options = {}) {
|
|
15705
|
+
const out = await putWithCacheKeyPolling(client, `/api/functions/${id}/data/v2`, body, {
|
|
15706
|
+
label: "function data",
|
|
15707
|
+
...options
|
|
15708
|
+
});
|
|
15709
|
+
return out ?? { query_results: [] };
|
|
15710
|
+
}
|
|
15711
|
+
async function drilldown(client, id, body, options = {}) {
|
|
15712
|
+
return putWithCacheKeyPolling(client, `/api/functions/${id}/drilldown/v4`, body, {
|
|
15713
|
+
label: "function drilldown",
|
|
15714
|
+
...options
|
|
15715
|
+
});
|
|
15716
|
+
}
|
|
15717
|
+
function parseDrilldown(body) {
|
|
15718
|
+
if (!Array.isArray(body)) {
|
|
15719
|
+
throw new Error(`unexpected drill-down response (not an array): ${preview(body)}`);
|
|
15720
|
+
}
|
|
15721
|
+
const first = body[0];
|
|
15722
|
+
if (first !== null && typeof first === "object" && "error" in first) {
|
|
15723
|
+
const e = first;
|
|
15724
|
+
if (e["error"] === "too_large_data") {
|
|
15725
|
+
return {
|
|
15726
|
+
kind: "too_large",
|
|
15727
|
+
rows_count: Number(e["rows_count"] ?? 0),
|
|
15728
|
+
cols_count: Number(e["cols_count"] ?? 0)
|
|
15729
|
+
};
|
|
15730
|
+
}
|
|
15731
|
+
throw new Error(`drill-down error: ${preview(first)}`);
|
|
15732
|
+
}
|
|
15733
|
+
if (body.length === 0) {
|
|
15734
|
+
return { kind: "rows", rows: [], schema: { fields: [], empty_fields: [] } };
|
|
15735
|
+
}
|
|
15736
|
+
const last = body[body.length - 1];
|
|
15737
|
+
const isSchema = last !== null && typeof last === "object" && Array.isArray(last["fields"]);
|
|
15738
|
+
if (!isSchema) {
|
|
15739
|
+
throw new Error("unexpected drill-down response: no trailing {fields, empty_fields} schema");
|
|
15740
|
+
}
|
|
15741
|
+
const raw = last;
|
|
15742
|
+
const schema = {
|
|
15743
|
+
fields: raw["fields"],
|
|
15744
|
+
empty_fields: Array.isArray(raw["empty_fields"]) ? raw["empty_fields"] : []
|
|
15745
|
+
};
|
|
15746
|
+
return { kind: "rows", rows: body.slice(0, -1), schema };
|
|
15747
|
+
}
|
|
15748
|
+
async function exportDrilldownCsv(client, id, body, options = {}) {
|
|
15749
|
+
const interval = options.pollIntervalMs ?? CACHE_KEY_POLL_INTERVAL_MS;
|
|
15750
|
+
const sleep3 = options.sleep ?? abortableSleep;
|
|
15751
|
+
const path11 = `/api/functions/${id}/drilldown/export_to_csv`;
|
|
15752
|
+
const payload = JSON.stringify(body);
|
|
15753
|
+
let res = await putOnce(client, path11, payload, options);
|
|
15754
|
+
while (res.status === 409) {
|
|
15755
|
+
const key = extractCacheKey(res.parsed);
|
|
15756
|
+
if (key === "") {
|
|
15757
|
+
throw new Error(`PUT ${path11}: 409 without cache_key: ${preview(res.parsed)}`);
|
|
15758
|
+
}
|
|
15759
|
+
for (; ; ) {
|
|
15760
|
+
try {
|
|
15761
|
+
await sleep3(interval, options.signal);
|
|
15762
|
+
} catch (err) {
|
|
15763
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
15764
|
+
throw new Error(`drill-down CSV export polling timed out: ${reason}`);
|
|
15765
|
+
}
|
|
15766
|
+
const status2 = await client.postJSON(
|
|
15767
|
+
"/api/cache/cache_keys_status",
|
|
15768
|
+
{ cache_keys: [key] },
|
|
15769
|
+
options
|
|
15770
|
+
);
|
|
15771
|
+
const entry = status2?.[key];
|
|
15772
|
+
if (entry !== null && typeof entry === "object") {
|
|
15773
|
+
const e = entry;
|
|
15774
|
+
const status3 = typeof e["status"] === "string" ? e["status"].toUpperCase() : "";
|
|
15775
|
+
const hasError = e["error"] !== void 0 && e["error"] !== null && e["error"] !== false;
|
|
15776
|
+
if (status3 === "FAILURE" || hasError) {
|
|
15777
|
+
throw new Error(
|
|
15778
|
+
`drill-down CSV export failed on the server (cache key ${key}, status ${status3 === "" ? "-" : status3}${hasError ? `, error ${preview(e["error"])}` : ""}); retry, or narrow the drill-down with more --filter flags`
|
|
15779
|
+
);
|
|
15780
|
+
}
|
|
15781
|
+
if (e["is_ready"] === true) break;
|
|
15782
|
+
}
|
|
15783
|
+
}
|
|
15784
|
+
res = await putOnce(client, withCacheKey(path11, key), payload, options);
|
|
15785
|
+
}
|
|
15786
|
+
const parsed2 = res.parsed;
|
|
15787
|
+
const reportId = parsed2 !== null && typeof parsed2 === "object" ? parsed2["drill_down_report_id"] : void 0;
|
|
15788
|
+
if (typeof reportId !== "number") {
|
|
15789
|
+
throw new Error(`PUT ${path11}: no drill_down_report_id in ${preview(parsed2)}`);
|
|
15790
|
+
}
|
|
15791
|
+
const reportPath = `/api/dashboards/report/${reportId}?report_type=drill_down_report`;
|
|
15792
|
+
const report = await client.do("GET", reportPath, void 0, options);
|
|
15793
|
+
if (report.status < 200 || report.status >= 300) {
|
|
15794
|
+
const text2 = await report.text();
|
|
15795
|
+
throw new Error(`GET ${reportPath}: HTTP ${report.status}: ${text2.slice(0, 200)}`);
|
|
15796
|
+
}
|
|
15797
|
+
return { reportId, bytes: new Uint8Array(await report.arrayBuffer()) };
|
|
15798
|
+
}
|
|
15799
|
+
function preview(v) {
|
|
15800
|
+
const s = JSON.stringify(v ?? null);
|
|
15801
|
+
return s.length > 200 ? s.slice(0, 200) + "\u2026" : s;
|
|
15802
|
+
}
|
|
15803
|
+
|
|
15804
|
+
// src/commands/_excelSerial.ts
|
|
15805
|
+
var EXCEL_UNIX_EPOCH_OFFSET_DAYS = 25569;
|
|
15806
|
+
var EXCEL_MAX_SERIAL = 2958465;
|
|
15807
|
+
var MIN_EPOCH_SECONDS = 1e8;
|
|
15808
|
+
var MAX_EPOCH_SECONDS = 1e11;
|
|
15809
|
+
var SERIAL_DATE_FORMS = "a full date (YYYY-MM-DD, M/D/YYYY or an ISO datetime \u2014 a month alone is ambiguous), an Excel serial (e.g. 45688), or Unix epoch seconds";
|
|
15810
|
+
function toExcelSerial(raw) {
|
|
15811
|
+
const s = raw.trim();
|
|
15812
|
+
if (/^\d+(\.\d+)?$/.test(s)) {
|
|
15813
|
+
const n = Number(s);
|
|
15814
|
+
if (n <= EXCEL_MAX_SERIAL) return n;
|
|
15815
|
+
if (Number.isInteger(n) && n >= MIN_EPOCH_SECONDS && n < MAX_EPOCH_SECONDS) {
|
|
15816
|
+
return epochSecondsToSerial(n);
|
|
15817
|
+
}
|
|
15818
|
+
return null;
|
|
15819
|
+
}
|
|
15820
|
+
if (/^\d{4}[-/]\d{1,2}$/.test(s)) return null;
|
|
15821
|
+
const epoch = toEpochSeconds(s);
|
|
15822
|
+
if (epoch === null) return null;
|
|
15823
|
+
return epochSecondsToSerial(Number(epoch));
|
|
15824
|
+
}
|
|
15825
|
+
function epochSecondsToSerial(sec) {
|
|
15826
|
+
return Math.floor(sec / 86400) + EXCEL_UNIX_EPOCH_OFFSET_DAYS;
|
|
15827
|
+
}
|
|
15828
|
+
function excelSerialToIso(n) {
|
|
15829
|
+
if (!Number.isFinite(n)) return String(n);
|
|
15830
|
+
const ms = (Math.floor(n) - EXCEL_UNIX_EPOCH_OFFSET_DAYS) * 864e5;
|
|
15831
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
15832
|
+
}
|
|
15833
|
+
function epochSecondsToIso(sec) {
|
|
15834
|
+
if (!Number.isFinite(sec)) return String(sec);
|
|
15835
|
+
const iso = new Date(sec * 1e3).toISOString();
|
|
15836
|
+
return sec % 86400 === 0 ? iso.slice(0, 10) : iso.replace(/\.\d{3}Z$/, "Z");
|
|
15837
|
+
}
|
|
15580
15838
|
|
|
15581
15839
|
// src/commands/functions.ts
|
|
15582
15840
|
var VALID_AGGS2 = ["SUM", "COUNT", "AVG", "MIN", "MAX"];
|
|
@@ -15627,19 +15885,37 @@ async function runFunctionsGet(cfg) {
|
|
|
15627
15885
|
renderFunctionTable(cfg.out, [fn]);
|
|
15628
15886
|
renderFunctionFilters(cfg.out, fn);
|
|
15629
15887
|
}
|
|
15888
|
+
async function resolveDateField(client, templateId, token, signal) {
|
|
15889
|
+
const index = await loadTemplateFields(client, templateId, { signal });
|
|
15890
|
+
const field = resolveField(index, token, "--date-field");
|
|
15891
|
+
if (!isDateField(field)) {
|
|
15892
|
+
throw new Error(
|
|
15893
|
+
`--date-field "${token}": "${field.name}" is a ${field.type} field; the default aggregation date field must be a Date field`
|
|
15894
|
+
);
|
|
15895
|
+
}
|
|
15896
|
+
return field;
|
|
15897
|
+
}
|
|
15630
15898
|
async function runFunctionsCreate(cfg) {
|
|
15631
15899
|
validateName(cfg.name);
|
|
15632
15900
|
const aggUpper = validateAgg(cfg.aggOriginal);
|
|
15901
|
+
const dateField = cfg.dateField === void 0 ? void 0 : await resolveDateField(cfg.client, cfg.template, cfg.dateField, cfg.signal);
|
|
15633
15902
|
const created = await create6(
|
|
15634
15903
|
cfg.client,
|
|
15635
15904
|
{
|
|
15636
15905
|
name: cfg.name,
|
|
15637
15906
|
template: cfg.template,
|
|
15638
15907
|
valueField: cfg.valueField,
|
|
15639
|
-
aggfun: aggUpper
|
|
15908
|
+
aggfun: aggUpper,
|
|
15909
|
+
...dateField !== void 0 ? { dateField: dateField.id } : {}
|
|
15640
15910
|
},
|
|
15641
15911
|
{ signal: cfg.signal }
|
|
15642
15912
|
);
|
|
15913
|
+
if (dateField === void 0) {
|
|
15914
|
+
(cfg.err ?? process.stderr).write(
|
|
15915
|
+
`warning: no --date-field given \u2014 period aggregations (DR.YTD/QTD/MTD/\u2026, functions data --agg YTD) will not work for function ${created.id}; set one with: dr functions update ${created.id} --date-field <Date field>
|
|
15916
|
+
`
|
|
15917
|
+
);
|
|
15918
|
+
}
|
|
15643
15919
|
if (cfg.asJson) {
|
|
15644
15920
|
writeJSONIndented(cfg.out, toJsonShape(created));
|
|
15645
15921
|
return;
|
|
@@ -15654,13 +15930,14 @@ async function runFunctionsUpdate(cfg) {
|
|
|
15654
15930
|
const hasField = cfg.field !== void 0;
|
|
15655
15931
|
const hasAgg = cfg.aggOriginal !== void 0;
|
|
15656
15932
|
const hasTemplate = cfg.template !== void 0;
|
|
15657
|
-
|
|
15658
|
-
|
|
15933
|
+
const hasDateField = cfg.dateField !== void 0;
|
|
15934
|
+
if (!hasName && !hasField && !hasAgg && !hasTemplate && !hasDateField) {
|
|
15935
|
+
throw new Error("nothing to update (pass --name/--field/--agg/--template/--date-field)");
|
|
15659
15936
|
}
|
|
15660
15937
|
if (hasName) validateName(cfg.name);
|
|
15661
15938
|
const aggUpper = hasAgg ? validateAgg(cfg.aggOriginal) : void 0;
|
|
15662
15939
|
let result;
|
|
15663
|
-
if (!hasField && !hasAgg && !hasTemplate) {
|
|
15940
|
+
if (!hasField && !hasAgg && !hasTemplate && !hasDateField) {
|
|
15664
15941
|
await rename2(cfg.client, cfg.id, cfg.name, {
|
|
15665
15942
|
signal: cfg.signal
|
|
15666
15943
|
});
|
|
@@ -15669,6 +15946,13 @@ async function runFunctionsUpdate(cfg) {
|
|
|
15669
15946
|
const current = await getFull(cfg.client, cfg.id, {
|
|
15670
15947
|
signal: cfg.signal
|
|
15671
15948
|
});
|
|
15949
|
+
const template = hasTemplate ? cfg.template : current.template;
|
|
15950
|
+
if (template !== current.template && current.default_agg_datefield !== null && !hasDateField) {
|
|
15951
|
+
throw new Error(
|
|
15952
|
+
`function ${cfg.id} has default date field ${current.default_agg_datefield}, which belongs to the old template ${current.template}; pass --date-field <Date field of template ${template}> together with --template`
|
|
15953
|
+
);
|
|
15954
|
+
}
|
|
15955
|
+
const dateField = hasDateField ? await resolveDateField(cfg.client, template, cfg.dateField, cfg.signal) : void 0;
|
|
15672
15956
|
result = await update2(
|
|
15673
15957
|
cfg.client,
|
|
15674
15958
|
cfg.id,
|
|
@@ -15676,11 +15960,16 @@ async function runFunctionsUpdate(cfg) {
|
|
|
15676
15960
|
name: hasName ? cfg.name : current.name,
|
|
15677
15961
|
value_field: hasField ? cfg.field : current.value_field,
|
|
15678
15962
|
aggfun: hasAgg ? aggUpper : current.aggfun,
|
|
15679
|
-
template
|
|
15963
|
+
template,
|
|
15680
15964
|
calculated_values: current.calculated_values,
|
|
15681
15965
|
// Re-sent so the full PUT can't silently reset the currency-conversion
|
|
15682
15966
|
// flag (same class of trap as calculated_values above).
|
|
15683
|
-
is_fx_aware: current.is_fx_aware
|
|
15967
|
+
is_fx_aware: current.is_fx_aware,
|
|
15968
|
+
...dateField !== void 0 ? {
|
|
15969
|
+
default_agg_datefield: dateField.id,
|
|
15970
|
+
// Keep a config the function already has; otherwise the platform default.
|
|
15971
|
+
default_agg_config: current.default_agg_config ?? DEFAULT_AGG_CONFIG
|
|
15972
|
+
} : {}
|
|
15684
15973
|
},
|
|
15685
15974
|
{ signal: cfg.signal }
|
|
15686
15975
|
);
|
|
@@ -15823,6 +16112,365 @@ async function runFunctionsDelete(cfg) {
|
|
|
15823
16112
|
}
|
|
15824
16113
|
});
|
|
15825
16114
|
}
|
|
16115
|
+
var PERIOD_AGGS = [
|
|
16116
|
+
"DEFAULT",
|
|
16117
|
+
"YTD",
|
|
16118
|
+
"YTG",
|
|
16119
|
+
"QTD",
|
|
16120
|
+
"QTG",
|
|
16121
|
+
"MTD",
|
|
16122
|
+
"MTG"
|
|
16123
|
+
];
|
|
16124
|
+
function validatePeriodAgg(orig) {
|
|
16125
|
+
const upper = orig.trim().toUpperCase();
|
|
16126
|
+
if (!PERIOD_AGGS.includes(upper)) {
|
|
16127
|
+
throw new Error(`invalid --agg "${orig}" (valid: ${PERIOD_AGGS.join(", ")})`);
|
|
16128
|
+
}
|
|
16129
|
+
return upper;
|
|
16130
|
+
}
|
|
16131
|
+
function parseEvalFilterFlags(raw, flag) {
|
|
16132
|
+
return raw.map((r) => {
|
|
16133
|
+
const idx = r.indexOf("=");
|
|
16134
|
+
const name = idx > 0 ? r.slice(0, idx).trim() : "";
|
|
16135
|
+
const values = idx > 0 ? r.slice(idx + 1).split(",").map((v) => v.trim()).filter((v) => v !== "") : [];
|
|
16136
|
+
if (name === "" || values.length === 0) {
|
|
16137
|
+
throw new Error(`invalid ${flag} "${r}": expected NAME=V1[,V2\u2026]`);
|
|
16138
|
+
}
|
|
16139
|
+
return { flag, name, values };
|
|
16140
|
+
});
|
|
16141
|
+
}
|
|
16142
|
+
var NUMERIC_FIELD_TYPES = /* @__PURE__ */ new Set(["integer", "float", "number", "numeric", "monetary"]);
|
|
16143
|
+
function resolveEvalFilters(index, specs) {
|
|
16144
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16145
|
+
return specs.map((s) => {
|
|
16146
|
+
const field = resolveField(index, s.name, s.flag);
|
|
16147
|
+
if (seen.has(field.id)) {
|
|
16148
|
+
throw new Error(
|
|
16149
|
+
`field "${field.name}" is given more than once; put all its values in one ${s.flag} "${field.name}=V1,V2"`
|
|
16150
|
+
);
|
|
16151
|
+
}
|
|
16152
|
+
seen.add(field.id);
|
|
16153
|
+
const isDate = isDateField(field);
|
|
16154
|
+
if (s.flag === "--date" && !isDate) {
|
|
16155
|
+
throw new Error(
|
|
16156
|
+
`--date "${s.name}=\u2026": "${field.name}" is a ${field.type} field, not Date; use --filter`
|
|
16157
|
+
);
|
|
16158
|
+
}
|
|
16159
|
+
let values;
|
|
16160
|
+
if (isDate) {
|
|
16161
|
+
values = s.values.map((v) => {
|
|
16162
|
+
const serial = toExcelSerial(v);
|
|
16163
|
+
if (serial === null) {
|
|
16164
|
+
throw new Error(
|
|
16165
|
+
`invalid date "${v}" for Date field "${field.name}" (${s.flag}): expected ${SERIAL_DATE_FORMS}`
|
|
16166
|
+
);
|
|
16167
|
+
}
|
|
16168
|
+
return serial;
|
|
16169
|
+
});
|
|
16170
|
+
} else if (NUMERIC_FIELD_TYPES.has(field.type.trim().toLowerCase())) {
|
|
16171
|
+
values = s.values.map((v) => /^-?\d+(\.\d+)?$/.test(v) ? Number(v) : v);
|
|
16172
|
+
} else {
|
|
16173
|
+
values = [...s.values];
|
|
16174
|
+
}
|
|
16175
|
+
return { field, isDate, values, isExcluded: s.flag === "--exclude" };
|
|
16176
|
+
});
|
|
16177
|
+
}
|
|
16178
|
+
function readableQueryError(msg) {
|
|
16179
|
+
const decoded = String(decodeHtmlEntitiesDeep(msg));
|
|
16180
|
+
const inner = [...decoded.matchAll(/\[\s*'((?:[^'\\]|\\.)*)'\s*\]/g)].map((m) => m[1]);
|
|
16181
|
+
return inner.length > 0 ? inner.join("; ") : decoded;
|
|
16182
|
+
}
|
|
16183
|
+
function rethrowEvaluatorError(err, id, what) {
|
|
16184
|
+
if (err instanceof CacheKeyHttpError) {
|
|
16185
|
+
let msg = `function ${id} ${what}: HTTP ${err.status}: ${err.bodyText}`;
|
|
16186
|
+
if (err.status === 400 && err.bodyText.includes("DateAgg")) {
|
|
16187
|
+
msg += `
|
|
16188
|
+
hint: period aggregations need a default date field \u2014 dr functions update ${id} --date-field <Date field>`;
|
|
16189
|
+
}
|
|
16190
|
+
throw new Error(msg);
|
|
16191
|
+
}
|
|
16192
|
+
throw err;
|
|
16193
|
+
}
|
|
16194
|
+
function formatEvalNumber(v) {
|
|
16195
|
+
if (v === null || v === void 0) return "-";
|
|
16196
|
+
if (typeof v !== "number") return typeof v === "string" ? v : JSON.stringify(v);
|
|
16197
|
+
if (!Number.isFinite(v) || Number.isInteger(v)) return String(v);
|
|
16198
|
+
if (Math.abs(v) < 1) return String(Number(v.toPrecision(10)));
|
|
16199
|
+
if (Math.abs(v) >= 1e15) return String(v);
|
|
16200
|
+
return String(Math.round(v * 1e4) / 1e4);
|
|
16201
|
+
}
|
|
16202
|
+
async function runFunctionsData(cfg) {
|
|
16203
|
+
if (cfg.id <= 0) throw new Error("function id must be positive");
|
|
16204
|
+
const agg = validatePeriodAgg(cfg.aggOriginal);
|
|
16205
|
+
const specs = [
|
|
16206
|
+
...parseEvalFilterFlags(cfg.filters, "--filter"),
|
|
16207
|
+
...parseEvalFilterFlags(cfg.dates, "--date")
|
|
16208
|
+
];
|
|
16209
|
+
const fn = await getDetail(cfg.client, cfg.id, { signal: cfg.signal });
|
|
16210
|
+
const resolved = specs.length === 0 ? [] : resolveEvalFilters(
|
|
16211
|
+
await loadTemplateFields(cfg.client, fn.template, { signal: cfg.signal }),
|
|
16212
|
+
specs
|
|
16213
|
+
);
|
|
16214
|
+
const body = {
|
|
16215
|
+
queries: [
|
|
16216
|
+
{
|
|
16217
|
+
client_key: "q1",
|
|
16218
|
+
agg_type: agg,
|
|
16219
|
+
fields: resolved.map((r) => ({ name: r.field.name, values: r.values }))
|
|
16220
|
+
}
|
|
16221
|
+
],
|
|
16222
|
+
global_filters: [],
|
|
16223
|
+
function_filters: []
|
|
16224
|
+
};
|
|
16225
|
+
let res;
|
|
16226
|
+
try {
|
|
16227
|
+
res = await data2(cfg.client, cfg.id, body, {
|
|
16228
|
+
signal: cfg.signal,
|
|
16229
|
+
pollIntervalMs: cfg.pollIntervalMs,
|
|
16230
|
+
sleep: cfg.sleep
|
|
16231
|
+
});
|
|
16232
|
+
} catch (err) {
|
|
16233
|
+
rethrowEvaluatorError(err, cfg.id, "data");
|
|
16234
|
+
}
|
|
16235
|
+
if (cfg.asJson) writeJSONIndented(cfg.out, res);
|
|
16236
|
+
const results = Array.isArray(res.query_results) ? res.query_results : [];
|
|
16237
|
+
const errors = results.filter((q) => q.error !== void 0 && q.error !== null);
|
|
16238
|
+
if (errors.length > 0) {
|
|
16239
|
+
throw new Error(
|
|
16240
|
+
`function ${cfg.id} data: ` + errors.map((q) => readableQueryError(String(q.error?.msg ?? ""))).join("; ")
|
|
16241
|
+
);
|
|
16242
|
+
}
|
|
16243
|
+
if (cfg.asJson) return;
|
|
16244
|
+
const values = results.flatMap((q) => Array.isArray(q.res) ? q.res : []);
|
|
16245
|
+
if (values.length === 0) {
|
|
16246
|
+
cfg.out.write("(no data: nothing matches these filters)\n");
|
|
16247
|
+
return;
|
|
16248
|
+
}
|
|
16249
|
+
const rows = [{ cells: [...resolved.map((r) => r.field.name), "VALUE"] }];
|
|
16250
|
+
for (const r of values) {
|
|
16251
|
+
const k = Array.isArray(r.k) ? r.k : [];
|
|
16252
|
+
const keyCells = k.length === resolved.length ? k.map(
|
|
16253
|
+
(kv, i) => resolved[i]?.isDate === true && typeof kv === "number" ? excelSerialToIso(kv) : formatEvalNumber(kv)
|
|
16254
|
+
) : resolved.map((_, i) => i === 0 ? JSON.stringify(k) : "");
|
|
16255
|
+
rows.push({ cells: [...keyCells, formatEvalNumber(r.v)] });
|
|
16256
|
+
}
|
|
16257
|
+
writeTable11(cfg.out, rows);
|
|
16258
|
+
}
|
|
16259
|
+
function aggregateColumn(aggfun, values) {
|
|
16260
|
+
const present = values.filter((v) => v !== null && v !== void 0 && v !== "");
|
|
16261
|
+
const nums = present.map((v) => Number(v)).filter((n) => Number.isFinite(n));
|
|
16262
|
+
switch (aggfun.trim().toUpperCase()) {
|
|
16263
|
+
case "COUNT":
|
|
16264
|
+
return present.length;
|
|
16265
|
+
case "AVG":
|
|
16266
|
+
return nums.length === 0 ? null : nums.reduce((a, b) => a + b, 0) / nums.length;
|
|
16267
|
+
case "MIN":
|
|
16268
|
+
return nums.length === 0 ? null : Math.min(...nums);
|
|
16269
|
+
case "MAX":
|
|
16270
|
+
return nums.length === 0 ? null : Math.max(...nums);
|
|
16271
|
+
default:
|
|
16272
|
+
return nums.reduce((a, b) => a + b, 0);
|
|
16273
|
+
}
|
|
16274
|
+
}
|
|
16275
|
+
function drilldownCell(v, isDate) {
|
|
16276
|
+
if (v === null || v === void 0) return "";
|
|
16277
|
+
if (isDate && typeof v === "number") return epochSecondsToIso(v);
|
|
16278
|
+
if (typeof v === "number") return formatEvalNumber(v);
|
|
16279
|
+
if (typeof v === "string") return v;
|
|
16280
|
+
return JSON.stringify(v);
|
|
16281
|
+
}
|
|
16282
|
+
function drilldownRowsToCsv(rows, schema) {
|
|
16283
|
+
const cols = schema.fields.length > 0 ? schema.fields : Object.keys(rows[0] ?? {}).map((name) => ({ name }));
|
|
16284
|
+
const dateCols = new Set(
|
|
16285
|
+
schema.fields.filter((f) => (f.type ?? "").toLowerCase() === "date").map((f) => f.name)
|
|
16286
|
+
);
|
|
16287
|
+
const cell = (v) => {
|
|
16288
|
+
if (v === null || v === void 0) return "";
|
|
16289
|
+
if (typeof v === "string") return v;
|
|
16290
|
+
return typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
16291
|
+
};
|
|
16292
|
+
const lines = [formatCsvRow(cols.map((c) => c.name))];
|
|
16293
|
+
for (const r of rows) {
|
|
16294
|
+
lines.push(
|
|
16295
|
+
formatCsvRow(
|
|
16296
|
+
cols.map((c) => {
|
|
16297
|
+
const v = r[c.name];
|
|
16298
|
+
return dateCols.has(c.name) && typeof v === "number" ? epochSecondsToIso(v) : cell(v);
|
|
16299
|
+
})
|
|
16300
|
+
)
|
|
16301
|
+
);
|
|
16302
|
+
}
|
|
16303
|
+
return lines.join("\n") + "\n";
|
|
16304
|
+
}
|
|
16305
|
+
async function runFunctionsDrilldown(cfg) {
|
|
16306
|
+
if (cfg.id <= 0) throw new Error("function id must be positive");
|
|
16307
|
+
if (cfg.forceCsv && cfg.outPath === void 0) {
|
|
16308
|
+
throw new Error("--csv needs --out <file.csv>");
|
|
16309
|
+
}
|
|
16310
|
+
if (!Number.isInteger(cfg.limit) || cfg.limit < 0) {
|
|
16311
|
+
throw new Error("--limit must be a non-negative integer (0 = all rows)");
|
|
16312
|
+
}
|
|
16313
|
+
const agg = validatePeriodAgg(cfg.aggOriginal);
|
|
16314
|
+
const specs = [
|
|
16315
|
+
...parseEvalFilterFlags(cfg.filters, "--filter"),
|
|
16316
|
+
...parseEvalFilterFlags(cfg.excludes, "--exclude"),
|
|
16317
|
+
...parseEvalFilterFlags(cfg.dates, "--date")
|
|
16318
|
+
];
|
|
16319
|
+
const fn = await getDetail(cfg.client, cfg.id, { signal: cfg.signal });
|
|
16320
|
+
const index = await loadTemplateFields(cfg.client, fn.template, { signal: cfg.signal });
|
|
16321
|
+
const resolved = resolveEvalFilters(index, specs);
|
|
16322
|
+
const valueFieldName = index.fields.find((f) => f.id === fn.value_field)?.name ?? `field ${fn.value_field}`;
|
|
16323
|
+
const aggLabel = `${fn.aggfun === "" ? "SUM" : fn.aggfun.toUpperCase()}(${valueFieldName})`;
|
|
16324
|
+
const body = {
|
|
16325
|
+
agg_type: [agg],
|
|
16326
|
+
global_filters: [],
|
|
16327
|
+
function_filters: [
|
|
16328
|
+
resolved.map((r) => ({
|
|
16329
|
+
values: r.values,
|
|
16330
|
+
name: r.field.name,
|
|
16331
|
+
type: r.field.type,
|
|
16332
|
+
field: r.field.id,
|
|
16333
|
+
function_id: cfg.id,
|
|
16334
|
+
is_excluded: r.isExcluded,
|
|
16335
|
+
show_in_graph: false
|
|
16336
|
+
}))
|
|
16337
|
+
],
|
|
16338
|
+
table_filters: []
|
|
16339
|
+
};
|
|
16340
|
+
const writeFile6 = cfg.writeFile ?? ((p, d) => fs11.writeFile(p, d));
|
|
16341
|
+
const seams = { signal: cfg.signal, pollIntervalMs: cfg.pollIntervalMs, sleep: cfg.sleep };
|
|
16342
|
+
const exportCsv2 = async (reason) => {
|
|
16343
|
+
const outPath = cfg.outPath;
|
|
16344
|
+
let exp;
|
|
16345
|
+
try {
|
|
16346
|
+
exp = await exportDrilldownCsv(cfg.client, cfg.id, body, seams);
|
|
16347
|
+
} catch (err) {
|
|
16348
|
+
rethrowEvaluatorError(err, cfg.id, "drilldown export");
|
|
16349
|
+
}
|
|
16350
|
+
await writeFile6(outPath, exp.bytes);
|
|
16351
|
+
const table2 = parseCsvRows(new TextDecoder().decode(exp.bytes).replace(/^/, ""));
|
|
16352
|
+
const header = table2[0] ?? [];
|
|
16353
|
+
const col = header.findIndex((h) => h.trim() === valueFieldName);
|
|
16354
|
+
const dataRows = table2.slice(1);
|
|
16355
|
+
const aggValue2 = col < 0 ? null : aggregateColumn(
|
|
16356
|
+
fn.aggfun,
|
|
16357
|
+
dataRows.map((r) => r[col])
|
|
16358
|
+
);
|
|
16359
|
+
if (cfg.asJson) {
|
|
16360
|
+
writeJSONIndented(cfg.out, {
|
|
16361
|
+
function_id: cfg.id,
|
|
16362
|
+
out: outPath,
|
|
16363
|
+
source: "export_to_csv",
|
|
16364
|
+
report_id: exp.reportId,
|
|
16365
|
+
bytes: exp.bytes.length,
|
|
16366
|
+
rows: dataRows.length,
|
|
16367
|
+
columns: header.length,
|
|
16368
|
+
aggregate: { label: aggLabel, value: aggValue2 }
|
|
16369
|
+
});
|
|
16370
|
+
return;
|
|
16371
|
+
}
|
|
16372
|
+
cfg.out.write(
|
|
16373
|
+
`Wrote ${dataRows.length} rows \xD7 ${header.length} columns to ${outPath} (server CSV export${reason}, report ${exp.reportId}, ${exp.bytes.length} bytes).
|
|
16374
|
+
`
|
|
16375
|
+
);
|
|
16376
|
+
cfg.out.write(
|
|
16377
|
+
col < 0 ? `(value field "${valueFieldName}" not found in the CSV header; no aggregate)
|
|
16378
|
+
` : `${aggLabel} over these rows: ${formatEvalNumber(aggValue2)}
|
|
16379
|
+
`
|
|
16380
|
+
);
|
|
16381
|
+
};
|
|
16382
|
+
if (cfg.forceCsv) {
|
|
16383
|
+
await exportCsv2("");
|
|
16384
|
+
return;
|
|
16385
|
+
}
|
|
16386
|
+
let raw;
|
|
16387
|
+
try {
|
|
16388
|
+
raw = await drilldown(cfg.client, cfg.id, body, seams);
|
|
16389
|
+
} catch (err) {
|
|
16390
|
+
rethrowEvaluatorError(err, cfg.id, "drilldown");
|
|
16391
|
+
}
|
|
16392
|
+
const parsed2 = parseDrilldown(raw);
|
|
16393
|
+
if (parsed2.kind === "too_large") {
|
|
16394
|
+
if (cfg.outPath !== void 0) {
|
|
16395
|
+
await exportCsv2(`; inline answer too large: ${parsed2.rows_count} rows`);
|
|
16396
|
+
return;
|
|
16397
|
+
}
|
|
16398
|
+
if (cfg.asJson) writeJSONIndented(cfg.out, raw);
|
|
16399
|
+
throw new Error(
|
|
16400
|
+
`drill-down for function ${cfg.id} is too large to return inline (${parsed2.rows_count} rows \xD7 ${parsed2.cols_count} columns); re-run with --out <file.csv> to export it as CSV, or narrow it with more --filter flags`
|
|
16401
|
+
);
|
|
16402
|
+
}
|
|
16403
|
+
const { rows, schema } = parsed2;
|
|
16404
|
+
const aggValue = aggregateColumn(
|
|
16405
|
+
fn.aggfun,
|
|
16406
|
+
rows.map((r) => r[valueFieldName])
|
|
16407
|
+
);
|
|
16408
|
+
if (cfg.outPath !== void 0) {
|
|
16409
|
+
await writeFile6(cfg.outPath, drilldownRowsToCsv(rows, schema));
|
|
16410
|
+
if (cfg.asJson) {
|
|
16411
|
+
writeJSONIndented(cfg.out, {
|
|
16412
|
+
function_id: cfg.id,
|
|
16413
|
+
out: cfg.outPath,
|
|
16414
|
+
source: "inline",
|
|
16415
|
+
rows: rows.length,
|
|
16416
|
+
columns: schema.fields.length,
|
|
16417
|
+
aggregate: { label: aggLabel, value: aggValue }
|
|
16418
|
+
});
|
|
16419
|
+
return;
|
|
16420
|
+
}
|
|
16421
|
+
cfg.out.write(
|
|
16422
|
+
`Wrote ${rows.length} rows \xD7 ${schema.fields.length} columns to ${cfg.outPath} (inline drill-down).
|
|
16423
|
+
`
|
|
16424
|
+
);
|
|
16425
|
+
cfg.out.write(`${aggLabel} over these rows: ${formatEvalNumber(aggValue)}
|
|
16426
|
+
`);
|
|
16427
|
+
return;
|
|
16428
|
+
}
|
|
16429
|
+
if (cfg.asJson) {
|
|
16430
|
+
writeJSONIndented(cfg.out, raw);
|
|
16431
|
+
return;
|
|
16432
|
+
}
|
|
16433
|
+
cfg.out.write(
|
|
16434
|
+
`${rows.length} rows \xD7 ${schema.fields.length} columns (${schema.empty_fields.length} empty)
|
|
16435
|
+
`
|
|
16436
|
+
);
|
|
16437
|
+
cfg.out.write(`${aggLabel} over these rows: ${formatEvalNumber(aggValue)}
|
|
16438
|
+
`);
|
|
16439
|
+
if (rows.length === 0) return;
|
|
16440
|
+
const byName = new Map(schema.fields.map((f) => [f.name, f]));
|
|
16441
|
+
let cols;
|
|
16442
|
+
if (cfg.columns !== void 0 && cfg.columns.length > 0) {
|
|
16443
|
+
cols = cfg.columns.map((c) => {
|
|
16444
|
+
if (byName.has(c)) return c;
|
|
16445
|
+
const loose = schema.fields.filter((f) => f.name.toLowerCase() === c.toLowerCase());
|
|
16446
|
+
if (loose.length === 1) return loose[0].name;
|
|
16447
|
+
throw new Error(
|
|
16448
|
+
`unknown --columns entry "${c}"; drill-down columns: ${schema.fields.map((f) => f.name).join(", ")}`
|
|
16449
|
+
);
|
|
16450
|
+
});
|
|
16451
|
+
} else {
|
|
16452
|
+
cols = [.../* @__PURE__ */ new Set([...resolved.map((r) => r.field.name), valueFieldName])].filter(
|
|
16453
|
+
(c) => byName.has(c)
|
|
16454
|
+
);
|
|
16455
|
+
}
|
|
16456
|
+
const shown = cfg.limit === 0 ? rows : rows.slice(0, cfg.limit);
|
|
16457
|
+
cfg.out.write("\n");
|
|
16458
|
+
const table = [{ cells: cols }];
|
|
16459
|
+
for (const r of shown) {
|
|
16460
|
+
table.push({
|
|
16461
|
+
cells: cols.map(
|
|
16462
|
+
(c) => drilldownCell(r[c], (byName.get(c)?.type ?? "").toLowerCase() === "date")
|
|
16463
|
+
)
|
|
16464
|
+
});
|
|
16465
|
+
}
|
|
16466
|
+
writeTable11(cfg.out, table);
|
|
16467
|
+
if (shown.length < rows.length) {
|
|
16468
|
+
cfg.out.write(
|
|
16469
|
+
`(showing ${shown.length} of ${rows.length} rows \u2014 --limit 0 for all, --columns to pick columns, --out <file.csv> for every column)
|
|
16470
|
+
`
|
|
16471
|
+
);
|
|
16472
|
+
}
|
|
16473
|
+
}
|
|
15826
16474
|
function writeTable11(out, rows) {
|
|
15827
16475
|
if (rows.length === 0) return;
|
|
15828
16476
|
const widths = [];
|
|
@@ -15898,6 +16546,10 @@ function parseIntFlag(raw, flagName) {
|
|
|
15898
16546
|
}
|
|
15899
16547
|
return n;
|
|
15900
16548
|
}
|
|
16549
|
+
function collectFlag(value, previous) {
|
|
16550
|
+
return [...previous, value];
|
|
16551
|
+
}
|
|
16552
|
+
var DRILLDOWN_EXPORT_TIMEOUT_MS = 3e5;
|
|
15901
16553
|
function registerFunctionsCommand(root) {
|
|
15902
16554
|
const fn = root.command("functions").description("Operations on Datarails template functions");
|
|
15903
16555
|
const listCmd = fn.command("list").description("List functions on this tenant").option("--json", "Output raw JSON instead of a table", false).action(async (opts) => {
|
|
@@ -15932,7 +16584,10 @@ function registerFunctionsCommand(root) {
|
|
|
15932
16584
|
});
|
|
15933
16585
|
addServerFlag(getCmd);
|
|
15934
16586
|
addAccountFlag(getCmd);
|
|
15935
|
-
const createCmd = fn.command("create").description("Create a new function").requiredOption("--name <name>", "Function name (no whitespace; required)").requiredOption("--template <id>", "Template ID (required)").requiredOption("--field <id>", "Value field ID (required)").option("--agg <kind>", "Aggregation: SUM, COUNT, AVG, MIN, MAX (default: SUM)", "SUM").option(
|
|
16587
|
+
const createCmd = fn.command("create").description("Create a new function").requiredOption("--name <name>", "Function name (no whitespace; required)").requiredOption("--template <id>", "Template ID (required)").requiredOption("--field <id>", "Value field ID (required)").option("--agg <kind>", "Aggregation: SUM, COUNT, AVG, MIN, MAX (default: SUM)", "SUM").option(
|
|
16588
|
+
"--date-field <id|name>",
|
|
16589
|
+
"Default aggregation date field (a Date field of the template). Needed for DR.YTD/QTD/MTD/\u2026 and `functions data --agg YTD`; without it a warning is printed"
|
|
16590
|
+
).option("--json", "Output raw JSON instead of a table", false).action(
|
|
15936
16591
|
async (opts) => {
|
|
15937
16592
|
const template = parseIntFlag(opts.template, "--template");
|
|
15938
16593
|
const valueField = parseIntFlag(opts.field, "--field");
|
|
@@ -15944,6 +16599,7 @@ function registerFunctionsCommand(root) {
|
|
|
15944
16599
|
template,
|
|
15945
16600
|
valueField,
|
|
15946
16601
|
aggOriginal: opts.agg ?? "SUM",
|
|
16602
|
+
dateField: opts.dateField,
|
|
15947
16603
|
asJson: opts.json === true,
|
|
15948
16604
|
signal: setup.signal,
|
|
15949
16605
|
out: process.stdout
|
|
@@ -15957,7 +16613,10 @@ function registerFunctionsCommand(root) {
|
|
|
15957
16613
|
addAccountFlag(createCmd);
|
|
15958
16614
|
const updateCmd = fn.command("update").description(
|
|
15959
16615
|
"Update a function (name-only routes to the rename endpoint; structural changes read-modify-write a full PUT)"
|
|
15960
|
-
).argument("<id>", "function id").option("--name <name>", "New function name (no whitespace)").option("--field <id>", "New value field ID").option("--agg <kind>", "New aggregation: SUM, COUNT, AVG, MIN, MAX").option("--template <id>", "New template ID").option(
|
|
16616
|
+
).argument("<id>", "function id").option("--name <name>", "New function name (no whitespace)").option("--field <id>", "New value field ID").option("--agg <kind>", "New aggregation: SUM, COUNT, AVG, MIN, MAX").option("--template <id>", "New template ID").option(
|
|
16617
|
+
"--date-field <id|name>",
|
|
16618
|
+
"Set the default aggregation date field (a Date field) used by DR.YTD/QTD/MTD/\u2026"
|
|
16619
|
+
).option("--json", "Output raw JSON instead of a table", false).action(
|
|
15961
16620
|
async (idStr, opts) => {
|
|
15962
16621
|
const id = parseFunctionIdArg(idStr);
|
|
15963
16622
|
const field = opts.field !== void 0 ? parseIntFlag(opts.field, "--field") : void 0;
|
|
@@ -15971,6 +16630,7 @@ function registerFunctionsCommand(root) {
|
|
|
15971
16630
|
field,
|
|
15972
16631
|
aggOriginal: opts.agg,
|
|
15973
16632
|
template,
|
|
16633
|
+
dateField: opts.dateField,
|
|
15974
16634
|
asJson: opts.json === true,
|
|
15975
16635
|
signal: setup.signal,
|
|
15976
16636
|
out: process.stdout
|
|
@@ -16016,6 +16676,106 @@ function registerFunctionsCommand(root) {
|
|
|
16016
16676
|
);
|
|
16017
16677
|
addServerFlag(setFiltersCmd);
|
|
16018
16678
|
addAccountFlag(setFiltersCmd);
|
|
16679
|
+
const dataCmd = fn.command("data").description(
|
|
16680
|
+
"Evaluate a function the way the Excel add-in does (PUT /api/functions/<id>/data/v2): one value per filter-value combination; dates are sent as Excel serials"
|
|
16681
|
+
).argument("<id>", "function id").option(
|
|
16682
|
+
"--filter <NAME=V1,V2>",
|
|
16683
|
+
"Dimension filter by field name (or id); repeatable. Several values return one result per value. A value on a Date field is converted like --date",
|
|
16684
|
+
collectFlag,
|
|
16685
|
+
[]
|
|
16686
|
+
).option(
|
|
16687
|
+
"--date <NAME=DATE[,DATE]>",
|
|
16688
|
+
'Date filter, e.g. "Reporting Date=2025-01-31" (YYYY-MM-DD, M/D/YYYY, an ISO datetime, an Excel serial, or epoch seconds; a bare month is refused); repeatable',
|
|
16689
|
+
collectFlag,
|
|
16690
|
+
[]
|
|
16691
|
+
).option(
|
|
16692
|
+
"--agg <period>",
|
|
16693
|
+
"Period aggregation: DEFAULT, YTD, YTG, QTD, QTG, MTD, MTG",
|
|
16694
|
+
"DEFAULT"
|
|
16695
|
+
).option("--timeout <seconds>", "Whole-command budget in seconds (default 30)").option("--json", "Print the server body verbatim", false).action(
|
|
16696
|
+
async (idStr, opts) => {
|
|
16697
|
+
const id = parseFunctionIdArg(idStr);
|
|
16698
|
+
validatePeriodAgg(opts.agg ?? "DEFAULT");
|
|
16699
|
+
const setup = await setupCall(opts.server, opts.account, batchSetupOptions(opts.timeout));
|
|
16700
|
+
try {
|
|
16701
|
+
await runFunctionsData({
|
|
16702
|
+
client: setup.client,
|
|
16703
|
+
id,
|
|
16704
|
+
filters: opts.filter,
|
|
16705
|
+
dates: opts.date,
|
|
16706
|
+
aggOriginal: opts.agg ?? "DEFAULT",
|
|
16707
|
+
asJson: opts.json === true,
|
|
16708
|
+
signal: setup.signal,
|
|
16709
|
+
out: process.stdout
|
|
16710
|
+
});
|
|
16711
|
+
} finally {
|
|
16712
|
+
setup.cleanup();
|
|
16713
|
+
}
|
|
16714
|
+
}
|
|
16715
|
+
);
|
|
16716
|
+
addServerFlag(dataCmd);
|
|
16717
|
+
addAccountFlag(dataCmd);
|
|
16718
|
+
const drilldownCmd = fn.command("drilldown").description(
|
|
16719
|
+
"The rows behind a function value (PUT /api/functions/<id>/drilldown/v4); with --out, a result too large to return inline is exported as CSV automatically"
|
|
16720
|
+
).argument("<id>", "function id").option(
|
|
16721
|
+
"--filter <NAME=V1,V2>",
|
|
16722
|
+
"Dimension filter by field name (or id); repeatable",
|
|
16723
|
+
collectFlag,
|
|
16724
|
+
[]
|
|
16725
|
+
).option(
|
|
16726
|
+
"--exclude <NAME=V1,V2>",
|
|
16727
|
+
"Exclude these values of a field; repeatable",
|
|
16728
|
+
collectFlag,
|
|
16729
|
+
[]
|
|
16730
|
+
).option(
|
|
16731
|
+
"--date <NAME=DATE[,DATE]>",
|
|
16732
|
+
'Date filter, e.g. "Reporting Date=2025-01-31"; repeatable',
|
|
16733
|
+
collectFlag,
|
|
16734
|
+
[]
|
|
16735
|
+
).option(
|
|
16736
|
+
"--agg <period>",
|
|
16737
|
+
"Period aggregation: DEFAULT, YTD, YTG, QTD, QTG, MTD, MTG",
|
|
16738
|
+
"DEFAULT"
|
|
16739
|
+
).option(
|
|
16740
|
+
"--columns <names>",
|
|
16741
|
+
"Comma-separated columns to print (default: the filtered fields and the value field)"
|
|
16742
|
+
).option("--limit <n>", "Rows to print; 0 = all", "20").option(
|
|
16743
|
+
"--out <file.csv>",
|
|
16744
|
+
"Write every row and column to a CSV file; a result over the inline cap is fetched through the server CSV export"
|
|
16745
|
+
).option("--csv", "Always use the server CSV export (needs --out)", false).option(
|
|
16746
|
+
"--timeout <seconds>",
|
|
16747
|
+
"Whole-command budget in seconds (default 30, or 300 with --out)"
|
|
16748
|
+
).option("--json", "Print the server body verbatim (with --out: a JSON summary)", false).action(
|
|
16749
|
+
async (idStr, opts) => {
|
|
16750
|
+
const id = parseFunctionIdArg(idStr);
|
|
16751
|
+
validatePeriodAgg(opts.agg ?? "DEFAULT");
|
|
16752
|
+
const limit = parseIntFlag(opts.limit ?? "20", "--limit");
|
|
16753
|
+
const columns = opts.columns === void 0 ? void 0 : opts.columns.split(",").map((c) => c.trim()).filter((c) => c !== "");
|
|
16754
|
+
const setupOpts = opts.timeout !== void 0 ? batchSetupOptions(opts.timeout) : opts.out !== void 0 ? { timeoutMs: DRILLDOWN_EXPORT_TIMEOUT_MS } : {};
|
|
16755
|
+
const setup = await setupCall(opts.server, opts.account, setupOpts);
|
|
16756
|
+
try {
|
|
16757
|
+
await runFunctionsDrilldown({
|
|
16758
|
+
client: setup.client,
|
|
16759
|
+
id,
|
|
16760
|
+
filters: opts.filter,
|
|
16761
|
+
excludes: opts.exclude,
|
|
16762
|
+
dates: opts.date,
|
|
16763
|
+
aggOriginal: opts.agg ?? "DEFAULT",
|
|
16764
|
+
columns,
|
|
16765
|
+
limit,
|
|
16766
|
+
outPath: opts.out,
|
|
16767
|
+
forceCsv: opts.csv === true,
|
|
16768
|
+
asJson: opts.json === true,
|
|
16769
|
+
signal: setup.signal,
|
|
16770
|
+
out: process.stdout
|
|
16771
|
+
});
|
|
16772
|
+
} finally {
|
|
16773
|
+
setup.cleanup();
|
|
16774
|
+
}
|
|
16775
|
+
}
|
|
16776
|
+
);
|
|
16777
|
+
addServerFlag(drilldownCmd);
|
|
16778
|
+
addAccountFlag(drilldownCmd);
|
|
16019
16779
|
const deleteCmd = fn.command("delete").description("Delete a function by ID (two-step confirm)").argument("<id>", "function id (positive integer)").option(
|
|
16020
16780
|
"--confirm-token <token>",
|
|
16021
16781
|
"Token from a prior plan run \u2014 required to actually delete",
|
|
@@ -16064,7 +16824,7 @@ async function get7(client, alias, options) {
|
|
|
16064
16824
|
}
|
|
16065
16825
|
return out;
|
|
16066
16826
|
}
|
|
16067
|
-
async function
|
|
16827
|
+
async function data3(client, alias, params, options) {
|
|
16068
16828
|
const path11 = `/finance-os/api/semantictables/v1/${rawPathSegmentEscape(alias)}/data`;
|
|
16069
16829
|
const body = {
|
|
16070
16830
|
limit: params.rows,
|
|
@@ -16126,7 +16886,7 @@ async function runSemanticTablesData(cfg) {
|
|
|
16126
16886
|
}
|
|
16127
16887
|
const parsedFilters = parseAggregateFilters(cfg.filters, cfg.excludes);
|
|
16128
16888
|
const select = parseSelectFields(cfg.fields);
|
|
16129
|
-
const env = await
|
|
16889
|
+
const env = await data3(
|
|
16130
16890
|
cfg.client,
|
|
16131
16891
|
cfg.alias,
|
|
16132
16892
|
{
|
|
@@ -17450,7 +18210,7 @@ var LUT_SAVE_POLL_TIMEOUT_MS = 3e5;
|
|
|
17450
18210
|
async function saveMappings(client, documentId, body, options = {}) {
|
|
17451
18211
|
const interval = options.pollIntervalMs ?? LUT_SAVE_POLL_INTERVAL_MS;
|
|
17452
18212
|
const timeout = options.timeoutMs ?? LUT_SAVE_POLL_TIMEOUT_MS;
|
|
17453
|
-
const
|
|
18213
|
+
const sleep3 = options.sleep ?? defaultPollSleep;
|
|
17454
18214
|
const now = options.now ?? Date.now;
|
|
17455
18215
|
const deadline = now() + timeout;
|
|
17456
18216
|
const path11 = `/api/documents/${documentId}/web_update_lut`;
|
|
@@ -17459,7 +18219,7 @@ async function saveMappings(client, documentId, body, options = {}) {
|
|
|
17459
18219
|
if (first.status === 200) {
|
|
17460
18220
|
return toResult(first.text);
|
|
17461
18221
|
}
|
|
17462
|
-
const cacheKey =
|
|
18222
|
+
const cacheKey = extractCacheKey2(first.text);
|
|
17463
18223
|
if (cacheKey === "") {
|
|
17464
18224
|
throw new Error(
|
|
17465
18225
|
`POST ${path11}: HTTP ${first.status} without a cache_key to join: ${truncateBody(first.text)}`
|
|
@@ -17472,7 +18232,7 @@ async function saveMappings(client, documentId, body, options = {}) {
|
|
|
17472
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.`
|
|
17473
18233
|
);
|
|
17474
18234
|
}
|
|
17475
|
-
await
|
|
18235
|
+
await sleep3(interval, options.signal);
|
|
17476
18236
|
const tick = await postOnce(client, pollPath, payload, options.signal);
|
|
17477
18237
|
if (tick.status === 200) {
|
|
17478
18238
|
return toResult(tick.text);
|
|
@@ -17487,7 +18247,7 @@ async function postOnce(client, path11, payload, signal) {
|
|
|
17487
18247
|
}
|
|
17488
18248
|
throw new Error(`POST ${path11}: HTTP ${res.status}: ${truncateBody(text2)}`);
|
|
17489
18249
|
}
|
|
17490
|
-
function
|
|
18250
|
+
function extractCacheKey2(text2) {
|
|
17491
18251
|
try {
|
|
17492
18252
|
const parsed2 = JSON.parse(text2);
|
|
17493
18253
|
return typeof parsed2.cache_key === "string" ? parsed2.cache_key : "";
|
|
@@ -17770,7 +18530,7 @@ async function exportDocument(client, id, destinationOrgIds, override, reqOption
|
|
|
17770
18530
|
async function pollDocumentJob(client, basePath, label, requestId, options = {}) {
|
|
17771
18531
|
const interval = options.pollIntervalMs ?? AUTO_CALC_POLL_INTERVAL_MS;
|
|
17772
18532
|
const timeout = options.timeoutMs ?? AUTO_CALC_POLL_TIMEOUT_MS;
|
|
17773
|
-
const
|
|
18533
|
+
const sleep3 = options.sleep ?? defaultSleep3;
|
|
17774
18534
|
const now = options.now ?? Date.now;
|
|
17775
18535
|
const deadline = now() + timeout;
|
|
17776
18536
|
for (; ; ) {
|
|
@@ -17798,7 +18558,7 @@ async function pollDocumentJob(client, basePath, label, requestId, options = {})
|
|
|
17798
18558
|
`${label} polling timed out after ${timeout}ms (request ${requestId} still processing)`
|
|
17799
18559
|
);
|
|
17800
18560
|
}
|
|
17801
|
-
await
|
|
18561
|
+
await sleep3(interval, options.signal);
|
|
17802
18562
|
}
|
|
17803
18563
|
}
|
|
17804
18564
|
|
|
@@ -18269,14 +19029,14 @@ async function runLutData(cfg) {
|
|
|
18269
19029
|
if (cfg.lutId <= 0) {
|
|
18270
19030
|
throw new Error("lut id must be positive");
|
|
18271
19031
|
}
|
|
18272
|
-
const
|
|
19032
|
+
const data4 = await getMappedData(cfg.client, cfg.documentId, cfg.lutId, {
|
|
18273
19033
|
signal: cfg.signal
|
|
18274
19034
|
});
|
|
18275
19035
|
if (cfg.asJson) {
|
|
18276
|
-
writeJSONIndented(cfg.out,
|
|
19036
|
+
writeJSONIndented(cfg.out, data4);
|
|
18277
19037
|
return;
|
|
18278
19038
|
}
|
|
18279
|
-
const keys2 = Object.keys(
|
|
19039
|
+
const keys2 = Object.keys(data4);
|
|
18280
19040
|
if (keys2.length === 0) {
|
|
18281
19041
|
cfg.out.write(`(LUT ${cfg.lutId} on document ${cfg.documentId} is empty or unmapped)
|
|
18282
19042
|
`);
|
|
@@ -18285,7 +19045,7 @@ async function runLutData(cfg) {
|
|
|
18285
19045
|
const sortedKeys = [...keys2].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
18286
19046
|
const rows = [{ cells: ["KEY", "VALUE"] }];
|
|
18287
19047
|
for (const k of sortedKeys) {
|
|
18288
|
-
rows.push({ cells: [k, renderLutValue(
|
|
19048
|
+
rows.push({ cells: [k, renderLutValue(data4[k])] });
|
|
18289
19049
|
}
|
|
18290
19050
|
writeTable15(cfg.out, rows);
|
|
18291
19051
|
cfg.out.write(`
|
|
@@ -20376,14 +21136,14 @@ function renderDataflow(out, items) {
|
|
|
20376
21136
|
];
|
|
20377
21137
|
const group = groups[col] ?? [];
|
|
20378
21138
|
for (const it of group) {
|
|
20379
|
-
const
|
|
21139
|
+
const data4 = extractData(it.data);
|
|
20380
21140
|
rows.push({
|
|
20381
21141
|
cells: [
|
|
20382
21142
|
String(it.order),
|
|
20383
21143
|
it.type,
|
|
20384
|
-
String(
|
|
20385
|
-
|
|
20386
|
-
|
|
21144
|
+
String(data4.entityId ?? 0),
|
|
21145
|
+
data4.name ?? "",
|
|
21146
|
+
data4.statusType ?? "",
|
|
20387
21147
|
String(it.connections.length),
|
|
20388
21148
|
String(it.children.length)
|
|
20389
21149
|
]
|
|
@@ -20484,7 +21244,7 @@ async function getData2(client, params, options) {
|
|
|
20484
21244
|
}
|
|
20485
21245
|
return out;
|
|
20486
21246
|
}
|
|
20487
|
-
async function
|
|
21247
|
+
async function drilldown2(client, params, options) {
|
|
20488
21248
|
const path11 = "/finance-os/api/metrics/v1/drill_down";
|
|
20489
21249
|
const body = {
|
|
20490
21250
|
metric_ids: params.metric_ids
|
|
@@ -21023,7 +21783,7 @@ async function waitForReAggregation(cfg) {
|
|
|
21023
21783
|
let interval = cfg.pollIntervalMs ?? REAGG_POLL_INTERVAL_MS;
|
|
21024
21784
|
const maxInterval = cfg.maxPollIntervalMs ?? REAGG_POLL_MAX_INTERVAL_MS;
|
|
21025
21785
|
const timeout = cfg.timeoutMs ?? REAGG_POLL_TIMEOUT_MS;
|
|
21026
|
-
const
|
|
21786
|
+
const sleep3 = cfg.sleep ?? defaultSleep4;
|
|
21027
21787
|
const now = cfg.now ?? Date.now;
|
|
21028
21788
|
const accepted = [...cfg.accepted];
|
|
21029
21789
|
const observedProcessing = /* @__PURE__ */ new Set();
|
|
@@ -21058,7 +21818,7 @@ async function waitForReAggregation(cfg) {
|
|
|
21058
21818
|
break;
|
|
21059
21819
|
}
|
|
21060
21820
|
try {
|
|
21061
|
-
await
|
|
21821
|
+
await sleep3(interval, cfg.signal);
|
|
21062
21822
|
} catch {
|
|
21063
21823
|
aborted = true;
|
|
21064
21824
|
break;
|
|
@@ -21750,8 +22510,8 @@ function detectApiTruncation(env) {
|
|
|
21750
22510
|
}
|
|
21751
22511
|
return false;
|
|
21752
22512
|
}
|
|
21753
|
-
function readCountUniqueRows(
|
|
21754
|
-
const rows = Array.isArray(
|
|
22513
|
+
function readCountUniqueRows(data4, field) {
|
|
22514
|
+
const rows = Array.isArray(data4) ? data4 : null;
|
|
21755
22515
|
if (rows === null) return null;
|
|
21756
22516
|
let best = null;
|
|
21757
22517
|
for (const row of rows) {
|
|
@@ -23665,7 +24425,7 @@ async function waitForSuggestionRun(r, cfg, runId) {
|
|
|
23665
24425
|
let interval = cfg.pollIntervalMs ?? SUGGESTIONS_POLL_INTERVAL_MS;
|
|
23666
24426
|
const maxInterval = cfg.maxPollIntervalMs ?? SUGGESTIONS_POLL_MAX_INTERVAL_MS;
|
|
23667
24427
|
const timeout = cfg.timeoutMs ?? SUGGESTIONS_POLL_TIMEOUT_MS;
|
|
23668
|
-
const
|
|
24428
|
+
const sleep3 = cfg.sleep ?? defaultSleep5;
|
|
23669
24429
|
const now = cfg.now ?? Date.now;
|
|
23670
24430
|
const errOut = cfg.errOut ?? process.stderr;
|
|
23671
24431
|
const start = now();
|
|
@@ -23695,7 +24455,7 @@ async function waitForSuggestionRun(r, cfg, runId) {
|
|
|
23695
24455
|
break;
|
|
23696
24456
|
}
|
|
23697
24457
|
try {
|
|
23698
|
-
await
|
|
24458
|
+
await sleep3(interval, cfg.signal);
|
|
23699
24459
|
} catch {
|
|
23700
24460
|
aborted = true;
|
|
23701
24461
|
break;
|
|
@@ -25303,12 +26063,12 @@ async function runMetricsList(cfg) {
|
|
|
25303
26063
|
if (cfg.raw) {
|
|
25304
26064
|
const env2 = await listRaw2(cfg.client, { signal: cfg.signal });
|
|
25305
26065
|
envelopeErr(env2.success, env2.error);
|
|
25306
|
-
const
|
|
26066
|
+
const data4 = decodeHtmlEntitiesDeep(env2.data);
|
|
25307
26067
|
if (cfg.asJson) {
|
|
25308
|
-
writeJSONIndented(cfg.out,
|
|
26068
|
+
writeJSONIndented(cfg.out, data4);
|
|
25309
26069
|
return;
|
|
25310
26070
|
}
|
|
25311
|
-
renderMetricsRawTable(cfg.out,
|
|
26071
|
+
renderMetricsRawTable(cfg.out, data4);
|
|
25312
26072
|
return;
|
|
25313
26073
|
}
|
|
25314
26074
|
const env = await listSemantic(cfg.client, {
|
|
@@ -25406,7 +26166,7 @@ async function runMetricsDrilldown(cfg) {
|
|
|
25406
26166
|
throw new Error(`--id list capped at 50 (got ${cfg.ids.length})`);
|
|
25407
26167
|
}
|
|
25408
26168
|
const dimFilters = parseDimensionFilters(cfg.filters);
|
|
25409
|
-
const env = await
|
|
26169
|
+
const env = await drilldown2(
|
|
25410
26170
|
cfg.client,
|
|
25411
26171
|
{
|
|
25412
26172
|
metric_ids: cfg.ids,
|
|
@@ -25853,83 +26613,13 @@ async function getTemplateFields(client, templateId, options) {
|
|
|
25853
26613
|
}
|
|
25854
26614
|
return result;
|
|
25855
26615
|
}
|
|
25856
|
-
async function putDataOnce(client, path11, body, options) {
|
|
25857
|
-
const res = await client.do("PUT", path11, body, options);
|
|
25858
|
-
const text2 = await res.text();
|
|
25859
|
-
if (res.status === 401 || res.status === 403) {
|
|
25860
|
-
throw new Error(`PUT ${path11}: HTTP ${res.status}: ${truncate5(text2, 200)}`);
|
|
25861
|
-
}
|
|
25862
|
-
if (res.status !== 200 && res.status !== 409 && (res.status < 200 || res.status >= 300)) {
|
|
25863
|
-
throw new Error(`PUT ${path11}: HTTP ${res.status}: ${truncate5(text2, 200)}`);
|
|
25864
|
-
}
|
|
25865
|
-
if (text2 === "" || text2 === '""') {
|
|
25866
|
-
return { status: res.status, parsed: null };
|
|
25867
|
-
}
|
|
25868
|
-
try {
|
|
25869
|
-
return { status: res.status, parsed: JSON.parse(text2) };
|
|
25870
|
-
} catch (err) {
|
|
25871
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
25872
|
-
throw new Error(`PUT ${path11}: decode: ${msg}`);
|
|
25873
|
-
}
|
|
25874
|
-
}
|
|
25875
|
-
var WIDGET_DATA_POLL_INTERVAL_MS = 500;
|
|
25876
|
-
var defaultSleep6 = (ms, signal) => new Promise((resolve3, reject) => {
|
|
25877
|
-
if (signal?.aborted) {
|
|
25878
|
-
reject(signal.reason ?? new Error("aborted"));
|
|
25879
|
-
return;
|
|
25880
|
-
}
|
|
25881
|
-
const onAbort = () => {
|
|
25882
|
-
clearTimeout(t);
|
|
25883
|
-
reject(signal?.reason ?? new Error("aborted"));
|
|
25884
|
-
};
|
|
25885
|
-
const t = setTimeout(() => {
|
|
25886
|
-
signal?.removeEventListener("abort", onAbort);
|
|
25887
|
-
resolve3();
|
|
25888
|
-
}, ms);
|
|
25889
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
25890
|
-
});
|
|
25891
26616
|
async function putDataWithPolling(client, dashboardId, id, payload, options = {}) {
|
|
25892
|
-
|
|
25893
|
-
|
|
25894
|
-
|
|
25895
|
-
|
|
25896
|
-
|
|
25897
|
-
|
|
25898
|
-
return first.parsed;
|
|
25899
|
-
}
|
|
25900
|
-
const task = first.parsed !== null && typeof first.parsed === "object" ? first.parsed : {};
|
|
25901
|
-
const cacheKey = typeof task["cache_key"] === "string" ? task["cache_key"] : "";
|
|
25902
|
-
if (cacheKey === "") {
|
|
25903
|
-
throw new Error(
|
|
25904
|
-
`PUT ${path11}: 409 without cache_key: ${truncate5(JSON.stringify(first.parsed ?? ""), 200)}`
|
|
25905
|
-
);
|
|
25906
|
-
}
|
|
25907
|
-
const pollPath = `${path11}?cache_key=${encodeURIComponent(cacheKey)}`;
|
|
25908
|
-
for (; ; ) {
|
|
25909
|
-
try {
|
|
25910
|
-
await sleep4(interval, options.signal);
|
|
25911
|
-
} catch (err) {
|
|
25912
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
25913
|
-
throw new Error(`widget data polling timed out: ${reason}`);
|
|
25914
|
-
}
|
|
25915
|
-
let result;
|
|
25916
|
-
try {
|
|
25917
|
-
result = await putDataOnce(client, pollPath, body, options);
|
|
25918
|
-
} catch (err) {
|
|
25919
|
-
if (options.signal?.aborted) {
|
|
25920
|
-
const reason = options.signal.reason instanceof Error ? options.signal.reason.message : String(options.signal.reason ?? "aborted");
|
|
25921
|
-
throw new Error(`widget data polling timed out: ${reason}`);
|
|
25922
|
-
}
|
|
25923
|
-
throw err;
|
|
25924
|
-
}
|
|
25925
|
-
if (result.status === 200) {
|
|
25926
|
-
return result.parsed;
|
|
25927
|
-
}
|
|
25928
|
-
}
|
|
25929
|
-
}
|
|
25930
|
-
function truncate5(s, n) {
|
|
25931
|
-
if (s.length <= n) return s;
|
|
25932
|
-
return s.slice(0, n) + "\u2026";
|
|
26617
|
+
return putWithCacheKeyPolling(
|
|
26618
|
+
client,
|
|
26619
|
+
`/api/dashboards/${dashboardId}/widgets/${id}/data`,
|
|
26620
|
+
payload,
|
|
26621
|
+
{ ...options, label: "widget data" }
|
|
26622
|
+
);
|
|
25933
26623
|
}
|
|
25934
26624
|
|
|
25935
26625
|
// src/commands/_shellQuote.ts
|
|
@@ -29761,9 +30451,6 @@ function registerSkillCommand(root) {
|
|
|
29761
30451
|
});
|
|
29762
30452
|
}
|
|
29763
30453
|
|
|
29764
|
-
// src/commands/datamappers.ts
|
|
29765
|
-
import { setTimeout as sleep3 } from "timers/promises";
|
|
29766
|
-
|
|
29767
30454
|
// src/api/datamappers.ts
|
|
29768
30455
|
async function list20(client, tableId, options) {
|
|
29769
30456
|
const out = await client.getJSON(`/api/templates/${tableId}/mappers`, options);
|
|
@@ -29797,14 +30484,6 @@ async function create12(client, tableId, body, options) {
|
|
|
29797
30484
|
}
|
|
29798
30485
|
return out;
|
|
29799
30486
|
}
|
|
29800
|
-
async function update8(client, id, body, options) {
|
|
29801
|
-
const path11 = `/api/templates/mappers/${id}`;
|
|
29802
|
-
const out = await client.putJSON(path11, body, options);
|
|
29803
|
-
if (out === null) {
|
|
29804
|
-
throw new Error(`PUT ${path11}: empty response from datamapper update`);
|
|
29805
|
-
}
|
|
29806
|
-
return out;
|
|
29807
|
-
}
|
|
29808
30487
|
async function deleteById12(client, tableId, id, options) {
|
|
29809
30488
|
await client.deleteJSON(`/api/templates/${tableId}/mappers/${id}`, options);
|
|
29810
30489
|
}
|
|
@@ -30006,16 +30685,16 @@ function validateCalcFields(entries) {
|
|
|
30006
30685
|
if (isLikelyValidCalcFormula(entry.formula)) continue;
|
|
30007
30686
|
const name = entry.name === "" ? "(unnamed)" : entry.name;
|
|
30008
30687
|
const trimmed = entry.formula.trim();
|
|
30009
|
-
const
|
|
30010
|
-
throw new Error(formatCalcFieldError(name,
|
|
30688
|
+
const preview2 = trimmed.slice(0, 80);
|
|
30689
|
+
throw new Error(formatCalcFieldError(name, preview2, trimmed));
|
|
30011
30690
|
}
|
|
30012
30691
|
const arityViolations = findArityViolations(entries);
|
|
30013
30692
|
if (arityViolations.length > 0) {
|
|
30014
30693
|
throw new Error(formatArityViolation(arityViolations[0]));
|
|
30015
30694
|
}
|
|
30016
30695
|
}
|
|
30017
|
-
function formatCalcFieldError(name,
|
|
30018
|
-
const head = `calculated_fields["${name}"]: formula "${
|
|
30696
|
+
function formatCalcFieldError(name, preview2, full) {
|
|
30697
|
+
const head = `calculated_fields["${name}"]: formula "${preview2}"`;
|
|
30019
30698
|
if (full === "") {
|
|
30020
30699
|
return `${head} is empty \u2014 every calc field needs a formula.`;
|
|
30021
30700
|
}
|
|
@@ -30528,34 +31207,69 @@ async function runDatamappersUpdate(cfg) {
|
|
|
30528
31207
|
"merge"
|
|
30529
31208
|
);
|
|
30530
31209
|
}
|
|
31210
|
+
const err = cfg.err ?? process.stderr;
|
|
30531
31211
|
const { decoded: decodedBody, changeCount } = decodeBodyEntitiesDeep(body);
|
|
30532
31212
|
if (changeCount > 0) {
|
|
30533
|
-
|
|
30534
|
-
`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.
|
|
30535
31215
|
`
|
|
30536
31216
|
);
|
|
30537
31217
|
body = decodedBody;
|
|
30538
31218
|
}
|
|
30539
31219
|
validateCalcFields(extractCalcFields(body));
|
|
30540
|
-
const
|
|
30541
|
-
const
|
|
30542
|
-
|
|
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;
|
|
30543
31259
|
try {
|
|
30544
|
-
|
|
30545
|
-
signal: cfg.signal
|
|
30546
|
-
});
|
|
31260
|
+
published = await create12(cfg.client, templateId, body, { signal: cfg.signal });
|
|
30547
31261
|
} catch (err2) {
|
|
30548
31262
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
30549
31263
|
if (/HTTP 500/.test(msg)) {
|
|
30550
|
-
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.";
|
|
30551
31265
|
const responseBody = extractResponseBodyFromError(msg);
|
|
30552
31266
|
let logPath;
|
|
30553
31267
|
try {
|
|
30554
31268
|
logPath = await writeErrorLog({
|
|
30555
31269
|
command: `dr datamappers update ${cfg.id} --payload-file ${payloadFile}`,
|
|
30556
31270
|
server: cfg.serverFlag ?? "",
|
|
30557
|
-
method: "
|
|
30558
|
-
url:
|
|
31271
|
+
method: "POST",
|
|
31272
|
+
url: publishPath,
|
|
30559
31273
|
status: 500,
|
|
30560
31274
|
requestBody: body,
|
|
30561
31275
|
responseBody,
|
|
@@ -30574,103 +31288,26 @@ ${hint}${logSuffix}`);
|
|
|
30574
31288
|
throw err2;
|
|
30575
31289
|
}
|
|
30576
31290
|
if (cfg.asJson) {
|
|
30577
|
-
writeJSONIndented(cfg.out,
|
|
31291
|
+
writeJSONIndented(cfg.out, published);
|
|
30578
31292
|
} else {
|
|
30579
|
-
renderMappersTable(cfg.out, [
|
|
30580
|
-
}
|
|
30581
|
-
if (scanBaseline !== void 0) {
|
|
30582
|
-
const opts = cfg.verifyScanDispatch ?? {};
|
|
30583
|
-
const started2 = await waitForNewScan(cfg.client, scanBaseline, cfg.signal, opts);
|
|
30584
|
-
if (!started2) {
|
|
30585
|
-
const docIds = await mappedDocumentIds(
|
|
30586
|
-
cfg.client,
|
|
30587
|
-
scanBaseline.templateId,
|
|
30588
|
-
cfg.id,
|
|
30589
|
-
cfg.signal
|
|
30590
|
-
);
|
|
30591
|
-
err.write(
|
|
30592
|
-
formatNoScanWarning({
|
|
30593
|
-
mapperId: cfg.id,
|
|
30594
|
-
templateId: scanBaseline.templateId,
|
|
30595
|
-
timeoutMs: opts.timeoutMs ?? DEFAULT_SCAN_DISPATCH_TIMEOUT_MS,
|
|
30596
|
-
docIds
|
|
30597
|
-
})
|
|
30598
|
-
);
|
|
30599
|
-
}
|
|
30600
|
-
}
|
|
30601
|
-
}
|
|
30602
|
-
var DEFAULT_SCAN_DISPATCH_TIMEOUT_MS = 3e4;
|
|
30603
|
-
var DEFAULT_SCAN_DISPATCH_POLL_MS = 2e3;
|
|
30604
|
-
function scanFingerprint(report) {
|
|
30605
|
-
let maxScanId = 0;
|
|
30606
|
-
for (const doc of report.documents ?? []) {
|
|
30607
|
-
for (const v of doc.versions ?? []) {
|
|
30608
|
-
if (typeof v.scan_id === "number" && v.scan_id > maxScanId) maxScanId = v.scan_id;
|
|
30609
|
-
}
|
|
30610
|
-
}
|
|
30611
|
-
return `${report.scans_status?.last_updated ?? ""}|${maxScanId}`;
|
|
30612
|
-
}
|
|
30613
|
-
async function captureScanBaseline(client, mapperId, signal, err) {
|
|
30614
|
-
try {
|
|
30615
|
-
const details = await getDetails2(client, mapperId, { signal });
|
|
30616
|
-
const templateId = details.template.id;
|
|
30617
|
-
const report = await getStatus(client, templateId, { signal });
|
|
30618
|
-
return { templateId, fingerprint: scanFingerprint(report) };
|
|
30619
|
-
} catch (e) {
|
|
30620
|
-
if (signal.aborted) throw e;
|
|
30621
|
-
err.write(
|
|
30622
|
-
"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"
|
|
30623
|
-
);
|
|
30624
|
-
return void 0;
|
|
30625
|
-
}
|
|
30626
|
-
}
|
|
30627
|
-
async function waitForNewScan(client, baseline, signal, opts) {
|
|
30628
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_SCAN_DISPATCH_TIMEOUT_MS;
|
|
30629
|
-
const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_SCAN_DISPATCH_POLL_MS;
|
|
30630
|
-
const deadline = Date.now() + timeoutMs;
|
|
30631
|
-
for (; ; ) {
|
|
30632
|
-
try {
|
|
30633
|
-
const report = await getStatus(client, baseline.templateId, { signal });
|
|
30634
|
-
if (scanFingerprint(report) !== baseline.fingerprint) return true;
|
|
30635
|
-
} catch (e) {
|
|
30636
|
-
if (signal.aborted) throw e;
|
|
30637
|
-
}
|
|
30638
|
-
const remaining = deadline - Date.now();
|
|
30639
|
-
if (remaining <= 0) return false;
|
|
30640
|
-
await sleep3(Math.min(pollIntervalMs, remaining), void 0, { signal });
|
|
30641
|
-
}
|
|
30642
|
-
}
|
|
30643
|
-
async function mappedDocumentIds(client, templateId, mapperId, signal) {
|
|
30644
|
-
try {
|
|
30645
|
-
const mapper = await get14(client, templateId, mapperId, { signal });
|
|
30646
|
-
return Array.isArray(mapper.mapped_documents_ids) ? mapper.mapped_documents_ids : void 0;
|
|
30647
|
-
} catch (e) {
|
|
30648
|
-
if (signal.aborted) throw e;
|
|
30649
|
-
return void 0;
|
|
31293
|
+
renderMappersTable(cfg.out, [published]);
|
|
30650
31294
|
}
|
|
30651
|
-
|
|
30652
|
-
|
|
30653
|
-
|
|
30654
|
-
|
|
30655
|
-
|
|
30656
|
-
|
|
30657
|
-
|
|
30658
|
-
|
|
30659
|
-
|
|
30660
|
-
|
|
30661
|
-
|
|
30662
|
-
|
|
30663
|
-
lines.push(
|
|
30664
|
-
" 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
|
|
30665
31307
|
);
|
|
30666
|
-
|
|
30667
|
-
|
|
30668
|
-
` dr datamappers get ${p.mapperId} --table ${p.templateId} --json # read mapped_documents_ids`
|
|
30669
|
-
);
|
|
30670
|
-
}
|
|
30671
|
-
lines.push(` dr datamappers bind ${p.mapperId} ${docFlags} --rescan latest`);
|
|
31308
|
+
const timeoutMs = cfg.verifyCalcFieldsOpts?.timeoutMs ?? 3e4;
|
|
31309
|
+
err.write(formatVerifyResult(templateId, result, timeoutMs));
|
|
30672
31310
|
}
|
|
30673
|
-
return lines.join("\n") + "\n";
|
|
30674
31311
|
}
|
|
30675
31312
|
async function buildConveniencePayload(client, tableId, name, versionId, sheet, flags, signal) {
|
|
30676
31313
|
const fieldTypes = parseFieldOverrides(flags.fieldOverrides);
|
|
@@ -31283,8 +31920,12 @@ function registerDatamappersCommand(root) {
|
|
|
31283
31920
|
addServerFlag(fromFileCmd);
|
|
31284
31921
|
addAccountFlag(fromFileCmd);
|
|
31285
31922
|
const updateCmd = dm.command("update").description(
|
|
31286
|
-
|
|
31287
|
-
).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(
|
|
31288
31929
|
"--field <NAME:TYPE>",
|
|
31289
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.',
|
|
31290
31931
|
collectArrayOpt,
|
|
@@ -31306,8 +31947,7 @@ function registerDatamappersCommand(root) {
|
|
|
31306
31947
|
serverFlag: opts.server ?? "",
|
|
31307
31948
|
asJson: opts.json === true,
|
|
31308
31949
|
signal: setup.signal,
|
|
31309
|
-
out: process.stdout
|
|
31310
|
-
verifyScanDispatch: {}
|
|
31950
|
+
out: process.stdout
|
|
31311
31951
|
});
|
|
31312
31952
|
} finally {
|
|
31313
31953
|
setup.cleanup();
|
|
@@ -33042,7 +33682,7 @@ async function create15(client, body, reqOptions) {
|
|
|
33042
33682
|
}
|
|
33043
33683
|
return out;
|
|
33044
33684
|
}
|
|
33045
|
-
async function
|
|
33685
|
+
async function update8(client, id, body, reqOptions) {
|
|
33046
33686
|
const path11 = `/api/v3/data_integrations/${id}/`;
|
|
33047
33687
|
const out = await client.putJSON(path11, body, reqOptions);
|
|
33048
33688
|
if (out === null) {
|
|
@@ -34802,7 +35442,7 @@ async function captureDataQuerySyncBaseline(client, fileboxId, periods, signal)
|
|
|
34802
35442
|
);
|
|
34803
35443
|
}
|
|
34804
35444
|
}
|
|
34805
|
-
var
|
|
35445
|
+
var defaultSleep6 = (ms, signal) => new Promise((resolve3, reject) => {
|
|
34806
35446
|
if (signal?.aborted) {
|
|
34807
35447
|
reject(signal.reason ?? new Error("aborted"));
|
|
34808
35448
|
return;
|
|
@@ -34820,7 +35460,7 @@ var defaultSleep7 = (ms, signal) => new Promise((resolve3, reject) => {
|
|
|
34820
35460
|
async function waitForDataQuerySync(cfg) {
|
|
34821
35461
|
let interval = cfg.pollIntervalMs ?? DQ_WAIT_POLL_INTERVAL_MS;
|
|
34822
35462
|
const maxInterval = cfg.maxPollIntervalMs ?? DQ_WAIT_POLL_MAX_INTERVAL_MS;
|
|
34823
|
-
const
|
|
35463
|
+
const sleep3 = cfg.sleep ?? defaultSleep6;
|
|
34824
35464
|
const now = cfg.now ?? Date.now;
|
|
34825
35465
|
const start = now();
|
|
34826
35466
|
const deadline = start + cfg.timeoutSec * 1e3;
|
|
@@ -34862,7 +35502,7 @@ async function waitForDataQuerySync(cfg) {
|
|
|
34862
35502
|
break;
|
|
34863
35503
|
}
|
|
34864
35504
|
try {
|
|
34865
|
-
await
|
|
35505
|
+
await sleep3(interval, cfg.signal);
|
|
34866
35506
|
} catch {
|
|
34867
35507
|
outcome = "cancelled";
|
|
34868
35508
|
break;
|
|
@@ -36173,7 +36813,7 @@ async function runFileboxSetVersionFileStatus(cfg) {
|
|
|
36173
36813
|
throw new Error("version id must be positive");
|
|
36174
36814
|
}
|
|
36175
36815
|
const err = cfg.err ?? process.stderr;
|
|
36176
|
-
const
|
|
36816
|
+
const sleep3 = cfg.sleep ?? defaultCycleTagSleep;
|
|
36177
36817
|
const settleMs = cfg.tagSettleDelayMs ?? CYCLE_TAG_SETTLE_DELAY_MS;
|
|
36178
36818
|
const details = await getDetails(cfg.client, cfg.docId, { signal: cfg.signal });
|
|
36179
36819
|
const tag = (details.tags_config ?? []).find(isFileStatusTag);
|
|
@@ -36266,7 +36906,7 @@ async function runFileboxSetVersionFileStatus(cfg) {
|
|
|
36266
36906
|
}
|
|
36267
36907
|
};
|
|
36268
36908
|
const readBack = async () => {
|
|
36269
|
-
await
|
|
36909
|
+
await sleep3(settleMs, cfg.signal);
|
|
36270
36910
|
return readVersionForConfirm(cfg.client, cfg.versionId, cfg.docId, cfg.signal);
|
|
36271
36911
|
};
|
|
36272
36912
|
await write();
|
|
@@ -38581,7 +39221,7 @@ function syncCallTimeoutMs(waitMs) {
|
|
|
38581
39221
|
if (waitMs <= 0) return void 0;
|
|
38582
39222
|
return waitMs + SYNC_SETUP_OVERHEAD_MS;
|
|
38583
39223
|
}
|
|
38584
|
-
var
|
|
39224
|
+
var defaultSleep7 = (ms, signal) => new Promise((resolve3, reject) => {
|
|
38585
39225
|
if (signal?.aborted) {
|
|
38586
39226
|
reject(signal.reason ?? new Error("aborted"));
|
|
38587
39227
|
return;
|
|
@@ -38601,7 +39241,7 @@ async function runIntegrationsSync(cfg) {
|
|
|
38601
39241
|
throw new Error("integration id must be positive");
|
|
38602
39242
|
}
|
|
38603
39243
|
const interval = cfg.pollIntervalMs ?? INTEGRATIONS_SYNC_POLL_INTERVAL_MS;
|
|
38604
|
-
const
|
|
39244
|
+
const sleep3 = cfg.sleep ?? defaultSleep7;
|
|
38605
39245
|
const resp = await resync(cfg.client, cfg.id, {
|
|
38606
39246
|
signal: cfg.signal
|
|
38607
39247
|
});
|
|
@@ -38650,7 +39290,7 @@ async function runIntegrationsSync(cfg) {
|
|
|
38650
39290
|
return;
|
|
38651
39291
|
}
|
|
38652
39292
|
try {
|
|
38653
|
-
await
|
|
39293
|
+
await sleep3(interval, inner.signal);
|
|
38654
39294
|
} catch {
|
|
38655
39295
|
cfg.out.write(
|
|
38656
39296
|
`Timed out waiting for sync to finish \u2014 last status was ${orDash2(lastStatus)}
|
|
@@ -38740,7 +39380,7 @@ async function runIntegrationsUpdate(cfg) {
|
|
|
38740
39380
|
throw new Error("--payload-file is required");
|
|
38741
39381
|
}
|
|
38742
39382
|
const body = await readJsonPayload(pf);
|
|
38743
|
-
const updated = await
|
|
39383
|
+
const updated = await update8(cfg.client, cfg.id, body, {
|
|
38744
39384
|
signal: cfg.signal
|
|
38745
39385
|
});
|
|
38746
39386
|
if (cfg.asJson) {
|
|
@@ -44274,7 +44914,7 @@ async function runReconciliationAccountsUpdateSettings(cfg) {
|
|
|
44274
44914
|
const current = await getAccountSettings(cfg.client, {}, { signal: cfg.signal });
|
|
44275
44915
|
const currentByKey = new Map(current.map((s) => [settingsKey(s), s]));
|
|
44276
44916
|
const changed = [];
|
|
44277
|
-
const
|
|
44917
|
+
const preview2 = [];
|
|
44278
44918
|
for (const want of cfg.settings) {
|
|
44279
44919
|
const have = currentByKey.get(settingsKey(want));
|
|
44280
44920
|
let what;
|
|
@@ -44286,8 +44926,8 @@ async function runReconciliationAccountsUpdateSettings(cfg) {
|
|
|
44286
44926
|
what = diffs.join(", ");
|
|
44287
44927
|
}
|
|
44288
44928
|
changed.push(want);
|
|
44289
|
-
if (
|
|
44290
|
-
|
|
44929
|
+
if (preview2.length < 5) {
|
|
44930
|
+
preview2.push(` ${want.entity ?? ""} / ${want.account_group ?? ""} / ${want.name}: ${what}`);
|
|
44291
44931
|
}
|
|
44292
44932
|
}
|
|
44293
44933
|
const unchanged = n - changed.length;
|
|
@@ -44314,10 +44954,10 @@ async function runReconciliationAccountsUpdateSettings(cfg) {
|
|
|
44314
44954
|
`unchanged: ${unchanged} (already match the tenant \u2014 skipped)`,
|
|
44315
44955
|
`by frequency: ${[...byFrequency].map(([f, c]) => `${f} ${c}`).join(", ")}`,
|
|
44316
44956
|
"changes:",
|
|
44317
|
-
...
|
|
44957
|
+
...preview2
|
|
44318
44958
|
];
|
|
44319
|
-
if (changed.length >
|
|
44320
|
-
lines.push(` \u2026 (+${changed.length -
|
|
44959
|
+
if (changed.length > preview2.length) {
|
|
44960
|
+
lines.push(` \u2026 (+${changed.length - preview2.length} more)`);
|
|
44321
44961
|
}
|
|
44322
44962
|
return lines;
|
|
44323
44963
|
},
|
|
@@ -44743,16 +45383,16 @@ verify the date matches the accounts' reporting period (try \`dr reconciliation
|
|
|
44743
45383
|
describe: async () => {
|
|
44744
45384
|
let signOffsReset = 0;
|
|
44745
45385
|
let bootstraps = 0;
|
|
44746
|
-
const
|
|
45386
|
+
const preview2 = [];
|
|
44747
45387
|
for (const id of accountIds) {
|
|
44748
45388
|
const account = byId.get(id);
|
|
44749
45389
|
if (account.members.length === 0) bootstraps += 1;
|
|
44750
45390
|
for (const r of changesByAccount.get(id)) {
|
|
44751
45391
|
const current = account.members.find((m) => m.type === r.row.type);
|
|
44752
45392
|
if (current?.is_sign_off === true) signOffsReset += 1;
|
|
44753
|
-
if (
|
|
45393
|
+
if (preview2.length < 5) {
|
|
44754
45394
|
const from = current === void 0 ? "(no slot)" : `${current.name} (user ${current.user_id})`;
|
|
44755
|
-
|
|
45395
|
+
preview2.push(
|
|
44756
45396
|
` ${account.account_name} (${id}) ${r.row.type}: ${from} \u2192 ${r.name} (user ${r.row.userId})`
|
|
44757
45397
|
);
|
|
44758
45398
|
}
|
|
@@ -44769,8 +45409,8 @@ verify the date matches the accounts' reporting period (try \`dr reconciliation
|
|
|
44769
45409
|
`no member slots yet: ${bootstraps} account${plural(bootstraps)} \u2014 both slots are initialized, the unlisted role as Unassigned`
|
|
44770
45410
|
);
|
|
44771
45411
|
}
|
|
44772
|
-
lines.push("changes:", ...
|
|
44773
|
-
if (changeCount >
|
|
45412
|
+
lines.push("changes:", ...preview2);
|
|
45413
|
+
if (changeCount > preview2.length) lines.push(` \u2026 (+${changeCount - preview2.length} more)`);
|
|
44774
45414
|
return lines;
|
|
44775
45415
|
},
|
|
44776
45416
|
execute: async () => {
|
|
@@ -44868,13 +45508,13 @@ verify the date matches the accounts' reporting period (try \`dr reconciliation
|
|
|
44868
45508
|
confirmCmd: cfg.confirmCmd,
|
|
44869
45509
|
describe: async () => {
|
|
44870
45510
|
let signOffsReset = 0;
|
|
44871
|
-
const
|
|
45511
|
+
const preview2 = [];
|
|
44872
45512
|
for (const id of accountIds) {
|
|
44873
45513
|
const account = byId.get(id);
|
|
44874
45514
|
for (const { row, current } of clearsByAccount.get(id)) {
|
|
44875
45515
|
if (current.is_sign_off) signOffsReset += 1;
|
|
44876
|
-
if (
|
|
44877
|
-
|
|
45516
|
+
if (preview2.length < 5) {
|
|
45517
|
+
preview2.push(
|
|
44878
45518
|
` ${account.account_name} (${id}) ${row.type}: ${current.name} (user ${current.user_id}) \u2192 Unassigned`
|
|
44879
45519
|
);
|
|
44880
45520
|
}
|
|
@@ -44886,9 +45526,9 @@ verify the date matches the accounts' reporting period (try \`dr reconciliation
|
|
|
44886
45526
|
`unchanged: ${unchanged.length} (slot already Unassigned, or the account has no member slots \u2014 skipped)`,
|
|
44887
45527
|
`sign-offs reset: ${signOffsReset}`,
|
|
44888
45528
|
"changes:",
|
|
44889
|
-
...
|
|
45529
|
+
...preview2
|
|
44890
45530
|
];
|
|
44891
|
-
if (clearCount >
|
|
45531
|
+
if (clearCount > preview2.length) lines.push(` \u2026 (+${clearCount - preview2.length} more)`);
|
|
44892
45532
|
return lines;
|
|
44893
45533
|
},
|
|
44894
45534
|
execute: async () => {
|
|
@@ -46090,9 +46730,9 @@ function sanitizeProse(input, maxLen) {
|
|
|
46090
46730
|
}
|
|
46091
46731
|
return s;
|
|
46092
46732
|
}
|
|
46093
|
-
function applyProse(
|
|
46733
|
+
function applyProse(data4, overlay, warn2 = () => {
|
|
46094
46734
|
}) {
|
|
46095
|
-
const out = JSON.parse(JSON.stringify(
|
|
46735
|
+
const out = JSON.parse(JSON.stringify(data4));
|
|
46096
46736
|
const rowProse = overlay.rowProse ?? {};
|
|
46097
46737
|
const byId = new Map(
|
|
46098
46738
|
out.rows.filter((r) => "id" in r).map((r) => [r.id, r])
|
|
@@ -46119,11 +46759,11 @@ function applyProse(data3, overlay, warn2 = () => {
|
|
|
46119
46759
|
import { readFile as readFile6 } from "fs/promises";
|
|
46120
46760
|
import { join as join12 } from "path";
|
|
46121
46761
|
var MARKER = "<!--__DATA_BLOCK__-->";
|
|
46122
|
-
function renderReportHtml(
|
|
46762
|
+
function renderReportHtml(data4, templateHtml) {
|
|
46123
46763
|
if (!templateHtml.includes(MARKER)) {
|
|
46124
46764
|
throw new Error(`report template is missing the ${MARKER} marker`);
|
|
46125
46765
|
}
|
|
46126
|
-
const json = JSON.stringify(
|
|
46766
|
+
const json = JSON.stringify(data4).replace(/</g, "\\u003c");
|
|
46127
46767
|
const block = `<script>window.__DATA__ = ${json};</script>`;
|
|
46128
46768
|
return templateHtml.replace(MARKER, block);
|
|
46129
46769
|
}
|
|
@@ -46148,25 +46788,25 @@ function validateReportType(raw) {
|
|
|
46148
46788
|
throw new Error("--report-type must be 'P&L' or 'BS'");
|
|
46149
46789
|
}
|
|
46150
46790
|
async function runSessionCreate(cfg) {
|
|
46151
|
-
const
|
|
46152
|
-
cfg.out.write(`${
|
|
46791
|
+
const data4 = await createSession(cfg.client, cfg.baseUrl, { signal: cfg.signal });
|
|
46792
|
+
cfg.out.write(`${data4.session_id ?? ""}
|
|
46153
46793
|
`);
|
|
46154
46794
|
}
|
|
46155
46795
|
async function runSessionGet(cfg) {
|
|
46156
|
-
const
|
|
46796
|
+
const data4 = await getSession(cfg.client, cfg.baseUrl, cfg.sessionId, {
|
|
46157
46797
|
signal: cfg.signal
|
|
46158
46798
|
});
|
|
46159
46799
|
if (cfg.asJson) {
|
|
46160
|
-
writeJSONIndented(cfg.out,
|
|
46800
|
+
writeJSONIndented(cfg.out, data4);
|
|
46161
46801
|
return;
|
|
46162
46802
|
}
|
|
46163
|
-
const config =
|
|
46803
|
+
const config = data4.config ?? {};
|
|
46164
46804
|
const rows = [
|
|
46165
|
-
["session_id", String(
|
|
46166
|
-
["created_at", String(
|
|
46167
|
-
["reconcile_status", String(
|
|
46168
|
-
["investigation_status", String(
|
|
46169
|
-
["reconcile_error", String(
|
|
46805
|
+
["session_id", String(data4.session_id ?? "-")],
|
|
46806
|
+
["created_at", String(data4.created_at ?? "-")],
|
|
46807
|
+
["reconcile_status", String(data4.reconcile_status ?? "-")],
|
|
46808
|
+
["investigation_status", String(data4.investigation_status ?? "-")],
|
|
46809
|
+
["reconcile_error", String(data4.reconcile_error ?? "-")],
|
|
46170
46810
|
["report_type", String(config.report_type ?? "-")],
|
|
46171
46811
|
["period", String(config.period ?? "-")]
|
|
46172
46812
|
];
|
|
@@ -46190,12 +46830,12 @@ async function runSessionDelete(cfg) {
|
|
|
46190
46830
|
server: cfg.serverFlag,
|
|
46191
46831
|
confirmCmd: `dr validate onboarding session delete ${cfg.sessionId} --server ${cfg.serverFlag}`,
|
|
46192
46832
|
describe: async () => {
|
|
46193
|
-
const
|
|
46833
|
+
const data4 = await getSession(cfg.client, cfg.baseUrl, cfg.sessionId, {
|
|
46194
46834
|
signal: cfg.signal
|
|
46195
46835
|
});
|
|
46196
46836
|
return [
|
|
46197
|
-
`created_at: ${String(
|
|
46198
|
-
`reconcile_status: ${String(
|
|
46837
|
+
`created_at: ${String(data4.created_at ?? "-")}`,
|
|
46838
|
+
`reconcile_status: ${String(data4.reconcile_status ?? "-")}`
|
|
46199
46839
|
];
|
|
46200
46840
|
},
|
|
46201
46841
|
execute: async () => {
|
|
@@ -46237,14 +46877,14 @@ async function runUpload(cfg) {
|
|
|
46237
46877
|
}
|
|
46238
46878
|
}
|
|
46239
46879
|
async function runReconcile(cfg) {
|
|
46240
|
-
const
|
|
46880
|
+
const data4 = await startReconcile(
|
|
46241
46881
|
cfg.client,
|
|
46242
46882
|
cfg.baseUrl,
|
|
46243
46883
|
cfg.sessionId,
|
|
46244
46884
|
{ report_type: cfg.reportType, period: cfg.period, csm_notes: cfg.csmNotes },
|
|
46245
46885
|
{ signal: cfg.signal }
|
|
46246
46886
|
);
|
|
46247
|
-
if (
|
|
46887
|
+
if (data4.status === "already_running") {
|
|
46248
46888
|
cfg.out.write(`reconcile already running for session ${cfg.sessionId}
|
|
46249
46889
|
`);
|
|
46250
46890
|
} else {
|
|
@@ -46255,12 +46895,12 @@ async function runReconcile(cfg) {
|
|
|
46255
46895
|
`);
|
|
46256
46896
|
}
|
|
46257
46897
|
async function runStatus(cfg) {
|
|
46258
|
-
const
|
|
46898
|
+
const data4 = await getSession(cfg.client, cfg.baseUrl, cfg.sessionId, {
|
|
46259
46899
|
signal: cfg.signal
|
|
46260
46900
|
});
|
|
46261
|
-
const recon =
|
|
46262
|
-
const invest =
|
|
46263
|
-
const err =
|
|
46901
|
+
const recon = data4.reconcile_status ?? "pending";
|
|
46902
|
+
const invest = data4.investigation_status ?? "pending";
|
|
46903
|
+
const err = data4.reconcile_error ?? null;
|
|
46264
46904
|
const summary = summaryStatus(recon, invest);
|
|
46265
46905
|
if (cfg.asJson) {
|
|
46266
46906
|
writeJSONIndented(cfg.out, {
|
|
@@ -46274,13 +46914,13 @@ async function runStatus(cfg) {
|
|
|
46274
46914
|
cfg.out.write(`reconcile: ${recon} | investigation: ${invest}
|
|
46275
46915
|
`);
|
|
46276
46916
|
if (err !== null && err !== "") {
|
|
46277
|
-
const
|
|
46278
|
-
cfg.out.write(`error: ${
|
|
46917
|
+
const preview2 = err.length <= ERROR_PREVIEW_MAX ? err : `${err.slice(0, ERROR_PREVIEW_MAX)}...`;
|
|
46918
|
+
cfg.out.write(`error: ${preview2}
|
|
46279
46919
|
`);
|
|
46280
46920
|
}
|
|
46281
46921
|
}
|
|
46282
46922
|
async function runResult(cfg) {
|
|
46283
|
-
const
|
|
46923
|
+
const data4 = await getResult(
|
|
46284
46924
|
cfg.client,
|
|
46285
46925
|
cfg.baseUrl,
|
|
46286
46926
|
cfg.sessionId,
|
|
@@ -46288,21 +46928,21 @@ async function runResult(cfg) {
|
|
|
46288
46928
|
{ signal: cfg.signal }
|
|
46289
46929
|
);
|
|
46290
46930
|
if (cfg.asJson) {
|
|
46291
|
-
writeJSONIndented(cfg.out,
|
|
46931
|
+
writeJSONIndented(cfg.out, data4);
|
|
46292
46932
|
return;
|
|
46293
46933
|
}
|
|
46294
46934
|
if (cfg.reconcile) {
|
|
46295
|
-
writeJSONIndented(cfg.out,
|
|
46935
|
+
writeJSONIndented(cfg.out, data4);
|
|
46296
46936
|
return;
|
|
46297
46937
|
}
|
|
46298
|
-
const report =
|
|
46938
|
+
const report = data4.report;
|
|
46299
46939
|
const markdown = report !== null && report !== void 0 && typeof report === "object" ? report.markdown : void 0;
|
|
46300
46940
|
if (typeof markdown === "string" && markdown !== "") {
|
|
46301
46941
|
cfg.out.write(`${markdown}
|
|
46302
46942
|
`);
|
|
46303
46943
|
return;
|
|
46304
46944
|
}
|
|
46305
|
-
writeJSONIndented(cfg.out,
|
|
46945
|
+
writeJSONIndented(cfg.out, data4);
|
|
46306
46946
|
}
|
|
46307
46947
|
async function runReport(cfg) {
|
|
46308
46948
|
let result;
|
|
@@ -46375,24 +47015,24 @@ async function runReport(cfg) {
|
|
|
46375
47015
|
tenant: cfg.org || null,
|
|
46376
47016
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
46377
47017
|
};
|
|
46378
|
-
let
|
|
47018
|
+
let data4 = buildReportData(result, meta, balanceCheck);
|
|
46379
47019
|
if (cfg.emitData) {
|
|
46380
47020
|
if (cfg.outPath) {
|
|
46381
|
-
await fs19.writeFile(cfg.outPath, JSON.stringify(
|
|
47021
|
+
await fs19.writeFile(cfg.outPath, JSON.stringify(data4, null, 2), "utf8");
|
|
46382
47022
|
cfg.out.write(`wrote data block to ${cfg.outPath}
|
|
46383
47023
|
`);
|
|
46384
47024
|
} else {
|
|
46385
|
-
writeJSONIndented(cfg.out,
|
|
47025
|
+
writeJSONIndented(cfg.out, data4);
|
|
46386
47026
|
}
|
|
46387
47027
|
return;
|
|
46388
47028
|
}
|
|
46389
47029
|
if (cfg.prosePath) {
|
|
46390
47030
|
const overlay = JSON.parse(await fs19.readFile(cfg.prosePath, "utf8"));
|
|
46391
|
-
|
|
47031
|
+
data4 = applyProse(data4, overlay, (m) => process.stderr.write(`${m}
|
|
46392
47032
|
`));
|
|
46393
47033
|
}
|
|
46394
47034
|
const template = await readReportTemplate();
|
|
46395
|
-
const html = renderReportHtml(
|
|
47035
|
+
const html = renderReportHtml(data4, template);
|
|
46396
47036
|
const outPath = cfg.outPath || `./${cfg.sessionId}-report.html`;
|
|
46397
47037
|
await fs19.writeFile(outPath, html, "utf8");
|
|
46398
47038
|
cfg.out.write(`${outPath}
|
|
@@ -49757,9 +50397,9 @@ async function runMecMemberGrantBulk(cfg) {
|
|
|
49757
50397
|
signal: cfg.signal
|
|
49758
50398
|
});
|
|
49759
50399
|
const wirePayload = { ...cfg.payload, role: role.id };
|
|
49760
|
-
let
|
|
50400
|
+
let preview2 = null;
|
|
49761
50401
|
try {
|
|
49762
|
-
|
|
50402
|
+
preview2 = await withMec(
|
|
49763
50403
|
() => grantUsersBulkCheck(cfg.client, wirePayload, { signal: cfg.signal })
|
|
49764
50404
|
);
|
|
49765
50405
|
} catch {
|
|
@@ -49778,8 +50418,8 @@ async function runMecMemberGrantBulk(cfg) {
|
|
|
49778
50418
|
"",
|
|
49779
50419
|
"WARNING: this cascades table permissions for linked LUT templates."
|
|
49780
50420
|
];
|
|
49781
|
-
if (
|
|
49782
|
-
lines.push(`pre-check response: ${JSON.stringify(
|
|
50421
|
+
if (preview2 !== null) {
|
|
50422
|
+
lines.push(`pre-check response: ${JSON.stringify(preview2).slice(0, 200)}`);
|
|
49783
50423
|
}
|
|
49784
50424
|
return lines;
|
|
49785
50425
|
},
|
|
@@ -52603,9 +53243,9 @@ async function runMemberGrantBulk(cfg) {
|
|
|
52603
53243
|
signal: cfg.signal
|
|
52604
53244
|
});
|
|
52605
53245
|
const wirePayload = { ...cfg.payload, role: role.id };
|
|
52606
|
-
let
|
|
53246
|
+
let preview2 = null;
|
|
52607
53247
|
try {
|
|
52608
|
-
|
|
53248
|
+
preview2 = await withWorkflowV2(
|
|
52609
53249
|
() => grantUsersBulkCheck(cfg.client, wirePayload, { signal: cfg.signal })
|
|
52610
53250
|
);
|
|
52611
53251
|
} catch {
|
|
@@ -52624,8 +53264,8 @@ async function runMemberGrantBulk(cfg) {
|
|
|
52624
53264
|
"",
|
|
52625
53265
|
"WARNING: this cascades table permissions for linked LUT templates."
|
|
52626
53266
|
];
|
|
52627
|
-
if (
|
|
52628
|
-
lines.push(`pre-check response: ${JSON.stringify(
|
|
53267
|
+
if (preview2 !== null) {
|
|
53268
|
+
lines.push(`pre-check response: ${JSON.stringify(preview2).slice(0, 200)}`);
|
|
52629
53269
|
}
|
|
52630
53270
|
return lines;
|
|
52631
53271
|
},
|
|
@@ -54225,12 +54865,12 @@ function parseCsvImportText(text2) {
|
|
|
54225
54865
|
`--file: unexpected CSV header \u2014 expected "${EXPECTED_HEADER5.join(",")}", got "${header.slice(0, 3).join(",")}"`
|
|
54226
54866
|
);
|
|
54227
54867
|
}
|
|
54228
|
-
const
|
|
54229
|
-
if (
|
|
54868
|
+
const data4 = rows.slice(1).filter((r) => r.some((c) => c.trim() !== ""));
|
|
54869
|
+
if (data4.length === 0) {
|
|
54230
54870
|
throw new Error("--file: CSV has no data rows (header only)");
|
|
54231
54871
|
}
|
|
54232
54872
|
const out = [];
|
|
54233
|
-
|
|
54873
|
+
data4.forEach((row, idx) => {
|
|
54234
54874
|
const lineNo = idx + 2;
|
|
54235
54875
|
const detailedRule = (row[0] ?? "").trim();
|
|
54236
54876
|
const category = (row[2] ?? "").trim();
|
|
@@ -56527,7 +57167,7 @@ async function create16(client, body, options) {
|
|
|
56527
57167
|
}
|
|
56528
57168
|
return out;
|
|
56529
57169
|
}
|
|
56530
|
-
async function
|
|
57170
|
+
async function update9(client, id, body, options) {
|
|
56531
57171
|
const out = await client.putJSON(`/api/templates/${id}/calcmodels`, body, options);
|
|
56532
57172
|
if (out === null) {
|
|
56533
57173
|
throw new Error(
|
|
@@ -56624,7 +57264,7 @@ async function runCalcmodelsUpdate(cfg) {
|
|
|
56624
57264
|
);
|
|
56625
57265
|
}
|
|
56626
57266
|
validateCalcmodelBody(body);
|
|
56627
|
-
const updated = await
|
|
57267
|
+
const updated = await update9(cfg.client, cfg.id, body, {
|
|
56628
57268
|
signal: cfg.signal
|
|
56629
57269
|
});
|
|
56630
57270
|
if (cfg.asJson) {
|
|
@@ -56671,7 +57311,7 @@ async function runCalcmodelsSetFlow(cfg) {
|
|
|
56671
57311
|
`new update_flow: ${flow}`
|
|
56672
57312
|
]),
|
|
56673
57313
|
execute: async () => {
|
|
56674
|
-
const updated = await
|
|
57314
|
+
const updated = await update9(
|
|
56675
57315
|
cfg.client,
|
|
56676
57316
|
cfg.id,
|
|
56677
57317
|
{ ...model, update_flow: flow },
|
|
@@ -56701,7 +57341,7 @@ async function runCalcmodelsRecalculate(cfg) {
|
|
|
56701
57341
|
`type: ${model.type}`
|
|
56702
57342
|
]),
|
|
56703
57343
|
execute: async () => {
|
|
56704
|
-
await
|
|
57344
|
+
await update9(cfg.client, cfg.id, model, { signal: cfg.signal });
|
|
56705
57345
|
cfg.out.write(`recalculation queued \u2014 check status via dr calcmodels get ${cfg.id}
|
|
56706
57346
|
`);
|
|
56707
57347
|
}
|
|
@@ -56967,7 +57607,7 @@ async function create17(client, templateId, body, options) {
|
|
|
56967
57607
|
}
|
|
56968
57608
|
return out;
|
|
56969
57609
|
}
|
|
56970
|
-
async function
|
|
57610
|
+
async function update10(client, templateId, calcId, body, options) {
|
|
56971
57611
|
const out = await client.putJSON(
|
|
56972
57612
|
`/api/templates/${templateId}/calc_vals/${calcId}`,
|
|
56973
57613
|
body,
|
|
@@ -57094,7 +57734,7 @@ async function runCalcValsUpdate(cfg) {
|
|
|
57094
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.`
|
|
57095
57735
|
);
|
|
57096
57736
|
}
|
|
57097
|
-
updated = await
|
|
57737
|
+
updated = await update10(cfg.client, cfg.templateId, cfg.id, body, {
|
|
57098
57738
|
signal: cfg.signal
|
|
57099
57739
|
});
|
|
57100
57740
|
} else {
|
|
@@ -57109,7 +57749,7 @@ async function runCalcValsUpdate(cfg) {
|
|
|
57109
57749
|
// `--format ""` clears the mask (→ null); otherwise keep existing.
|
|
57110
57750
|
format: cfg.format !== void 0 ? cfg.format === "" ? null : cfg.format : existing.format
|
|
57111
57751
|
};
|
|
57112
|
-
updated = await
|
|
57752
|
+
updated = await update10(cfg.client, cfg.templateId, cfg.id, body, {
|
|
57113
57753
|
signal: cfg.signal
|
|
57114
57754
|
});
|
|
57115
57755
|
}
|
|
@@ -58992,9 +59632,9 @@ async function runCpList(cfg) {
|
|
|
58992
59632
|
}
|
|
58993
59633
|
async function runCpGet(cfg) {
|
|
58994
59634
|
const bucket = await getBucket(cfg.client, cfg.id, { signal: cfg.signal });
|
|
58995
|
-
const
|
|
59635
|
+
const data4 = cfg.noCpData ? void 0 : await getCpData(cfg.client, cfg.id, { signal: cfg.signal });
|
|
58996
59636
|
if (cfg.asJson) {
|
|
58997
|
-
writeJSONIndented(cfg.out, { bucket, cp_data:
|
|
59637
|
+
writeJSONIndented(cfg.out, { bucket, cp_data: data4 ?? null });
|
|
58998
59638
|
return;
|
|
58999
59639
|
}
|
|
59000
59640
|
cfg.out.write(`id: ${bucket.id}
|
|
@@ -59007,11 +59647,11 @@ async function runCpGet(cfg) {
|
|
|
59007
59647
|
`);
|
|
59008
59648
|
cfg.out.write(`is_demo: ${bucket.is_demo}
|
|
59009
59649
|
`);
|
|
59010
|
-
if (
|
|
59011
|
-
cfg.out.write(`cp_data.owner_user_id: ${
|
|
59650
|
+
if (data4 !== void 0) {
|
|
59651
|
+
cfg.out.write(`cp_data.owner_user_id: ${data4.owner_user_id}
|
|
59012
59652
|
`);
|
|
59013
59653
|
cfg.out.write(
|
|
59014
|
-
`cp_data.level_dimension_tags: ${JSON.stringify(
|
|
59654
|
+
`cp_data.level_dimension_tags: ${JSON.stringify(data4.level_dimension_tags)}
|
|
59015
59655
|
`
|
|
59016
59656
|
);
|
|
59017
59657
|
}
|
|
@@ -60098,7 +60738,7 @@ var EXTRACT_POLL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
|
60098
60738
|
function isTerminalAiStatus(status2) {
|
|
60099
60739
|
return status2 === "PROCESSED" || status2 === "FAILED";
|
|
60100
60740
|
}
|
|
60101
|
-
async function
|
|
60741
|
+
async function defaultSleep8(ms, signal) {
|
|
60102
60742
|
if (signal?.aborted === true) return;
|
|
60103
60743
|
await new Promise((resolve3) => {
|
|
60104
60744
|
const timer = setTimeout(resolve3, ms);
|
|
@@ -60115,7 +60755,7 @@ async function defaultSleep9(ms, signal) {
|
|
|
60115
60755
|
async function pollExtractionStatus(client, contractFileIds, options = {}) {
|
|
60116
60756
|
const interval = options.pollIntervalMs ?? EXTRACT_POLL_INTERVAL_MS;
|
|
60117
60757
|
const timeout = options.timeoutMs ?? EXTRACT_POLL_TIMEOUT_MS;
|
|
60118
|
-
const
|
|
60758
|
+
const sleep3 = options.sleep ?? defaultSleep8;
|
|
60119
60759
|
const now = options.now ?? Date.now;
|
|
60120
60760
|
const resolved = /* @__PURE__ */ new Map();
|
|
60121
60761
|
if (contractFileIds.length === 0) return { resolved, pending: [] };
|
|
@@ -60165,7 +60805,7 @@ async function pollExtractionStatus(client, contractFileIds, options = {}) {
|
|
|
60165
60805
|
options.onTick?.(resolved.size, contractFileIds.length);
|
|
60166
60806
|
if (resolved.size >= contractFileIds.length) break;
|
|
60167
60807
|
if (now() > deadline || inner.signal.aborted) break;
|
|
60168
|
-
await
|
|
60808
|
+
await sleep3(interval, inner.signal);
|
|
60169
60809
|
}
|
|
60170
60810
|
} finally {
|
|
60171
60811
|
clearTimeout(deadlineTimer);
|
|
@@ -62741,16 +63381,16 @@ function describeBulkCreate(draft, contracts, batchCount, serverKey) {
|
|
|
62741
63381
|
}
|
|
62742
63382
|
if (d.category !== void 0) out.push(`default category: ${d.category}`);
|
|
62743
63383
|
}
|
|
62744
|
-
const
|
|
62745
|
-
if (
|
|
63384
|
+
const preview2 = contracts.slice(0, DESCRIBE_PREVIEW_ROWS3);
|
|
63385
|
+
if (preview2.length > 0) {
|
|
62746
63386
|
out.push("preview:");
|
|
62747
|
-
for (let i = 0; i <
|
|
62748
|
-
const c =
|
|
63387
|
+
for (let i = 0; i < preview2.length; i++) {
|
|
63388
|
+
const c = preview2[i];
|
|
62749
63389
|
const vendor = c.vendor_name !== void 0 && c.vendor_name !== null && c.vendor_name !== "" ? c.vendor_name : c.vendor_id !== void 0 && c.vendor_id !== null ? c.vendor_id : "?";
|
|
62750
63390
|
out.push(` [${i}] ${vendor} \u2014 ${c.value} ${c.currency}`);
|
|
62751
63391
|
}
|
|
62752
|
-
if (contracts.length >
|
|
62753
|
-
out.push(` \u2026 (${contracts.length -
|
|
63392
|
+
if (contracts.length > preview2.length) {
|
|
63393
|
+
out.push(` \u2026 (${contracts.length - preview2.length} more)`);
|
|
62754
63394
|
}
|
|
62755
63395
|
}
|
|
62756
63396
|
return out;
|
|
@@ -63022,7 +63662,7 @@ function registerSpendControlCommand(root) {
|
|
|
63022
63662
|
);
|
|
63023
63663
|
addServerFlag(bulkCreate);
|
|
63024
63664
|
addAccountFlag(bulkCreate);
|
|
63025
|
-
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) => {
|
|
63026
63666
|
const setup = await setupCall(opts.server, opts.account);
|
|
63027
63667
|
try {
|
|
63028
63668
|
await runContractUpdate({
|
|
@@ -63037,8 +63677,8 @@ function registerSpendControlCommand(root) {
|
|
|
63037
63677
|
setup.cleanup();
|
|
63038
63678
|
}
|
|
63039
63679
|
});
|
|
63040
|
-
addServerFlag(
|
|
63041
|
-
addAccountFlag(
|
|
63680
|
+
addServerFlag(update11);
|
|
63681
|
+
addAccountFlag(update11);
|
|
63042
63682
|
for (const spec of [
|
|
63043
63683
|
{
|
|
63044
63684
|
name: "archive",
|