@tenderprompt/cli 0.1.10 → 0.1.12
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/README.md +48 -3
- package/dist/index.js +931 -49
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -39,6 +39,14 @@ class TenderCliUsageError extends Error {
|
|
|
39
39
|
this.name = "TenderCliUsageError";
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
class TenderCliAnalyticsApiError extends Error {
|
|
43
|
+
reasonCode;
|
|
44
|
+
constructor(message, reasonCode) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.reasonCode = reasonCode;
|
|
47
|
+
this.name = "TenderCliAnalyticsApiError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
42
50
|
const DEFAULT_PROFILE_NAME = "default";
|
|
43
51
|
const DEFAULT_BASE_URL = "https://app.tenderprompt.com";
|
|
44
52
|
const ARTIFACT_SOURCE_EXPORT_MAX_FILES = 1000;
|
|
@@ -199,6 +207,16 @@ Commands:
|
|
|
199
207
|
List or create saved dashboards
|
|
200
208
|
tender app analytics charts create <app-id>
|
|
201
209
|
Create a saved chart from a chart spec
|
|
210
|
+
tender app analytics charts list <app-id>
|
|
211
|
+
List saved charts in one dashboard
|
|
212
|
+
tender app analytics charts update <app-id>
|
|
213
|
+
Update a saved chart title or spec
|
|
214
|
+
tender app analytics charts delete <app-id>
|
|
215
|
+
Delete a saved chart
|
|
216
|
+
tender app analytics properties list <app-id>
|
|
217
|
+
List properties available to dashboard queries
|
|
218
|
+
tender app analytics properties activate <app-id>
|
|
219
|
+
Make a custom event property queryable
|
|
202
220
|
tender app context fetch <app-id>
|
|
203
221
|
Fetch AGENTS.md, skills, and Tender App scaffold files
|
|
204
222
|
tender app context status <app-id>
|
|
@@ -234,6 +252,11 @@ Examples:
|
|
|
234
252
|
tender app analytics dashboards artifact_123 --json
|
|
235
253
|
tender app analytics dashboards artifact_123 --create "Agent dashboard" --json
|
|
236
254
|
tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec funnel.json --json
|
|
255
|
+
tender app analytics charts list artifact_123 --dashboard analytics_dashboard_123 --json
|
|
256
|
+
tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --spec funnel.json --dry-run --json
|
|
257
|
+
tender app analytics charts delete artifact_123 --chart analytics_chart_123 --dry-run --json
|
|
258
|
+
tender app analytics properties list artifact_123 --json
|
|
259
|
+
tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json
|
|
237
260
|
tender app context fetch artifact_123 --dir ./widget --dry-run --json
|
|
238
261
|
tender app context status artifact_123 --dir ./widget --json
|
|
239
262
|
tender app context refresh artifact_123 --dir ./widget --dry-run --json
|
|
@@ -268,6 +291,11 @@ Commands:
|
|
|
268
291
|
tender app analytics export <kind> <app-id> Export catalog metadata or query rows
|
|
269
292
|
tender app analytics charts templates Print reusable chart spec templates
|
|
270
293
|
tender app analytics charts create <app-id> Create a saved chart
|
|
294
|
+
tender app analytics charts list <app-id> List saved charts in one dashboard
|
|
295
|
+
tender app analytics charts update <app-id> Update a saved chart
|
|
296
|
+
tender app analytics charts delete <app-id> Delete a saved chart
|
|
297
|
+
tender app analytics properties list <app-id> List queryable custom event properties
|
|
298
|
+
tender app analytics properties activate <app-id> Make a property queryable
|
|
271
299
|
|
|
272
300
|
Examples:
|
|
273
301
|
tender app analytics summary artifact_123 --range 30d --json
|
|
@@ -279,7 +307,12 @@ Examples:
|
|
|
279
307
|
tender app analytics suggestions artifact_123 --range 2026-04-01..2026-04-30 --json
|
|
280
308
|
tender app analytics dashboards artifact_123 --create "Agent dashboard" --json
|
|
281
309
|
tender app analytics charts templates --json
|
|
282
|
-
tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec funnel.json --json
|
|
310
|
+
tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec funnel.json --json
|
|
311
|
+
tender app analytics charts list artifact_123 --dashboard analytics_dashboard_123 --json
|
|
312
|
+
tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --spec funnel.json --dry-run --json
|
|
313
|
+
tender app analytics charts delete artifact_123 --chart analytics_chart_123 --dry-run --json
|
|
314
|
+
tender app analytics properties list artifact_123 --json
|
|
315
|
+
tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json`;
|
|
283
316
|
}
|
|
284
317
|
function analyticsSummaryHelp() {
|
|
285
318
|
return `Usage: tender app analytics summary <app-id> [--range <7d|30d|90d|YYYY-MM-DD..YYYY-MM-DD>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
@@ -355,16 +388,59 @@ Examples:
|
|
|
355
388
|
function analyticsChartsCreateHelp() {
|
|
356
389
|
return `Usage: tender app analytics charts create <app-id> --dashboard <dashboard-id> --title <title> --spec <file|-> [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
357
390
|
|
|
391
|
+
Property charts automatically make required custom event properties queryable before
|
|
392
|
+
saving when the spec has an event_name = filter.
|
|
393
|
+
|
|
358
394
|
Examples:
|
|
359
395
|
tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec funnel.json --dry-run --json
|
|
360
396
|
tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec funnel.json --json`;
|
|
361
397
|
}
|
|
398
|
+
function analyticsChartsListHelp() {
|
|
399
|
+
return `Usage: tender app analytics charts list <app-id> --dashboard <dashboard-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
400
|
+
|
|
401
|
+
Examples:
|
|
402
|
+
tender app analytics charts list artifact_123 --dashboard analytics_dashboard_123 --json`;
|
|
403
|
+
}
|
|
404
|
+
function analyticsChartsUpdateHelp() {
|
|
405
|
+
return `Usage: tender app analytics charts update <app-id> --chart <chart-id> [--title <title>] [--spec <file|->] [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
406
|
+
|
|
407
|
+
At least one of --title or --spec is required. When one is omitted, the CLI reads
|
|
408
|
+
the existing saved chart and keeps that value.
|
|
409
|
+
Property charts automatically make required custom event properties queryable before
|
|
410
|
+
saving when the spec has an event_name = filter.
|
|
411
|
+
|
|
412
|
+
Examples:
|
|
413
|
+
tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --dry-run --json
|
|
414
|
+
tender app analytics charts update artifact_123 --chart analytics_chart_123 --spec funnel.json --dry-run --json
|
|
415
|
+
tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --spec - --json < funnel.json`;
|
|
416
|
+
}
|
|
417
|
+
function analyticsChartsDeleteHelp() {
|
|
418
|
+
return `Usage: tender app analytics charts delete <app-id> --chart <chart-id> [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
419
|
+
|
|
420
|
+
Examples:
|
|
421
|
+
tender app analytics charts delete artifact_123 --chart analytics_chart_123 --dry-run --json
|
|
422
|
+
tender app analytics charts delete artifact_123 --chart analytics_chart_123 --json`;
|
|
423
|
+
}
|
|
424
|
+
function analyticsPropertiesListHelp() {
|
|
425
|
+
return `Usage: tender app analytics properties list <app-id> [--event <event-name>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
426
|
+
|
|
427
|
+
Examples:
|
|
428
|
+
tender app analytics properties list artifact_123 --json
|
|
429
|
+
tender app analytics properties list artifact_123 --event signup_started --json`;
|
|
430
|
+
}
|
|
431
|
+
function analyticsPropertiesActivateHelp() {
|
|
432
|
+
return `Usage: tender app analytics properties activate <app-id> --event <event-name> --property <property-key> --type <dimension|metric> [--label <label>] [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
433
|
+
|
|
434
|
+
Examples:
|
|
435
|
+
tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json
|
|
436
|
+
tender app analytics properties activate artifact_123 --event score_saved --property score --type metric --json`;
|
|
437
|
+
}
|
|
362
438
|
function artifactsListHelp() {
|
|
363
439
|
return `Usage: tender app list [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
364
440
|
|
|
365
441
|
Options:
|
|
366
442
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
367
|
-
--token <token> Tender API token.
|
|
443
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
368
444
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
369
445
|
--json Emit stable JSON for coding agents.
|
|
370
446
|
|
|
@@ -381,7 +457,7 @@ Options:
|
|
|
381
457
|
--target-pack <id> Target pack id. Defaults to the API default.
|
|
382
458
|
--target-pack-version <value> Target pack version. Defaults to the API default.
|
|
383
459
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
384
|
-
--token <token> Tender API token.
|
|
460
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
385
461
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
386
462
|
--json Emit stable JSON for coding agents.
|
|
387
463
|
|
|
@@ -395,7 +471,7 @@ function artifactsUpdateHelp() {
|
|
|
395
471
|
Options:
|
|
396
472
|
--name <name> New app name.
|
|
397
473
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
398
|
-
--token <token> Tender API token.
|
|
474
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
399
475
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
400
476
|
--json Emit stable JSON for coding agents.
|
|
401
477
|
|
|
@@ -409,7 +485,7 @@ Options:
|
|
|
409
485
|
--dir <path> Local checkout directory to create/update. Defaults to current directory.
|
|
410
486
|
--remote <name> Git remote name to add or update. Defaults to tender.
|
|
411
487
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
412
|
-
--token <token> Tender API token.
|
|
488
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
413
489
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
414
490
|
--ttl <value> Deprecated for Tender-hosted Git remotes; accepted for compatibility.
|
|
415
491
|
--dry-run Show file and Git actions without writing.
|
|
@@ -427,7 +503,7 @@ function artifactsContextFetchHelp() {
|
|
|
427
503
|
Options:
|
|
428
504
|
--dir <path> Local checkout to update. Defaults to current directory.
|
|
429
505
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
430
|
-
--token <token> Tender API token.
|
|
506
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
431
507
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
432
508
|
--dry-run Show create/overwrite/conflict actions without writing files.
|
|
433
509
|
--force Overwrite conflicting managed context files.
|
|
@@ -444,7 +520,7 @@ function artifactsContextStatusHelp() {
|
|
|
444
520
|
Options:
|
|
445
521
|
--dir <path> Local checkout to inspect. Defaults to current directory.
|
|
446
522
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
447
|
-
--token <token> Tender API token.
|
|
523
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
448
524
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
449
525
|
--json Emit stable JSON for coding agents.
|
|
450
526
|
|
|
@@ -457,7 +533,7 @@ function artifactsContextRefreshHelp() {
|
|
|
457
533
|
Options:
|
|
458
534
|
--dir <path> Local checkout to update. Defaults to current directory.
|
|
459
535
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
460
|
-
--token <token> Tender API token.
|
|
536
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
461
537
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
462
538
|
--dry-run Show create/overwrite/conflict actions without writing files.
|
|
463
539
|
--force Overwrite conflicting managed context files.
|
|
@@ -514,7 +590,7 @@ function gitRemoteHelp() {
|
|
|
514
590
|
|
|
515
591
|
Options:
|
|
516
592
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
517
|
-
--token <token> Tender API token.
|
|
593
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
518
594
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
519
595
|
--ttl <value> Deprecated for Tender-hosted Git remotes; accepted for compatibility.
|
|
520
596
|
--json Emit stable JSON for coding agents.
|
|
@@ -531,7 +607,7 @@ Options:
|
|
|
531
607
|
--dir <path> Local Git checkout to configure. Defaults to current directory.
|
|
532
608
|
--remote <name> Git remote name to add or update. Defaults to tender.
|
|
533
609
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
534
|
-
--token <token> Tender API token.
|
|
610
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
535
611
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
536
612
|
--ttl <value> Deprecated for Tender-hosted Git remotes; accepted for compatibility.
|
|
537
613
|
--dry-run Show Git commands without running them.
|
|
@@ -559,7 +635,7 @@ function previewRebuildHelp() {
|
|
|
559
635
|
Options:
|
|
560
636
|
--commit <sha> Rebuild preview only if the app workspace is at this Git commit.
|
|
561
637
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
562
|
-
--token <token> Tender API token.
|
|
638
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
563
639
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
564
640
|
--stream Request lifecycle events from the API.
|
|
565
641
|
--json Emit stable JSON for coding agents.
|
|
@@ -573,7 +649,7 @@ function validateHelp() {
|
|
|
573
649
|
|
|
574
650
|
Options:
|
|
575
651
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
576
|
-
--token <token> Tender API token.
|
|
652
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
577
653
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
578
654
|
--stream Request lifecycle events from the API.
|
|
579
655
|
--json Emit stable JSON for coding agents.
|
|
@@ -588,7 +664,7 @@ function buildHelp() {
|
|
|
588
664
|
Options:
|
|
589
665
|
--commit <sha> Build only if the app workspace is at this Git commit.
|
|
590
666
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
591
|
-
--token <token> Tender API token.
|
|
667
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
592
668
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
593
669
|
--stream Request lifecycle events from the API.
|
|
594
670
|
--json Emit stable JSON for coding agents.
|
|
@@ -604,7 +680,7 @@ Options:
|
|
|
604
680
|
--commit <sha> Show the latest preview job for one artifact Git commit.
|
|
605
681
|
--job <job-id> Show one lifecycle job directly.
|
|
606
682
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
607
|
-
--token <token> Tender API token.
|
|
683
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
608
684
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
609
685
|
--json Emit stable JSON for coding agents.
|
|
610
686
|
|
|
@@ -619,7 +695,7 @@ Options:
|
|
|
619
695
|
--commit <sha> Watch the latest preview job for one artifact Git commit.
|
|
620
696
|
--job <job-id> Watch one lifecycle job directly.
|
|
621
697
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
622
|
-
--token <token> Tender API token.
|
|
698
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
623
699
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
624
700
|
--stream Request Server-Sent Events from the API.
|
|
625
701
|
--json Emit lifecycle events as JSON lines.
|
|
@@ -637,7 +713,7 @@ Options:
|
|
|
637
713
|
--commit <sha> Publish only if the app workspace is at this Git commit.
|
|
638
714
|
--message <text> Human audit message returned in CLI output.
|
|
639
715
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
640
|
-
--token <token> Tender API token.
|
|
716
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
641
717
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
642
718
|
--stream Request lifecycle events from the API.
|
|
643
719
|
--json Emit stable JSON for coding agents.
|
|
@@ -652,7 +728,7 @@ function publishStatusHelp() {
|
|
|
652
728
|
Options:
|
|
653
729
|
--job <job-id> Show one publish lifecycle job directly.
|
|
654
730
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
655
|
-
--token <token> Tender API token.
|
|
731
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
656
732
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
657
733
|
--json Emit stable JSON for coding agents.
|
|
658
734
|
|
|
@@ -665,7 +741,7 @@ function publishWatchHelp() {
|
|
|
665
741
|
Options:
|
|
666
742
|
--job <job-id> Watch one publish lifecycle job directly.
|
|
667
743
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
668
|
-
--token <token> Tender API token.
|
|
744
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
669
745
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
670
746
|
--stream Request Server-Sent Events from the API.
|
|
671
747
|
--json Emit lifecycle events as JSON lines.
|
|
@@ -740,12 +816,20 @@ function tenderCliCapabilities() {
|
|
|
740
816
|
"tender app analytics charts templates --json",
|
|
741
817
|
"tender app analytics query <artifact-id> --spec chart.json --json",
|
|
742
818
|
"tender app analytics export query <artifact-id> --spec chart.json --format csv",
|
|
819
|
+
"tender app analytics properties list <artifact-id> --json",
|
|
820
|
+
"tender app analytics properties activate <artifact-id> --event <event-name> --property <property-key> --type dimension --dry-run --json",
|
|
743
821
|
"tender app analytics dashboards <artifact-id> --create \"Agent dashboard\" --dry-run --json",
|
|
744
822
|
"tender app analytics charts create <artifact-id> --dashboard <dashboard-id> --title <title> --spec chart.json --dry-run --json",
|
|
823
|
+
"tender app analytics charts list <artifact-id> --dashboard <dashboard-id> --json",
|
|
824
|
+
"tender app analytics charts update <artifact-id> --chart <chart-id> --title <title> --spec chart.json --dry-run --json",
|
|
825
|
+
"tender app analytics charts delete <artifact-id> --chart <chart-id> --dry-run --json",
|
|
745
826
|
],
|
|
746
827
|
notes: [
|
|
747
828
|
"Combine CLI capabilities with local source inspection of analytics calls.",
|
|
748
|
-
"Validate chart specs with query before
|
|
829
|
+
"Validate chart specs with query before creating or updating saved charts.",
|
|
830
|
+
"Property chart create/update commands automatically make missing custom event properties queryable when the spec has an event_name = filter.",
|
|
831
|
+
"Ad hoc query is read-only; if it reports a missing dashboard-query property, activate the property first or save the chart through charts create/update.",
|
|
832
|
+
"Use list before writes so agents can avoid duplicates and explain the exact chart they are changing.",
|
|
749
833
|
],
|
|
750
834
|
},
|
|
751
835
|
{
|
|
@@ -801,6 +885,46 @@ function tenderCliCapabilities() {
|
|
|
801
885
|
requiresAuth: false,
|
|
802
886
|
writes: false,
|
|
803
887
|
},
|
|
888
|
+
{
|
|
889
|
+
command: "tender app analytics properties list <artifact-id> --json",
|
|
890
|
+
purpose: "List custom event properties available to dashboard queries.",
|
|
891
|
+
requiresAuth: true,
|
|
892
|
+
writes: false,
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
command: "tender app analytics properties activate <artifact-id> --event <event-name> --property <property-key> --type <dimension|metric> --dry-run --json",
|
|
896
|
+
purpose: "Preview or activate the custom event property needed for a property group-by or property metric chart.",
|
|
897
|
+
requiresAuth: true,
|
|
898
|
+
writes: true,
|
|
899
|
+
dryRun: true,
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
command: "tender app analytics charts create <artifact-id> --dashboard <dashboard-id> --title <title> --spec chart.json --dry-run --json",
|
|
903
|
+
purpose: "Preview or create a saved chart from a validated chart spec.",
|
|
904
|
+
requiresAuth: true,
|
|
905
|
+
writes: true,
|
|
906
|
+
dryRun: true,
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
command: "tender app analytics charts list <artifact-id> --dashboard <dashboard-id> --json",
|
|
910
|
+
purpose: "List saved charts and full chart specs from one dashboard.",
|
|
911
|
+
requiresAuth: true,
|
|
912
|
+
writes: false,
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
command: "tender app analytics charts update <artifact-id> --chart <chart-id> --title <title> --spec chart.json --dry-run --json",
|
|
916
|
+
purpose: "Preview or update a saved chart title and chart spec.",
|
|
917
|
+
requiresAuth: true,
|
|
918
|
+
writes: true,
|
|
919
|
+
dryRun: true,
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
command: "tender app analytics charts delete <artifact-id> --chart <chart-id> --dry-run --json",
|
|
923
|
+
purpose: "Preview or delete a saved chart.",
|
|
924
|
+
requiresAuth: true,
|
|
925
|
+
writes: true,
|
|
926
|
+
dryRun: true,
|
|
927
|
+
},
|
|
804
928
|
],
|
|
805
929
|
security: [
|
|
806
930
|
"Prefer artifact-scoped device tokens.",
|
|
@@ -921,6 +1045,19 @@ async function resolveApiCredentials(input) {
|
|
|
921
1045
|
profileName: null,
|
|
922
1046
|
};
|
|
923
1047
|
}
|
|
1048
|
+
if (input.profileName) {
|
|
1049
|
+
const config = await readConfig(input.env);
|
|
1050
|
+
const profile = config.profiles?.[input.profileName];
|
|
1051
|
+
if (!profile) {
|
|
1052
|
+
throw new TenderCliUsageError(`Tender profile "${input.profileName}" was not found. Run \`tender auth login --device --save-profile ${input.profileName}\` or choose another --profile.`);
|
|
1053
|
+
}
|
|
1054
|
+
return {
|
|
1055
|
+
baseUrl: input.baseUrl ?? profile.baseUrl ?? readDefaultBaseUrl(input.env),
|
|
1056
|
+
token: profile.token,
|
|
1057
|
+
source: "config",
|
|
1058
|
+
profileName: input.profileName,
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
924
1061
|
if (input.env.TENDER_API_TOKEN) {
|
|
925
1062
|
return {
|
|
926
1063
|
baseUrl: input.baseUrl ?? readDefaultBaseUrl(input.env),
|
|
@@ -930,7 +1067,7 @@ async function resolveApiCredentials(input) {
|
|
|
930
1067
|
};
|
|
931
1068
|
}
|
|
932
1069
|
const config = await readConfig(input.env);
|
|
933
|
-
const profileName =
|
|
1070
|
+
const profileName = config.defaultProfile ?? DEFAULT_PROFILE_NAME;
|
|
934
1071
|
const profile = config.profiles?.[profileName];
|
|
935
1072
|
if (!profile) {
|
|
936
1073
|
throw new TenderCliUsageError("Tender API token is required. Run `tender auth login --device`, set TENDER_API_TOKEN, or pass --token <token>.");
|
|
@@ -2801,13 +2938,13 @@ function analyticsChartTemplates() {
|
|
|
2801
2938
|
id: "property_breakdown",
|
|
2802
2939
|
title: "Property breakdown",
|
|
2803
2940
|
visualization: "bar",
|
|
2804
|
-
purpose: "Compare a
|
|
2941
|
+
purpose: "Compare a queryable dimension property observed in events.",
|
|
2805
2942
|
usableAsIs: false,
|
|
2806
2943
|
placeholders: {
|
|
2807
|
-
property: "<
|
|
2944
|
+
property: "<queryable_dimension_property>",
|
|
2808
2945
|
},
|
|
2809
2946
|
adaptWith: [
|
|
2810
|
-
"Use a property key
|
|
2947
|
+
"Use a queryable property key from capabilities chartSpec.groupBy, such as property:plan.",
|
|
2811
2948
|
"Choose stable categorical properties with enough observations to compare.",
|
|
2812
2949
|
],
|
|
2813
2950
|
spec: {
|
|
@@ -2815,7 +2952,7 @@ function analyticsChartTemplates() {
|
|
|
2815
2952
|
dataset: "events",
|
|
2816
2953
|
visualization: "bar",
|
|
2817
2954
|
metric: { name: "custom_event_count" },
|
|
2818
|
-
groupBy: [{ field: "property:<
|
|
2955
|
+
groupBy: [{ field: "property:<queryable_dimension_property>" }],
|
|
2819
2956
|
filters: [],
|
|
2820
2957
|
range: { preset: "30d" },
|
|
2821
2958
|
sort: [{ field: "value", direction: "desc" }],
|
|
@@ -2829,17 +2966,17 @@ function analyticsChartTemplates() {
|
|
|
2829
2966
|
purpose: "Track an observed numeric event property over time.",
|
|
2830
2967
|
usableAsIs: false,
|
|
2831
2968
|
placeholders: {
|
|
2832
|
-
metric: "avg:<
|
|
2969
|
+
metric: "avg:<queryable_metric_property>",
|
|
2833
2970
|
},
|
|
2834
2971
|
adaptWith: [
|
|
2835
|
-
"Use a numeric property key
|
|
2972
|
+
"Use a queryable numeric property key from capabilities chartSpec.metrics.",
|
|
2836
2973
|
"Prefer averages for scores, durations, quantities, or ordinal values.",
|
|
2837
2974
|
],
|
|
2838
2975
|
spec: {
|
|
2839
2976
|
version: 1,
|
|
2840
2977
|
dataset: "events",
|
|
2841
2978
|
visualization: "line",
|
|
2842
|
-
metric: { name: "avg:<
|
|
2979
|
+
metric: { name: "avg:<queryable_metric_property>" },
|
|
2843
2980
|
groupBy: [{ field: "day" }],
|
|
2844
2981
|
filters: [],
|
|
2845
2982
|
range: { preset: "30d" },
|
|
@@ -2882,7 +3019,7 @@ function runAnalyticsChartsTemplates(command, io) {
|
|
|
2882
3019
|
templates,
|
|
2883
3020
|
next: [
|
|
2884
3021
|
"Run tender app analytics capabilities <artifact-id> --include-catalog --range 30d --json.",
|
|
2885
|
-
"Choose a template and replace placeholders with observed events, paths, or
|
|
3022
|
+
"Choose a template and replace placeholders with observed events, paths, or queryable property keys.",
|
|
2886
3023
|
"Validate with tender app analytics query <artifact-id> --spec chart.json --json.",
|
|
2887
3024
|
"Preview saved writes with tender app analytics dashboards ... --dry-run and charts create ... --dry-run.",
|
|
2888
3025
|
],
|
|
@@ -2953,6 +3090,208 @@ function parseAnalyticsChartsCreateArgs(args) {
|
|
|
2953
3090
|
...parsed,
|
|
2954
3091
|
};
|
|
2955
3092
|
}
|
|
3093
|
+
function parseAnalyticsChartsListArgs(args) {
|
|
3094
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
3095
|
+
return { command: "help", topic: "app analytics charts list" };
|
|
3096
|
+
}
|
|
3097
|
+
const artifactId = args[0];
|
|
3098
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
3099
|
+
throw new TenderCliUsageError("app id is required. Example: tender app analytics charts list <app-id> --dashboard <dashboard-id> --json");
|
|
3100
|
+
}
|
|
3101
|
+
const parsed = parseAnalyticsCommonArgs(args, 1);
|
|
3102
|
+
let dashboardId = null;
|
|
3103
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
3104
|
+
const arg = parsed.remaining[index];
|
|
3105
|
+
if (arg === "--dashboard") {
|
|
3106
|
+
dashboardId = parseRequiredFlagValue(parsed.remaining[index + 1], "--dashboard");
|
|
3107
|
+
index += 1;
|
|
3108
|
+
continue;
|
|
3109
|
+
}
|
|
3110
|
+
throw new TenderCliUsageError(`Unknown analytics charts list option: ${arg}`);
|
|
3111
|
+
}
|
|
3112
|
+
if (!dashboardId) {
|
|
3113
|
+
throw new TenderCliUsageError("--dashboard is required. Example: tender app analytics charts list artifact_123 --dashboard analytics_dashboard_123 --json");
|
|
3114
|
+
}
|
|
3115
|
+
return {
|
|
3116
|
+
command: "app analytics charts list",
|
|
3117
|
+
artifactId,
|
|
3118
|
+
dashboardId,
|
|
3119
|
+
...parsed,
|
|
3120
|
+
};
|
|
3121
|
+
}
|
|
3122
|
+
function parseAnalyticsChartsUpdateArgs(args) {
|
|
3123
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
3124
|
+
return { command: "help", topic: "app analytics charts update" };
|
|
3125
|
+
}
|
|
3126
|
+
const artifactId = args[0];
|
|
3127
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
3128
|
+
throw new TenderCliUsageError("app id is required. Example: tender app analytics charts update <app-id> --chart <chart-id> --title <title> --spec chart.json --json");
|
|
3129
|
+
}
|
|
3130
|
+
const parsed = parseAnalyticsCommonArgs(args, 1);
|
|
3131
|
+
let chartId = null;
|
|
3132
|
+
let title = null;
|
|
3133
|
+
let specPath = null;
|
|
3134
|
+
let dryRun = false;
|
|
3135
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
3136
|
+
const arg = parsed.remaining[index];
|
|
3137
|
+
if (arg === "--chart") {
|
|
3138
|
+
chartId = parseRequiredFlagValue(parsed.remaining[index + 1], "--chart");
|
|
3139
|
+
index += 1;
|
|
3140
|
+
continue;
|
|
3141
|
+
}
|
|
3142
|
+
if (arg === "--title") {
|
|
3143
|
+
title = parseRequiredFlagValue(parsed.remaining[index + 1], "--title");
|
|
3144
|
+
index += 1;
|
|
3145
|
+
continue;
|
|
3146
|
+
}
|
|
3147
|
+
if (arg === "--spec") {
|
|
3148
|
+
specPath = parseSpecPath(parsed.remaining[index + 1]);
|
|
3149
|
+
index += 1;
|
|
3150
|
+
continue;
|
|
3151
|
+
}
|
|
3152
|
+
if (arg === "--dry-run") {
|
|
3153
|
+
dryRun = true;
|
|
3154
|
+
continue;
|
|
3155
|
+
}
|
|
3156
|
+
throw new TenderCliUsageError(`Unknown analytics charts update option: ${arg}`);
|
|
3157
|
+
}
|
|
3158
|
+
if (!chartId) {
|
|
3159
|
+
throw new TenderCliUsageError("--chart is required. Example: tender app analytics charts update artifact_123 --chart analytics_chart_123 --title \"Signup funnel\" --json");
|
|
3160
|
+
}
|
|
3161
|
+
if (!title && !specPath) {
|
|
3162
|
+
throw new TenderCliUsageError("At least one of --title or --spec is required. Example: tender app analytics charts update artifact_123 --chart analytics_chart_123 --title \"Signup funnel\" --dry-run --json");
|
|
3163
|
+
}
|
|
3164
|
+
return {
|
|
3165
|
+
command: "app analytics charts update",
|
|
3166
|
+
artifactId,
|
|
3167
|
+
chartId,
|
|
3168
|
+
title,
|
|
3169
|
+
specPath,
|
|
3170
|
+
dryRun,
|
|
3171
|
+
...parsed,
|
|
3172
|
+
};
|
|
3173
|
+
}
|
|
3174
|
+
function parseAnalyticsChartsDeleteArgs(args) {
|
|
3175
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
3176
|
+
return { command: "help", topic: "app analytics charts delete" };
|
|
3177
|
+
}
|
|
3178
|
+
const artifactId = args[0];
|
|
3179
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
3180
|
+
throw new TenderCliUsageError("app id is required. Example: tender app analytics charts delete <app-id> --chart <chart-id> --dry-run --json");
|
|
3181
|
+
}
|
|
3182
|
+
const parsed = parseAnalyticsCommonArgs(args, 1);
|
|
3183
|
+
let chartId = null;
|
|
3184
|
+
let dryRun = false;
|
|
3185
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
3186
|
+
const arg = parsed.remaining[index];
|
|
3187
|
+
if (arg === "--chart") {
|
|
3188
|
+
chartId = parseRequiredFlagValue(parsed.remaining[index + 1], "--chart");
|
|
3189
|
+
index += 1;
|
|
3190
|
+
continue;
|
|
3191
|
+
}
|
|
3192
|
+
if (arg === "--dry-run") {
|
|
3193
|
+
dryRun = true;
|
|
3194
|
+
continue;
|
|
3195
|
+
}
|
|
3196
|
+
throw new TenderCliUsageError(`Unknown analytics charts delete option: ${arg}`);
|
|
3197
|
+
}
|
|
3198
|
+
if (!chartId) {
|
|
3199
|
+
throw new TenderCliUsageError("--chart is required. Example: tender app analytics charts delete artifact_123 --chart analytics_chart_123 --dry-run --json");
|
|
3200
|
+
}
|
|
3201
|
+
return {
|
|
3202
|
+
command: "app analytics charts delete",
|
|
3203
|
+
artifactId,
|
|
3204
|
+
chartId,
|
|
3205
|
+
dryRun,
|
|
3206
|
+
...parsed,
|
|
3207
|
+
};
|
|
3208
|
+
}
|
|
3209
|
+
function parseAnalyticsPropertiesListArgs(args) {
|
|
3210
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
3211
|
+
return { command: "help", topic: "app analytics properties list" };
|
|
3212
|
+
}
|
|
3213
|
+
const artifactId = args[0];
|
|
3214
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
3215
|
+
throw new TenderCliUsageError("app id is required. Example: tender app analytics properties list <app-id> --json");
|
|
3216
|
+
}
|
|
3217
|
+
const parsed = parseAnalyticsCommonArgs(args, 1);
|
|
3218
|
+
let eventName = null;
|
|
3219
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
3220
|
+
const arg = parsed.remaining[index];
|
|
3221
|
+
if (arg === "--event") {
|
|
3222
|
+
eventName = parseRequiredFlagValue(parsed.remaining[index + 1], "--event");
|
|
3223
|
+
index += 1;
|
|
3224
|
+
continue;
|
|
3225
|
+
}
|
|
3226
|
+
throw new TenderCliUsageError(`Unknown analytics properties list option: ${arg}`);
|
|
3227
|
+
}
|
|
3228
|
+
return {
|
|
3229
|
+
command: "app analytics properties list",
|
|
3230
|
+
artifactId,
|
|
3231
|
+
eventName,
|
|
3232
|
+
...parsed,
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
3235
|
+
function parseAnalyticsPropertiesActivateArgs(args) {
|
|
3236
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
3237
|
+
return { command: "help", topic: "app analytics properties activate" };
|
|
3238
|
+
}
|
|
3239
|
+
const artifactId = args[0];
|
|
3240
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
3241
|
+
throw new TenderCliUsageError("app id is required. Example: tender app analytics properties activate <app-id> --event <event-name> --property <property-key> --type dimension --json");
|
|
3242
|
+
}
|
|
3243
|
+
const parsed = parseAnalyticsCommonArgs(args, 1);
|
|
3244
|
+
let eventName = null;
|
|
3245
|
+
let propertyKey = null;
|
|
3246
|
+
let propertyType = null;
|
|
3247
|
+
let label = null;
|
|
3248
|
+
let dryRun = false;
|
|
3249
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
3250
|
+
const arg = parsed.remaining[index];
|
|
3251
|
+
if (arg === "--event") {
|
|
3252
|
+
eventName = parseRequiredFlagValue(parsed.remaining[index + 1], "--event");
|
|
3253
|
+
index += 1;
|
|
3254
|
+
continue;
|
|
3255
|
+
}
|
|
3256
|
+
if (arg === "--property") {
|
|
3257
|
+
propertyKey = parseRequiredFlagValue(parsed.remaining[index + 1], "--property");
|
|
3258
|
+
index += 1;
|
|
3259
|
+
continue;
|
|
3260
|
+
}
|
|
3261
|
+
if (arg === "--type") {
|
|
3262
|
+
const value = parseRequiredFlagValue(parsed.remaining[index + 1], "--type");
|
|
3263
|
+
if (value !== "dimension" && value !== "metric") {
|
|
3264
|
+
throw new TenderCliUsageError("--type must be dimension or metric.");
|
|
3265
|
+
}
|
|
3266
|
+
propertyType = value;
|
|
3267
|
+
index += 1;
|
|
3268
|
+
continue;
|
|
3269
|
+
}
|
|
3270
|
+
if (arg === "--label") {
|
|
3271
|
+
label = parseRequiredFlagValue(parsed.remaining[index + 1], "--label");
|
|
3272
|
+
index += 1;
|
|
3273
|
+
continue;
|
|
3274
|
+
}
|
|
3275
|
+
if (arg === "--dry-run") {
|
|
3276
|
+
dryRun = true;
|
|
3277
|
+
continue;
|
|
3278
|
+
}
|
|
3279
|
+
throw new TenderCliUsageError(`Unknown analytics properties activate option: ${arg}`);
|
|
3280
|
+
}
|
|
3281
|
+
if (!eventName || !propertyKey || !propertyType) {
|
|
3282
|
+
throw new TenderCliUsageError("--event, --property, and --type are required. Example: tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --json");
|
|
3283
|
+
}
|
|
3284
|
+
return {
|
|
3285
|
+
command: "app analytics properties activate",
|
|
3286
|
+
artifactId,
|
|
3287
|
+
eventName,
|
|
3288
|
+
propertyKey,
|
|
3289
|
+
propertyType,
|
|
3290
|
+
label,
|
|
3291
|
+
dryRun,
|
|
3292
|
+
...parsed,
|
|
3293
|
+
};
|
|
3294
|
+
}
|
|
2956
3295
|
function parseTenderArgs(args) {
|
|
2957
3296
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
2958
3297
|
return { command: "help", topic: "root" };
|
|
@@ -3036,6 +3375,23 @@ function parseTenderArgs(args) {
|
|
|
3036
3375
|
if (resourceArgs[1] === "analytics" && resourceArgs[2] === "dashboards") {
|
|
3037
3376
|
return parseAnalyticsDashboardsArgs(resourceArgs.slice(3));
|
|
3038
3377
|
}
|
|
3378
|
+
if (resourceArgs[1] === "analytics" &&
|
|
3379
|
+
resourceArgs[2] === "properties" &&
|
|
3380
|
+
(resourceArgs[3] === undefined ||
|
|
3381
|
+
resourceArgs[3] === "--help" ||
|
|
3382
|
+
resourceArgs[3] === "-h")) {
|
|
3383
|
+
return { command: "help", topic: "app analytics properties list" };
|
|
3384
|
+
}
|
|
3385
|
+
if (resourceArgs[1] === "analytics" &&
|
|
3386
|
+
resourceArgs[2] === "properties" &&
|
|
3387
|
+
resourceArgs[3] === "list") {
|
|
3388
|
+
return parseAnalyticsPropertiesListArgs(resourceArgs.slice(4));
|
|
3389
|
+
}
|
|
3390
|
+
if (resourceArgs[1] === "analytics" &&
|
|
3391
|
+
resourceArgs[2] === "properties" &&
|
|
3392
|
+
resourceArgs[3] === "activate") {
|
|
3393
|
+
return parseAnalyticsPropertiesActivateArgs(resourceArgs.slice(4));
|
|
3394
|
+
}
|
|
3039
3395
|
if (resourceArgs[1] === "analytics" &&
|
|
3040
3396
|
resourceArgs[2] === "export" &&
|
|
3041
3397
|
(resourceArgs[3] === undefined ||
|
|
@@ -3070,6 +3426,21 @@ function parseTenderArgs(args) {
|
|
|
3070
3426
|
resourceArgs[3] === "create") {
|
|
3071
3427
|
return parseAnalyticsChartsCreateArgs(resourceArgs.slice(4));
|
|
3072
3428
|
}
|
|
3429
|
+
if (resourceArgs[1] === "analytics" &&
|
|
3430
|
+
resourceArgs[2] === "charts" &&
|
|
3431
|
+
resourceArgs[3] === "list") {
|
|
3432
|
+
return parseAnalyticsChartsListArgs(resourceArgs.slice(4));
|
|
3433
|
+
}
|
|
3434
|
+
if (resourceArgs[1] === "analytics" &&
|
|
3435
|
+
resourceArgs[2] === "charts" &&
|
|
3436
|
+
resourceArgs[3] === "update") {
|
|
3437
|
+
return parseAnalyticsChartsUpdateArgs(resourceArgs.slice(4));
|
|
3438
|
+
}
|
|
3439
|
+
if (resourceArgs[1] === "analytics" &&
|
|
3440
|
+
resourceArgs[2] === "charts" &&
|
|
3441
|
+
resourceArgs[3] === "delete") {
|
|
3442
|
+
return parseAnalyticsChartsDeleteArgs(resourceArgs.slice(4));
|
|
3443
|
+
}
|
|
3073
3444
|
if (resourceArgs[1] === "context" &&
|
|
3074
3445
|
(resourceArgs[2] === undefined ||
|
|
3075
3446
|
resourceArgs[2] === "--help" ||
|
|
@@ -3442,7 +3813,9 @@ async function requestAnalyticsApi(input) {
|
|
|
3442
3813
|
if (input.includeCatalog) {
|
|
3443
3814
|
params.set("includeCatalog", "1");
|
|
3444
3815
|
}
|
|
3445
|
-
const suffix = input.analyticsPath
|
|
3816
|
+
const suffix = input.analyticsPath
|
|
3817
|
+
? `/${input.analyticsPath}${input.resourceId ? `/${encodeURIComponent(input.resourceId)}` : ""}`
|
|
3818
|
+
: "";
|
|
3446
3819
|
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
3447
3820
|
const response = await input.fetcher(`${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/analytics${suffix}${query}`, {
|
|
3448
3821
|
method: input.method,
|
|
@@ -3460,12 +3833,12 @@ async function requestAnalyticsApi(input) {
|
|
|
3460
3833
|
const message = (typeof payload?.message === "string" && payload.message) ||
|
|
3461
3834
|
(typeof payload?.reasonCode === "string" && payload.reasonCode) ||
|
|
3462
3835
|
`Analytics request failed with HTTP ${response.status}.`;
|
|
3463
|
-
throw new
|
|
3836
|
+
throw new TenderCliAnalyticsApiError([
|
|
3464
3837
|
message,
|
|
3465
3838
|
`Scope: artifact ${input.artifactId}, analytics/${input.analyticsPath || "summary"}.`,
|
|
3466
3839
|
`Reason: ${reasonCode}.`,
|
|
3467
3840
|
`Example: ${analyticsCorrectedExample(input.artifactId, input.analyticsPath)}.`,
|
|
3468
|
-
].join("\n"));
|
|
3841
|
+
].join("\n"), reasonCode);
|
|
3469
3842
|
}
|
|
3470
3843
|
return payload;
|
|
3471
3844
|
}
|
|
@@ -3479,6 +3852,9 @@ function analyticsCorrectedExample(artifactId, analyticsPath) {
|
|
|
3479
3852
|
if (analyticsPath === "suggestions") {
|
|
3480
3853
|
return `tender app analytics suggestions ${artifactId} --range 30d --json`;
|
|
3481
3854
|
}
|
|
3855
|
+
if (analyticsPath === "properties") {
|
|
3856
|
+
return `tender app analytics properties list ${artifactId} --json`;
|
|
3857
|
+
}
|
|
3482
3858
|
if (analyticsPath === "dashboards") {
|
|
3483
3859
|
return `tender app analytics dashboards ${artifactId} --json`;
|
|
3484
3860
|
}
|
|
@@ -3487,6 +3863,250 @@ function analyticsCorrectedExample(artifactId, analyticsPath) {
|
|
|
3487
3863
|
}
|
|
3488
3864
|
return `tender app analytics summary ${artifactId} --range 30d --json`;
|
|
3489
3865
|
}
|
|
3866
|
+
function isCliJsonRecord(value) {
|
|
3867
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3868
|
+
}
|
|
3869
|
+
function readCliStringField(record, field) {
|
|
3870
|
+
const value = record[field];
|
|
3871
|
+
return typeof value === "string" ? value : null;
|
|
3872
|
+
}
|
|
3873
|
+
function readCliBooleanField(record, field) {
|
|
3874
|
+
const value = record[field];
|
|
3875
|
+
return typeof value === "boolean" ? value : false;
|
|
3876
|
+
}
|
|
3877
|
+
function readAnalyticsDashboardRecords(payload) {
|
|
3878
|
+
return Array.isArray(payload.dashboards)
|
|
3879
|
+
? payload.dashboards.filter(isCliJsonRecord)
|
|
3880
|
+
: [];
|
|
3881
|
+
}
|
|
3882
|
+
function readAnalyticsChartRecords(dashboard) {
|
|
3883
|
+
return Array.isArray(dashboard.charts)
|
|
3884
|
+
? dashboard.charts.filter(isCliJsonRecord)
|
|
3885
|
+
: [];
|
|
3886
|
+
}
|
|
3887
|
+
function findAnalyticsDashboard(payload, dashboardId) {
|
|
3888
|
+
return (readAnalyticsDashboardRecords(payload).find((dashboard) => readCliStringField(dashboard, "id") === dashboardId) ?? null);
|
|
3889
|
+
}
|
|
3890
|
+
function findAnalyticsChart(payload, chartId) {
|
|
3891
|
+
for (const dashboard of readAnalyticsDashboardRecords(payload)) {
|
|
3892
|
+
const chart = readAnalyticsChartRecords(dashboard).find((candidate) => readCliStringField(candidate, "id") === chartId);
|
|
3893
|
+
if (chart) {
|
|
3894
|
+
return { dashboard, chart };
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
return null;
|
|
3898
|
+
}
|
|
3899
|
+
function readAnalyticsChartTitle(chart) {
|
|
3900
|
+
return readCliStringField(chart, "title") ?? "Analytics chart";
|
|
3901
|
+
}
|
|
3902
|
+
function readAnalyticsChartSpec(chart) {
|
|
3903
|
+
const spec = chart.spec;
|
|
3904
|
+
if (spec === undefined) {
|
|
3905
|
+
throw new Error("Saved chart does not include a chart spec.");
|
|
3906
|
+
}
|
|
3907
|
+
return spec;
|
|
3908
|
+
}
|
|
3909
|
+
function readAnalyticsChartLayout(chart) {
|
|
3910
|
+
return isCliJsonRecord(chart.layout) ? chart.layout : {};
|
|
3911
|
+
}
|
|
3912
|
+
function readAnalyticsPropertyRecords(payload) {
|
|
3913
|
+
return Array.isArray(payload.properties)
|
|
3914
|
+
? payload.properties.filter(isCliJsonRecord)
|
|
3915
|
+
: [];
|
|
3916
|
+
}
|
|
3917
|
+
function readAnalyticsSpecEventNameFilter(spec) {
|
|
3918
|
+
if (!isCliJsonRecord(spec) || !Array.isArray(spec.filters)) {
|
|
3919
|
+
return null;
|
|
3920
|
+
}
|
|
3921
|
+
const filter = spec.filters.filter(isCliJsonRecord).find((candidate) => readCliStringField(candidate, "field") === "event_name" &&
|
|
3922
|
+
readCliStringField(candidate, "op") === "=" &&
|
|
3923
|
+
typeof candidate.value === "string" &&
|
|
3924
|
+
candidate.value.trim().length > 0);
|
|
3925
|
+
return typeof filter?.value === "string" ? filter.value.trim() : null;
|
|
3926
|
+
}
|
|
3927
|
+
function readAnalyticsSpecMetricName(spec) {
|
|
3928
|
+
if (!isCliJsonRecord(spec) || !isCliJsonRecord(spec.metric)) {
|
|
3929
|
+
return null;
|
|
3930
|
+
}
|
|
3931
|
+
return readCliStringField(spec.metric, "name");
|
|
3932
|
+
}
|
|
3933
|
+
function readAnalyticsSpecGroupField(spec) {
|
|
3934
|
+
if (!isCliJsonRecord(spec) || !Array.isArray(spec.groupBy)) {
|
|
3935
|
+
return null;
|
|
3936
|
+
}
|
|
3937
|
+
const [group] = spec.groupBy.filter(isCliJsonRecord);
|
|
3938
|
+
return group ? readCliStringField(group, "field") : null;
|
|
3939
|
+
}
|
|
3940
|
+
function readRequiredAnalyticsPropertiesFromSpec(spec) {
|
|
3941
|
+
const eventName = readAnalyticsSpecEventNameFilter(spec);
|
|
3942
|
+
const requirements = [];
|
|
3943
|
+
const groupField = readAnalyticsSpecGroupField(spec);
|
|
3944
|
+
if (groupField?.startsWith("property:")) {
|
|
3945
|
+
if (!eventName) {
|
|
3946
|
+
throw new TenderCliUsageError("Property group-by chart specs require an event_name = filter so the CLI can make the property available to dashboard queries.");
|
|
3947
|
+
}
|
|
3948
|
+
requirements.push({
|
|
3949
|
+
eventName,
|
|
3950
|
+
propertyKey: groupField.slice("property:".length),
|
|
3951
|
+
propertyType: "dimension",
|
|
3952
|
+
});
|
|
3953
|
+
}
|
|
3954
|
+
const metricName = readAnalyticsSpecMetricName(spec);
|
|
3955
|
+
if (metricName?.startsWith("sum:") || metricName?.startsWith("avg:")) {
|
|
3956
|
+
if (!eventName) {
|
|
3957
|
+
throw new TenderCliUsageError("Property metric chart specs require an event_name = filter so the CLI can make the property available to dashboard queries.");
|
|
3958
|
+
}
|
|
3959
|
+
requirements.push({
|
|
3960
|
+
eventName,
|
|
3961
|
+
propertyKey: metricName.slice(metricName.indexOf(":") + 1),
|
|
3962
|
+
propertyType: "metric",
|
|
3963
|
+
});
|
|
3964
|
+
}
|
|
3965
|
+
const seen = new Set();
|
|
3966
|
+
return requirements.filter((requirement) => {
|
|
3967
|
+
const key = `${requirement.propertyType}:${requirement.eventName}:${requirement.propertyKey}`;
|
|
3968
|
+
if (seen.has(key)) {
|
|
3969
|
+
return false;
|
|
3970
|
+
}
|
|
3971
|
+
seen.add(key);
|
|
3972
|
+
return true;
|
|
3973
|
+
});
|
|
3974
|
+
}
|
|
3975
|
+
async function ensureAnalyticsProperties(input) {
|
|
3976
|
+
if (input.requirements.length === 0) {
|
|
3977
|
+
return [];
|
|
3978
|
+
}
|
|
3979
|
+
const plan = [];
|
|
3980
|
+
for (const requirement of input.requirements) {
|
|
3981
|
+
const body = {
|
|
3982
|
+
eventName: requirement.eventName,
|
|
3983
|
+
propertyKey: requirement.propertyKey,
|
|
3984
|
+
propertyType: requirement.propertyType,
|
|
3985
|
+
label: requirement.propertyKey,
|
|
3986
|
+
dryRun: input.dryRun,
|
|
3987
|
+
};
|
|
3988
|
+
const payload = await requestAnalyticsApi({
|
|
3989
|
+
artifactId: input.command.artifactId,
|
|
3990
|
+
analyticsPath: "properties",
|
|
3991
|
+
method: "POST",
|
|
3992
|
+
baseUrl: input.credentials.baseUrl,
|
|
3993
|
+
token: input.credentials.token,
|
|
3994
|
+
fetcher: input.runtime.fetcher ?? fetch,
|
|
3995
|
+
body,
|
|
3996
|
+
});
|
|
3997
|
+
plan.push({
|
|
3998
|
+
eventName: requirement.eventName,
|
|
3999
|
+
propertyKey: requirement.propertyKey,
|
|
4000
|
+
propertyType: requirement.propertyType,
|
|
4001
|
+
label: requirement.propertyKey,
|
|
4002
|
+
status: readCliBooleanField(payload, "alreadyActive")
|
|
4003
|
+
? "existing"
|
|
4004
|
+
: input.dryRun
|
|
4005
|
+
? "planned"
|
|
4006
|
+
: "activated",
|
|
4007
|
+
property: payload.property,
|
|
4008
|
+
});
|
|
4009
|
+
}
|
|
4010
|
+
return plan;
|
|
4011
|
+
}
|
|
4012
|
+
function analyticsCommandAuthFlags(command) {
|
|
4013
|
+
const flags = [];
|
|
4014
|
+
if (command.baseUrl) {
|
|
4015
|
+
flags.push("--base-url", shellQuote(command.baseUrl));
|
|
4016
|
+
}
|
|
4017
|
+
if (command.profileName) {
|
|
4018
|
+
flags.push("--profile", shellQuote(command.profileName));
|
|
4019
|
+
}
|
|
4020
|
+
return flags;
|
|
4021
|
+
}
|
|
4022
|
+
function analyticsPropertyActivationCommand(input) {
|
|
4023
|
+
return [
|
|
4024
|
+
"tender",
|
|
4025
|
+
"app",
|
|
4026
|
+
"analytics",
|
|
4027
|
+
"properties",
|
|
4028
|
+
"activate",
|
|
4029
|
+
shellQuote(input.command.artifactId),
|
|
4030
|
+
"--event",
|
|
4031
|
+
shellQuote(input.requirement.eventName),
|
|
4032
|
+
"--property",
|
|
4033
|
+
shellQuote(input.requirement.propertyKey),
|
|
4034
|
+
"--type",
|
|
4035
|
+
input.requirement.propertyType,
|
|
4036
|
+
...(input.dryRun ? ["--dry-run"] : []),
|
|
4037
|
+
...analyticsCommandAuthFlags(input.command),
|
|
4038
|
+
"--json",
|
|
4039
|
+
].join(" ");
|
|
4040
|
+
}
|
|
4041
|
+
function createAnalyticsPropertyGuidance(input) {
|
|
4042
|
+
let requirements;
|
|
4043
|
+
try {
|
|
4044
|
+
requirements = readRequiredAnalyticsPropertiesFromSpec(input.spec);
|
|
4045
|
+
}
|
|
4046
|
+
catch {
|
|
4047
|
+
return null;
|
|
4048
|
+
}
|
|
4049
|
+
if (requirements.length === 0) {
|
|
4050
|
+
return null;
|
|
4051
|
+
}
|
|
4052
|
+
const lines = [
|
|
4053
|
+
"To make this chart available for dashboard queries, activate the custom event property first:",
|
|
4054
|
+
];
|
|
4055
|
+
for (const requirement of requirements) {
|
|
4056
|
+
lines.push(analyticsPropertyActivationCommand({
|
|
4057
|
+
command: input.command,
|
|
4058
|
+
requirement,
|
|
4059
|
+
dryRun: true,
|
|
4060
|
+
}), analyticsPropertyActivationCommand({
|
|
4061
|
+
command: input.command,
|
|
4062
|
+
requirement,
|
|
4063
|
+
dryRun: false,
|
|
4064
|
+
}));
|
|
4065
|
+
}
|
|
4066
|
+
if (input.command.flagToken) {
|
|
4067
|
+
lines.push("Pass the same --token again, or use a saved profile/TENDER_API_TOKEN; tokens are not echoed in suggestions.");
|
|
4068
|
+
}
|
|
4069
|
+
lines.push("Then rerun the original analytics query.");
|
|
4070
|
+
return lines.join("\n");
|
|
4071
|
+
}
|
|
4072
|
+
async function requestAnalyticsQueryWithPropertyGuidance(input) {
|
|
4073
|
+
try {
|
|
4074
|
+
return await requestAnalyticsApi({
|
|
4075
|
+
artifactId: input.command.artifactId,
|
|
4076
|
+
analyticsPath: "query",
|
|
4077
|
+
method: "POST",
|
|
4078
|
+
baseUrl: input.credentials.baseUrl,
|
|
4079
|
+
token: input.credentials.token,
|
|
4080
|
+
fetcher: input.runtime.fetcher ?? fetch,
|
|
4081
|
+
body: { spec: input.spec },
|
|
4082
|
+
});
|
|
4083
|
+
}
|
|
4084
|
+
catch (error) {
|
|
4085
|
+
if (error instanceof TenderCliAnalyticsApiError &&
|
|
4086
|
+
error.reasonCode === "analytics_property_not_queryable") {
|
|
4087
|
+
const guidance = createAnalyticsPropertyGuidance({
|
|
4088
|
+
command: input.command,
|
|
4089
|
+
spec: input.spec,
|
|
4090
|
+
});
|
|
4091
|
+
if (guidance) {
|
|
4092
|
+
throw new TenderCliAnalyticsApiError(`${error.message}\n\n${guidance}`, error.reasonCode);
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
throw error;
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
async function requestAnalyticsDashboardsForCli(command, runtime) {
|
|
4099
|
+
const credentials = await resolveAnalyticsCredentials(command, runtime);
|
|
4100
|
+
const payload = await requestAnalyticsApi({
|
|
4101
|
+
artifactId: command.artifactId,
|
|
4102
|
+
analyticsPath: "dashboards",
|
|
4103
|
+
method: "GET",
|
|
4104
|
+
baseUrl: credentials.baseUrl,
|
|
4105
|
+
token: credentials.token,
|
|
4106
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
4107
|
+
});
|
|
4108
|
+
return { credentials, payload };
|
|
4109
|
+
}
|
|
3490
4110
|
function printAnalyticsPayload(command, io, payload, fallbackLines) {
|
|
3491
4111
|
if (command.json) {
|
|
3492
4112
|
io.stdout(JSON.stringify(payload, null, 2));
|
|
@@ -3775,6 +4395,47 @@ async function planSourceFiles(input) {
|
|
|
3775
4395
|
}
|
|
3776
4396
|
async function runAuthStatus(command, io, runtime) {
|
|
3777
4397
|
const env = runtime.env ?? process.env;
|
|
4398
|
+
if (command.profileName) {
|
|
4399
|
+
const config = await readConfig(env);
|
|
4400
|
+
const profile = config.profiles?.[command.profileName] ?? null;
|
|
4401
|
+
const result = {
|
|
4402
|
+
ok: Boolean(profile),
|
|
4403
|
+
command: command.command,
|
|
4404
|
+
source: profile ? "config" : null,
|
|
4405
|
+
profile: command.profileName,
|
|
4406
|
+
configPath: readConfigPath(env),
|
|
4407
|
+
baseUrl: profile?.baseUrl ?? null,
|
|
4408
|
+
tokenAvailable: Boolean(profile?.token),
|
|
4409
|
+
tokenPrefix: profile?.tokenPrefix ?? null,
|
|
4410
|
+
tenantId: profile?.tenantId ?? null,
|
|
4411
|
+
artifactId: profile?.artifactId ?? null,
|
|
4412
|
+
permissions: profile?.permissions ?? [],
|
|
4413
|
+
expiresAt: formatExpiry(profile?.expiresAt),
|
|
4414
|
+
};
|
|
4415
|
+
if (command.json) {
|
|
4416
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
4417
|
+
}
|
|
4418
|
+
else if (profile) {
|
|
4419
|
+
io.stdout([
|
|
4420
|
+
"authenticated: true",
|
|
4421
|
+
`source: config:${command.profileName}`,
|
|
4422
|
+
`base_url: ${profile.baseUrl}`,
|
|
4423
|
+
`token_prefix: ${profile.tokenPrefix ?? "(unknown)"}`,
|
|
4424
|
+
`tenant_id: ${profile.tenantId ?? "(unknown)"}`,
|
|
4425
|
+
`app_id: ${profile.artifactId ?? "(account)"}`,
|
|
4426
|
+
`permissions: ${(profile.permissions ?? []).join(" ") || "(unknown)"}`,
|
|
4427
|
+
`expires_at: ${formatExpiry(profile.expiresAt) ?? "(unknown)"}`,
|
|
4428
|
+
].join("\n"));
|
|
4429
|
+
}
|
|
4430
|
+
else {
|
|
4431
|
+
io.stdout([
|
|
4432
|
+
"authenticated: false",
|
|
4433
|
+
`profile: ${command.profileName}`,
|
|
4434
|
+
"next: tender auth login --device --profile edit-preview --artifact <artifact-id>",
|
|
4435
|
+
].join("\n"));
|
|
4436
|
+
}
|
|
4437
|
+
return profile ? 0 : 1;
|
|
4438
|
+
}
|
|
3778
4439
|
if (env.TENDER_API_TOKEN) {
|
|
3779
4440
|
const result = {
|
|
3780
4441
|
ok: true,
|
|
@@ -5491,14 +6152,11 @@ async function runAnalyticsQuery(command, io, runtime) {
|
|
|
5491
6152
|
specPath: command.specPath,
|
|
5492
6153
|
stdin: runtime.stdin,
|
|
5493
6154
|
});
|
|
5494
|
-
const payload = await
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
token: credentials.token,
|
|
5500
|
-
fetcher: runtime.fetcher ?? fetch,
|
|
5501
|
-
body: { spec },
|
|
6155
|
+
const payload = await requestAnalyticsQueryWithPropertyGuidance({
|
|
6156
|
+
command,
|
|
6157
|
+
credentials,
|
|
6158
|
+
runtime,
|
|
6159
|
+
spec,
|
|
5502
6160
|
});
|
|
5503
6161
|
const rows = Array.isArray(payload.rows) ? payload.rows : [];
|
|
5504
6162
|
printAnalyticsPayload(command, io, payload, [
|
|
@@ -5619,14 +6277,11 @@ async function runAnalyticsExportQuery(command, io, runtime) {
|
|
|
5619
6277
|
specPath: command.specPath,
|
|
5620
6278
|
stdin: runtime.stdin,
|
|
5621
6279
|
});
|
|
5622
|
-
const payload = await
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
token: credentials.token,
|
|
5628
|
-
fetcher: runtime.fetcher ?? fetch,
|
|
5629
|
-
body: { spec },
|
|
6280
|
+
const payload = await requestAnalyticsQueryWithPropertyGuidance({
|
|
6281
|
+
command,
|
|
6282
|
+
credentials,
|
|
6283
|
+
runtime,
|
|
6284
|
+
spec,
|
|
5630
6285
|
});
|
|
5631
6286
|
const rows = Array.isArray(payload.rows) ? payload.rows : [];
|
|
5632
6287
|
if (command.format === "csv") {
|
|
@@ -5636,6 +6291,58 @@ async function runAnalyticsExportQuery(command, io, runtime) {
|
|
|
5636
6291
|
io.stdout(JSON.stringify(payload, null, 2));
|
|
5637
6292
|
return 0;
|
|
5638
6293
|
}
|
|
6294
|
+
async function runAnalyticsPropertiesList(command, io, runtime) {
|
|
6295
|
+
const credentials = await resolveAnalyticsCredentials(command, runtime);
|
|
6296
|
+
const payload = await requestAnalyticsApi({
|
|
6297
|
+
artifactId: command.artifactId,
|
|
6298
|
+
analyticsPath: "properties",
|
|
6299
|
+
method: "GET",
|
|
6300
|
+
baseUrl: credentials.baseUrl,
|
|
6301
|
+
token: credentials.token,
|
|
6302
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
6303
|
+
});
|
|
6304
|
+
const properties = readAnalyticsPropertyRecords(payload).filter((property) => !command.eventName ||
|
|
6305
|
+
readCliStringField(property, "eventName") === command.eventName);
|
|
6306
|
+
printAnalyticsPayload(command, io, {
|
|
6307
|
+
ok: true,
|
|
6308
|
+
command: command.command,
|
|
6309
|
+
artifactId: command.artifactId,
|
|
6310
|
+
eventName: command.eventName,
|
|
6311
|
+
properties,
|
|
6312
|
+
}, [
|
|
6313
|
+
`app_id: ${command.artifactId}`,
|
|
6314
|
+
command.eventName ? `event: ${command.eventName}` : "event: all",
|
|
6315
|
+
`properties: ${properties.length}`,
|
|
6316
|
+
]);
|
|
6317
|
+
return 0;
|
|
6318
|
+
}
|
|
6319
|
+
async function runAnalyticsPropertiesActivate(command, io, runtime) {
|
|
6320
|
+
const credentials = await resolveAnalyticsCredentials(command, runtime);
|
|
6321
|
+
const request = {
|
|
6322
|
+
eventName: command.eventName,
|
|
6323
|
+
propertyKey: command.propertyKey,
|
|
6324
|
+
propertyType: command.propertyType,
|
|
6325
|
+
label: command.label ?? command.propertyKey,
|
|
6326
|
+
dryRun: command.dryRun,
|
|
6327
|
+
};
|
|
6328
|
+
const payload = await requestAnalyticsApi({
|
|
6329
|
+
artifactId: command.artifactId,
|
|
6330
|
+
analyticsPath: "properties",
|
|
6331
|
+
method: "POST",
|
|
6332
|
+
baseUrl: credentials.baseUrl,
|
|
6333
|
+
token: credentials.token,
|
|
6334
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
6335
|
+
body: request,
|
|
6336
|
+
});
|
|
6337
|
+
printAnalyticsPayload(command, io, payload, [
|
|
6338
|
+
`app_id: ${command.artifactId}`,
|
|
6339
|
+
`event: ${command.eventName}`,
|
|
6340
|
+
`property: ${command.propertyKey}`,
|
|
6341
|
+
`type: ${command.propertyType}`,
|
|
6342
|
+
...(command.dryRun ? ["dry_run: true"] : []),
|
|
6343
|
+
]);
|
|
6344
|
+
return 0;
|
|
6345
|
+
}
|
|
5639
6346
|
async function runAnalyticsChartsCreate(command, io, runtime) {
|
|
5640
6347
|
const spec = await readAnalyticsSpec({
|
|
5641
6348
|
specPath: command.specPath,
|
|
@@ -5647,11 +6354,25 @@ async function runAnalyticsChartsCreate(command, io, runtime) {
|
|
|
5647
6354
|
spec,
|
|
5648
6355
|
layout: {},
|
|
5649
6356
|
};
|
|
6357
|
+
const requiredProperties = readRequiredAnalyticsPropertiesFromSpec(spec);
|
|
6358
|
+
const credentials = !command.dryRun || requiredProperties.length > 0
|
|
6359
|
+
? await resolveAnalyticsCredentials(command, runtime)
|
|
6360
|
+
: null;
|
|
6361
|
+
const properties = credentials
|
|
6362
|
+
? await ensureAnalyticsProperties({
|
|
6363
|
+
command,
|
|
6364
|
+
credentials,
|
|
6365
|
+
runtime,
|
|
6366
|
+
requirements: requiredProperties,
|
|
6367
|
+
dryRun: command.dryRun,
|
|
6368
|
+
})
|
|
6369
|
+
: [];
|
|
5650
6370
|
if (command.dryRun) {
|
|
5651
6371
|
const payload = {
|
|
5652
6372
|
ok: true,
|
|
5653
6373
|
dryRun: true,
|
|
5654
6374
|
artifactId: command.artifactId,
|
|
6375
|
+
properties,
|
|
5655
6376
|
request: body,
|
|
5656
6377
|
};
|
|
5657
6378
|
printAnalyticsPayload(command, io, payload, [
|
|
@@ -5662,7 +6383,9 @@ async function runAnalyticsChartsCreate(command, io, runtime) {
|
|
|
5662
6383
|
]);
|
|
5663
6384
|
return 0;
|
|
5664
6385
|
}
|
|
5665
|
-
|
|
6386
|
+
if (!credentials) {
|
|
6387
|
+
throw new Error("Analytics credentials are required to create saved charts.");
|
|
6388
|
+
}
|
|
5666
6389
|
const payload = await requestAnalyticsApi({
|
|
5667
6390
|
artifactId: command.artifactId,
|
|
5668
6391
|
analyticsPath: "charts",
|
|
@@ -5672,9 +6395,138 @@ async function runAnalyticsChartsCreate(command, io, runtime) {
|
|
|
5672
6395
|
fetcher: runtime.fetcher ?? fetch,
|
|
5673
6396
|
body,
|
|
5674
6397
|
});
|
|
5675
|
-
printAnalyticsPayload(command, io, payload, [
|
|
6398
|
+
printAnalyticsPayload(command, io, { ...payload, properties }, [
|
|
5676
6399
|
`app_id: ${command.artifactId}`,
|
|
5677
6400
|
`chart_id: ${String(payload.chart?.id ?? "")}`,
|
|
6401
|
+
`properties: ${properties.length}`,
|
|
6402
|
+
]);
|
|
6403
|
+
return 0;
|
|
6404
|
+
}
|
|
6405
|
+
async function runAnalyticsChartsList(command, io, runtime) {
|
|
6406
|
+
const { payload } = await requestAnalyticsDashboardsForCli(command, runtime);
|
|
6407
|
+
const dashboard = findAnalyticsDashboard(payload, command.dashboardId);
|
|
6408
|
+
if (!dashboard) {
|
|
6409
|
+
throw new Error(`Analytics dashboard ${command.dashboardId} was not found for app ${command.artifactId}.`);
|
|
6410
|
+
}
|
|
6411
|
+
const charts = readAnalyticsChartRecords(dashboard);
|
|
6412
|
+
const dashboardName = readCliStringField(dashboard, "name");
|
|
6413
|
+
printAnalyticsPayload(command, io, {
|
|
6414
|
+
ok: true,
|
|
6415
|
+
command: command.command,
|
|
6416
|
+
artifactId: command.artifactId,
|
|
6417
|
+
dashboardId: command.dashboardId,
|
|
6418
|
+
dashboard: {
|
|
6419
|
+
id: command.dashboardId,
|
|
6420
|
+
name: dashboardName,
|
|
6421
|
+
},
|
|
6422
|
+
charts,
|
|
6423
|
+
}, [
|
|
6424
|
+
`app_id: ${command.artifactId}`,
|
|
6425
|
+
`dashboard_id: ${command.dashboardId}`,
|
|
6426
|
+
`charts: ${charts.length}`,
|
|
6427
|
+
]);
|
|
6428
|
+
return 0;
|
|
6429
|
+
}
|
|
6430
|
+
async function runAnalyticsChartsUpdate(command, io, runtime) {
|
|
6431
|
+
const { credentials, payload: dashboardsPayload } = await requestAnalyticsDashboardsForCli(command, runtime);
|
|
6432
|
+
const existing = findAnalyticsChart(dashboardsPayload, command.chartId);
|
|
6433
|
+
if (!existing) {
|
|
6434
|
+
throw new Error(`Analytics chart ${command.chartId} was not found for app ${command.artifactId}.`);
|
|
6435
|
+
}
|
|
6436
|
+
const spec = command.specPath
|
|
6437
|
+
? await readAnalyticsSpec({
|
|
6438
|
+
specPath: command.specPath,
|
|
6439
|
+
stdin: runtime.stdin,
|
|
6440
|
+
})
|
|
6441
|
+
: readAnalyticsChartSpec(existing.chart);
|
|
6442
|
+
const title = command.title ?? readAnalyticsChartTitle(existing.chart);
|
|
6443
|
+
const body = {
|
|
6444
|
+
title,
|
|
6445
|
+
spec,
|
|
6446
|
+
layout: readAnalyticsChartLayout(existing.chart),
|
|
6447
|
+
};
|
|
6448
|
+
const properties = await ensureAnalyticsProperties({
|
|
6449
|
+
command,
|
|
6450
|
+
credentials,
|
|
6451
|
+
runtime,
|
|
6452
|
+
requirements: readRequiredAnalyticsPropertiesFromSpec(spec),
|
|
6453
|
+
dryRun: command.dryRun,
|
|
6454
|
+
});
|
|
6455
|
+
const dashboardId = readCliStringField(existing.dashboard, "id");
|
|
6456
|
+
if (command.dryRun) {
|
|
6457
|
+
printAnalyticsPayload(command, io, {
|
|
6458
|
+
ok: true,
|
|
6459
|
+
dryRun: true,
|
|
6460
|
+
artifactId: command.artifactId,
|
|
6461
|
+
chartId: command.chartId,
|
|
6462
|
+
dashboardId,
|
|
6463
|
+
currentChart: existing.chart,
|
|
6464
|
+
properties,
|
|
6465
|
+
request: body,
|
|
6466
|
+
}, [
|
|
6467
|
+
`app_id: ${command.artifactId}`,
|
|
6468
|
+
`chart_id: ${command.chartId}`,
|
|
6469
|
+
`title: ${title}`,
|
|
6470
|
+
"dry_run: true",
|
|
6471
|
+
]);
|
|
6472
|
+
return 0;
|
|
6473
|
+
}
|
|
6474
|
+
const payload = await requestAnalyticsApi({
|
|
6475
|
+
artifactId: command.artifactId,
|
|
6476
|
+
analyticsPath: "charts",
|
|
6477
|
+
resourceId: command.chartId,
|
|
6478
|
+
method: "PATCH",
|
|
6479
|
+
baseUrl: credentials.baseUrl,
|
|
6480
|
+
token: credentials.token,
|
|
6481
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
6482
|
+
body,
|
|
6483
|
+
});
|
|
6484
|
+
printAnalyticsPayload(command, io, { ...payload, properties }, [
|
|
6485
|
+
`app_id: ${command.artifactId}`,
|
|
6486
|
+
`chart_id: ${command.chartId}`,
|
|
6487
|
+
`title: ${String(payload.chart?.title ?? title)}`,
|
|
6488
|
+
`properties: ${properties.length}`,
|
|
6489
|
+
]);
|
|
6490
|
+
return 0;
|
|
6491
|
+
}
|
|
6492
|
+
async function runAnalyticsChartsDelete(command, io, runtime) {
|
|
6493
|
+
const { credentials, payload: dashboardsPayload } = await requestAnalyticsDashboardsForCli(command, runtime);
|
|
6494
|
+
const existing = findAnalyticsChart(dashboardsPayload, command.chartId);
|
|
6495
|
+
if (!existing) {
|
|
6496
|
+
throw new Error(`Analytics chart ${command.chartId} was not found for app ${command.artifactId}.`);
|
|
6497
|
+
}
|
|
6498
|
+
const dashboardId = readCliStringField(existing.dashboard, "id");
|
|
6499
|
+
if (command.dryRun) {
|
|
6500
|
+
printAnalyticsPayload(command, io, {
|
|
6501
|
+
ok: true,
|
|
6502
|
+
dryRun: true,
|
|
6503
|
+
artifactId: command.artifactId,
|
|
6504
|
+
chartId: command.chartId,
|
|
6505
|
+
dashboardId,
|
|
6506
|
+
currentChart: existing.chart,
|
|
6507
|
+
request: {
|
|
6508
|
+
chartId: command.chartId,
|
|
6509
|
+
},
|
|
6510
|
+
}, [
|
|
6511
|
+
`app_id: ${command.artifactId}`,
|
|
6512
|
+
`chart_id: ${command.chartId}`,
|
|
6513
|
+
"dry_run: true",
|
|
6514
|
+
]);
|
|
6515
|
+
return 0;
|
|
6516
|
+
}
|
|
6517
|
+
const payload = await requestAnalyticsApi({
|
|
6518
|
+
artifactId: command.artifactId,
|
|
6519
|
+
analyticsPath: "charts",
|
|
6520
|
+
resourceId: command.chartId,
|
|
6521
|
+
method: "DELETE",
|
|
6522
|
+
baseUrl: credentials.baseUrl,
|
|
6523
|
+
token: credentials.token,
|
|
6524
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
6525
|
+
});
|
|
6526
|
+
printAnalyticsPayload(command, io, payload, [
|
|
6527
|
+
`app_id: ${command.artifactId}`,
|
|
6528
|
+
`chart_id: ${command.chartId}`,
|
|
6529
|
+
`deleted: ${String(payload.deleted ?? "")}`,
|
|
5678
6530
|
]);
|
|
5679
6531
|
return 0;
|
|
5680
6532
|
}
|
|
@@ -5793,6 +6645,21 @@ export async function runTenderCli(args, io = {
|
|
|
5793
6645
|
else if (command.topic === "app analytics charts create") {
|
|
5794
6646
|
io.stdout(analyticsChartsCreateHelp());
|
|
5795
6647
|
}
|
|
6648
|
+
else if (command.topic === "app analytics charts list") {
|
|
6649
|
+
io.stdout(analyticsChartsListHelp());
|
|
6650
|
+
}
|
|
6651
|
+
else if (command.topic === "app analytics charts update") {
|
|
6652
|
+
io.stdout(analyticsChartsUpdateHelp());
|
|
6653
|
+
}
|
|
6654
|
+
else if (command.topic === "app analytics charts delete") {
|
|
6655
|
+
io.stdout(analyticsChartsDeleteHelp());
|
|
6656
|
+
}
|
|
6657
|
+
else if (command.topic === "app analytics properties list") {
|
|
6658
|
+
io.stdout(analyticsPropertiesListHelp());
|
|
6659
|
+
}
|
|
6660
|
+
else if (command.topic === "app analytics properties activate") {
|
|
6661
|
+
io.stdout(analyticsPropertiesActivateHelp());
|
|
6662
|
+
}
|
|
5796
6663
|
else if (command.topic === "app") {
|
|
5797
6664
|
io.stdout(appHelp());
|
|
5798
6665
|
}
|
|
@@ -5894,6 +6761,21 @@ export async function runTenderCli(args, io = {
|
|
|
5894
6761
|
if (command.command === "app analytics charts create") {
|
|
5895
6762
|
return await runAnalyticsChartsCreate(command, io, runtime);
|
|
5896
6763
|
}
|
|
6764
|
+
if (command.command === "app analytics charts list") {
|
|
6765
|
+
return await runAnalyticsChartsList(command, io, runtime);
|
|
6766
|
+
}
|
|
6767
|
+
if (command.command === "app analytics charts update") {
|
|
6768
|
+
return await runAnalyticsChartsUpdate(command, io, runtime);
|
|
6769
|
+
}
|
|
6770
|
+
if (command.command === "app analytics charts delete") {
|
|
6771
|
+
return await runAnalyticsChartsDelete(command, io, runtime);
|
|
6772
|
+
}
|
|
6773
|
+
if (command.command === "app analytics properties list") {
|
|
6774
|
+
return await runAnalyticsPropertiesList(command, io, runtime);
|
|
6775
|
+
}
|
|
6776
|
+
if (command.command === "app analytics properties activate") {
|
|
6777
|
+
return await runAnalyticsPropertiesActivate(command, io, runtime);
|
|
6778
|
+
}
|
|
5897
6779
|
if (command.command === "app git remote") {
|
|
5898
6780
|
return await runGitRemote(command, io, runtime);
|
|
5899
6781
|
}
|