@get-dx/cli 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +4 -0
- package/dist/commands/attributeGroups.js +100 -0
- package/dist/commands/auth.js +2 -1
- package/dist/commands/catalog/entityTypes.js +6 -6
- package/dist/commands/doctor.js +85 -0
- package/dist/commands/scorecards.js +6 -6
- package/dist/commands/snapshots.js +116 -0
- package/dist/commands/studio/reports.js +6 -6
- package/dist/commands/teams.js +1 -1
- package/dist/commands/workflow-blank-template.yaml +78 -0
- package/dist/commands/workflows.js +112 -2
- package/dist/commands/workflowsRendering.js +21 -9
- package/dist/config.js +70 -9
- package/dist/doctor/auth.js +133 -0
- package/dist/doctor/cli.js +154 -0
- package/dist/doctor/config.js +119 -0
- package/dist/doctor/environment.js +60 -0
- package/dist/doctor/format.js +68 -0
- package/dist/doctor/index.js +81 -0
- package/dist/doctor/network.js +224 -0
- package/dist/doctor/probes.js +130 -0
- package/dist/doctor/skill.js +16 -0
- package/dist/doctor/types.js +11 -0
- package/dist/http.js +2 -0
- package/dist/secrets.js +87 -9
- package/dist/ui/blocks.js +46 -0
- package/dist/ui.js +8 -1
- package/dist/versionCheck.js +25 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Command, Option } from "commander";
|
|
2
|
+
import { attributeGroupsCommand } from "./commands/attributeGroups.js";
|
|
2
3
|
import { authCommand } from "./commands/auth.js";
|
|
3
4
|
import { catalogCommand } from "./commands/catalog.js";
|
|
5
|
+
import { doctorCommand } from "./commands/doctor.js";
|
|
4
6
|
import { initCommand } from "./commands/init.js";
|
|
5
7
|
import { scorecardsCommand } from "./commands/scorecards.js";
|
|
6
8
|
import { snapshotsCommand } from "./commands/snapshots.js";
|
|
@@ -48,8 +50,10 @@ function createProgram() {
|
|
|
48
50
|
.addOption(new Option("--agent <name>", "Agent name to send as an HTTP header"))
|
|
49
51
|
.addOption(new Option("--agent-model <model>", "Agent model name to send as an HTTP header"))
|
|
50
52
|
.addOption(new Option("--agent-session-id <id>", "Agent session ID to send as an HTTP header"));
|
|
53
|
+
program.addCommand(attributeGroupsCommand());
|
|
51
54
|
program.addCommand(authCommand());
|
|
52
55
|
program.addCommand(catalogCommand());
|
|
56
|
+
program.addCommand(doctorCommand());
|
|
53
57
|
program.addCommand(initCommand());
|
|
54
58
|
program.addCommand(scorecardsCommand());
|
|
55
59
|
program.addCommand(snapshotsCommand());
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { createExampleText, getContext, wrapAction, } from "../commandHelpers.js";
|
|
3
|
+
import { request } from "../http.js";
|
|
4
|
+
import { renderJson, renderRichText } from "../renderers.js";
|
|
5
|
+
import { buildRuntime } from "../runtime.js";
|
|
6
|
+
import * as ui from "../ui.js";
|
|
7
|
+
export function attributeGroupsCommand() {
|
|
8
|
+
const attributeGroups = new Command()
|
|
9
|
+
.name("attributeGroups")
|
|
10
|
+
.description("List attribute groups and values for filtering snapshot scores");
|
|
11
|
+
attributeGroups
|
|
12
|
+
.command("list")
|
|
13
|
+
.description("List attribute groups and their attribute values")
|
|
14
|
+
.addHelpText("afterAll", createExampleText([
|
|
15
|
+
{
|
|
16
|
+
label: "List attribute groups in human-readable output",
|
|
17
|
+
command: "dx attributeGroups list",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
label: "Print raw JSON for automation",
|
|
21
|
+
command: "dx --json attributeGroups list",
|
|
22
|
+
},
|
|
23
|
+
]))
|
|
24
|
+
.action(wrapAction(async (_options, command) => {
|
|
25
|
+
const runtime = await buildRuntime(getContext(command));
|
|
26
|
+
const response = await listAttributeGroups(runtime);
|
|
27
|
+
if (runtime.context.json) {
|
|
28
|
+
renderJson(response);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
renderAttributeGroups(response.attribute_groups);
|
|
32
|
+
}
|
|
33
|
+
}));
|
|
34
|
+
return attributeGroups;
|
|
35
|
+
}
|
|
36
|
+
export async function listAttributeGroups(runtime) {
|
|
37
|
+
const response = await request(runtime, "/attributeGroups.list", {
|
|
38
|
+
method: "GET",
|
|
39
|
+
});
|
|
40
|
+
return response.body;
|
|
41
|
+
}
|
|
42
|
+
function renderAttributeGroups(unsortedGroups) {
|
|
43
|
+
const attributeGroups = [...unsortedGroups].sort(byPosition);
|
|
44
|
+
const blocks = [ui.h1("Attribute Groups")];
|
|
45
|
+
blocks.push(ui.p(`Displaying ${ui.bold(attributeGroups.length.toString())} attribute groups.`));
|
|
46
|
+
if (attributeGroups.length === 0) {
|
|
47
|
+
blocks.push(ui.p("No attribute groups are available for this account. Create or sync attribute groups in DX before filtering snapshot scores by attribute."));
|
|
48
|
+
renderRichText(blocks);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
for (const attributeGroup of attributeGroups) {
|
|
52
|
+
blocks.push(ui.h2(`${attributeGroup.name} (${ui.code(attributeGroup.id)})`));
|
|
53
|
+
blocks.push(ui.dl([
|
|
54
|
+
ui.dli("Type", formatAttributeGroupType(attributeGroup.type)),
|
|
55
|
+
ui.dli("ID", ui.code(attributeGroup.id)),
|
|
56
|
+
...(attributeGroup.position === null
|
|
57
|
+
? []
|
|
58
|
+
: [ui.dli("Position", attributeGroup.position.toString())]),
|
|
59
|
+
...(attributeGroup.hidden ? [ui.dli("Hidden", "Yes")] : []),
|
|
60
|
+
// The API only returns privileged groups the user can see.
|
|
61
|
+
...(attributeGroup.privileged ? [ui.dli("Privileged", "Yes")] : []),
|
|
62
|
+
], { termWidth: 12 }));
|
|
63
|
+
if (attributeGroup.attributes.length === 0) {
|
|
64
|
+
blocks.push(ui.p(`Attributes: ${ui.dim("(None)")}`));
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
blocks.push(ui.p("Attributes:"));
|
|
68
|
+
blocks.push(ui.ul([...attributeGroup.attributes]
|
|
69
|
+
.sort(byPositionThenName)
|
|
70
|
+
.map((attribute) => ui.li(`${attribute.name} (${ui.code(attribute.id)})`))));
|
|
71
|
+
}
|
|
72
|
+
renderRichText(blocks);
|
|
73
|
+
}
|
|
74
|
+
// Items without a position sort last. Ties keep the API's order (a stable
|
|
75
|
+
// sort), which is by id for groups.
|
|
76
|
+
function byPosition(a, b) {
|
|
77
|
+
if (a.position === b.position)
|
|
78
|
+
return 0;
|
|
79
|
+
if (a.position === null)
|
|
80
|
+
return 1;
|
|
81
|
+
if (b.position === null)
|
|
82
|
+
return -1;
|
|
83
|
+
return a.position - b.position;
|
|
84
|
+
}
|
|
85
|
+
// Attributes are ordered by position, then name.
|
|
86
|
+
function byPositionThenName(a, b) {
|
|
87
|
+
return byPosition(a, b) || a.name.localeCompare(b.name);
|
|
88
|
+
}
|
|
89
|
+
function formatAttributeGroupType(type) {
|
|
90
|
+
switch (type.toUpperCase()) {
|
|
91
|
+
case "ADMIN_MANAGED":
|
|
92
|
+
return "Admin managed";
|
|
93
|
+
case "SELF_REPORTED":
|
|
94
|
+
return "Self reported";
|
|
95
|
+
case "DX_MANAGED":
|
|
96
|
+
return "DX managed";
|
|
97
|
+
default:
|
|
98
|
+
return type;
|
|
99
|
+
}
|
|
100
|
+
}
|
package/dist/commands/auth.js
CHANGED
|
@@ -154,8 +154,9 @@ export function authCommand() {
|
|
|
154
154
|
return auth;
|
|
155
155
|
}
|
|
156
156
|
// TODO: should we move this somewhere more central, since `init` needs it?
|
|
157
|
-
export async function getAuthInfo(runtime) {
|
|
157
|
+
export async function getAuthInfo(runtime, options) {
|
|
158
158
|
const response = await request(runtime, "/auth.info", {
|
|
159
|
+
...options,
|
|
159
160
|
method: "GET",
|
|
160
161
|
});
|
|
161
162
|
return response.body;
|
|
@@ -14,9 +14,9 @@ export function entityTypesCommand() {
|
|
|
14
14
|
.description("Manage catalog entity types");
|
|
15
15
|
entityTypes
|
|
16
16
|
.command("create")
|
|
17
|
-
.description("Create a new entity type from a YAML file or stdin. The `init` command can be used to generate a starting template.")
|
|
18
|
-
.option("--from-file <path>", "Read a YAML file and create an entity type from its contents")
|
|
19
|
-
.option("--from-stdin", "Read YAML from stdin and create an entity type from its contents")
|
|
17
|
+
.description("Create a new entity type from a YAML or JSON file or stdin. The `init` command can be used to generate a starting template.")
|
|
18
|
+
.option("--from-file <path>", "Read a YAML or JSON file and create an entity type from its contents")
|
|
19
|
+
.option("--from-stdin", "Read YAML or JSON from stdin and create an entity type from its contents")
|
|
20
20
|
.addHelpText("afterAll", createExampleText([
|
|
21
21
|
{
|
|
22
22
|
label: "Generate a blank template first",
|
|
@@ -205,10 +205,10 @@ export function entityTypesCommand() {
|
|
|
205
205
|
}));
|
|
206
206
|
entityTypes
|
|
207
207
|
.command("update")
|
|
208
|
-
.description("Update an existing entity type from a YAML file or stdin. The `init` command can be used to initialize the entity type file.")
|
|
208
|
+
.description("Update an existing entity type from a YAML or JSON file or stdin. The `init` command can be used to initialize the entity type file.")
|
|
209
209
|
.argument("<identifier>", "The unique identifier of the entity type")
|
|
210
|
-
.option("--from-file <path>", "Read a YAML file and update the entity type with its contents")
|
|
211
|
-
.option("--from-stdin", "Read YAML from stdin and update the entity type with its contents")
|
|
210
|
+
.option("--from-file <path>", "Read a YAML or JSON file and update the entity type with its contents")
|
|
211
|
+
.option("--from-stdin", "Read YAML or JSON from stdin and update the entity type with its contents")
|
|
212
212
|
.addHelpText("afterAll", createExampleText([
|
|
213
213
|
{
|
|
214
214
|
label: "Initialize a file first",
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { createExampleText, getContext, wrapAction, } from "../commandHelpers.js";
|
|
3
|
+
import { runDoctor } from "../doctor/index.js";
|
|
4
|
+
import { AsyncProgressReporter, renderJson, renderRichText, } from "../renderers.js";
|
|
5
|
+
import * as ui from "../ui.js";
|
|
6
|
+
const MIN_LABEL_WIDTH = 26;
|
|
7
|
+
const PHASE_MESSAGES = {
|
|
8
|
+
config: "Checking configuration…",
|
|
9
|
+
network: "Checking network…",
|
|
10
|
+
auth: "Checking authentication…",
|
|
11
|
+
version: "Checking for updates…",
|
|
12
|
+
};
|
|
13
|
+
export function doctorCommand() {
|
|
14
|
+
return new Command("doctor")
|
|
15
|
+
.description("Diagnose problems with your CLI instance's installation, configuration, networking, and authentication")
|
|
16
|
+
.addHelpText("afterAll", createExampleText([
|
|
17
|
+
{ label: "Check your setup", command: "dx doctor" },
|
|
18
|
+
{ label: "Print the report as JSON", command: "dx doctor --json" },
|
|
19
|
+
]))
|
|
20
|
+
.action(wrapAction(async (_options, command) => {
|
|
21
|
+
const context = getContext(command);
|
|
22
|
+
const progress = new AsyncProgressReporter();
|
|
23
|
+
progress.start(PHASE_MESSAGES.config);
|
|
24
|
+
let report;
|
|
25
|
+
try {
|
|
26
|
+
report = await runDoctor(context, {
|
|
27
|
+
onPhase: (phase) => progress.update(PHASE_MESSAGES[phase]),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
progress.stop();
|
|
32
|
+
}
|
|
33
|
+
if (context.json) {
|
|
34
|
+
renderJson(report);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
renderDoctorReport(report);
|
|
38
|
+
}
|
|
39
|
+
if (!report.ok) {
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
}
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
export function renderDoctorReport(report) {
|
|
45
|
+
const labelWidth = Math.max(MIN_LABEL_WIDTH, ...report.sections.flatMap((section) => section.checks.map((check) => check.label.length + 2)));
|
|
46
|
+
renderRichText([
|
|
47
|
+
...report.sections.map((section) => ui.statusList(section.title, section.checks.map((check) => ({
|
|
48
|
+
status: check.status,
|
|
49
|
+
label: check.label,
|
|
50
|
+
detail: formatDetail(check),
|
|
51
|
+
hint: check.hint,
|
|
52
|
+
})), { labelWidth })),
|
|
53
|
+
ui.p(summaryLine(report)),
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
function formatDetail(check) {
|
|
57
|
+
if (!check.source) {
|
|
58
|
+
return check.message;
|
|
59
|
+
}
|
|
60
|
+
return `${check.message} ${ui.dim(`[from ${sourceName(check.source)}]`)}`;
|
|
61
|
+
}
|
|
62
|
+
function sourceName(source) {
|
|
63
|
+
switch (source) {
|
|
64
|
+
case "env":
|
|
65
|
+
return "env";
|
|
66
|
+
case "config":
|
|
67
|
+
return "config file";
|
|
68
|
+
case "default":
|
|
69
|
+
return "default";
|
|
70
|
+
case "keychain":
|
|
71
|
+
return "keychain";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function summaryLine(report) {
|
|
75
|
+
const { fail, warn } = report.summary;
|
|
76
|
+
const plural = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
77
|
+
if (fail > 0) {
|
|
78
|
+
const failed = ui.error(`${plural(fail, "check")} failed`);
|
|
79
|
+
return warn > 0 ? `${failed}, ${plural(warn, "warning")}.` : `${failed}.`;
|
|
80
|
+
}
|
|
81
|
+
if (warn > 0) {
|
|
82
|
+
return ui.warning(`All required checks passed, ${plural(warn, "warning")}.`);
|
|
83
|
+
}
|
|
84
|
+
return ui.success("All checks passed.");
|
|
85
|
+
}
|
|
@@ -14,9 +14,9 @@ export function scorecardsCommand() {
|
|
|
14
14
|
.description("Manage scorecards");
|
|
15
15
|
scorecards
|
|
16
16
|
.command("create")
|
|
17
|
-
.description("Create a new scorecard from a YAML file or stdin. The `init` command can be used to generate a starting template.")
|
|
18
|
-
.option("--from-file <path>", "Read a YAML file and create a scorecard from its contents")
|
|
19
|
-
.option("--from-stdin", "Read YAML from stdin and create a scorecard from its contents")
|
|
17
|
+
.description("Create a new scorecard from a YAML or JSON file or stdin. The `init` command can be used to generate a starting template.")
|
|
18
|
+
.option("--from-file <path>", "Read a YAML or JSON file and create a scorecard from its contents")
|
|
19
|
+
.option("--from-stdin", "Read YAML or JSON from stdin and create a scorecard from its contents")
|
|
20
20
|
.addHelpText("afterAll", createExampleText([
|
|
21
21
|
{
|
|
22
22
|
label: "Generate a blank template first",
|
|
@@ -216,10 +216,10 @@ export function scorecardsCommand() {
|
|
|
216
216
|
}));
|
|
217
217
|
scorecards
|
|
218
218
|
.command("update")
|
|
219
|
-
.description("Update an existing scorecard from a YAML file or stdin. The `init` command can be used to initialize the scorecard file.")
|
|
219
|
+
.description("Update an existing scorecard from a YAML or JSON file or stdin. The `init` command can be used to initialize the scorecard file.")
|
|
220
220
|
.argument("<id>", "The unique ID of the scorecard")
|
|
221
|
-
.option("--from-file <path>", "Read a YAML file and update the scorecard with its contents")
|
|
222
|
-
.option("--from-stdin", "Read YAML from stdin and update the scorecard with its contents")
|
|
221
|
+
.option("--from-file <path>", "Read a YAML or JSON file and update the scorecard with its contents")
|
|
222
|
+
.option("--from-stdin", "Read YAML or JSON from stdin and update the scorecard with its contents")
|
|
223
223
|
.addHelpText("afterAll", createExampleText([
|
|
224
224
|
{
|
|
225
225
|
label: "Initialize a file first",
|
|
@@ -4,6 +4,7 @@ import { CliError, EXIT_CODES } from "../errors.js";
|
|
|
4
4
|
import { request } from "../http.js";
|
|
5
5
|
import { renderJson, renderRichText } from "../renderers.js";
|
|
6
6
|
import { buildRuntime } from "../runtime.js";
|
|
7
|
+
import { formatTeamMember } from "./teams.js";
|
|
7
8
|
import * as ui from "../ui.js";
|
|
8
9
|
export function snapshotsCommand() {
|
|
9
10
|
const snapshots = new Command()
|
|
@@ -135,6 +136,35 @@ export function snapshotsCommand() {
|
|
|
135
136
|
renderSnapshots(response.snapshots, runtime);
|
|
136
137
|
}
|
|
137
138
|
}));
|
|
139
|
+
const snapshotTeams = new Command()
|
|
140
|
+
.name("snapshotTeams")
|
|
141
|
+
.description("Work with snapshot teams");
|
|
142
|
+
snapshotTeams
|
|
143
|
+
.command("list")
|
|
144
|
+
.description("List teams for a snapshot")
|
|
145
|
+
.option("--id <id>", "The unique ID of the snapshot")
|
|
146
|
+
.addHelpText("afterAll", createExampleText([
|
|
147
|
+
{
|
|
148
|
+
label: "List teams for a snapshot",
|
|
149
|
+
command: "dx snapshots snapshotTeams list --id MjUyNbaY",
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
label: "List snapshot teams as JSON",
|
|
153
|
+
command: "dx --json snapshots snapshotTeams list --id MjUyNbaY",
|
|
154
|
+
},
|
|
155
|
+
]))
|
|
156
|
+
.action(wrapAction(async (options, command) => {
|
|
157
|
+
const snapshotId = parseRequiredTextOption(options.id, "--id");
|
|
158
|
+
const runtime = await buildRuntime(getContext(command));
|
|
159
|
+
const response = await listSnapshotTeams(runtime, snapshotId);
|
|
160
|
+
if (runtime.context.json) {
|
|
161
|
+
renderJson(response);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
renderSnapshotTeams(response.snapshot_teams, snapshotId, runtime);
|
|
165
|
+
}
|
|
166
|
+
}));
|
|
167
|
+
snapshots.addCommand(snapshotTeams);
|
|
138
168
|
return snapshots;
|
|
139
169
|
}
|
|
140
170
|
async function listSnapshotDriverComments(runtime, options) {
|
|
@@ -159,6 +189,13 @@ async function listSnapshotCsatComments(runtime, options) {
|
|
|
159
189
|
});
|
|
160
190
|
return response.body;
|
|
161
191
|
}
|
|
192
|
+
async function listSnapshotTeams(runtime, snapshotId) {
|
|
193
|
+
const response = await request(runtime, "/snapshots.snapshotTeams.list", {
|
|
194
|
+
method: "GET",
|
|
195
|
+
query: { snapshot_id: snapshotId },
|
|
196
|
+
});
|
|
197
|
+
return response.body;
|
|
198
|
+
}
|
|
162
199
|
async function getSnapshotInfo(runtime, snapshotId) {
|
|
163
200
|
const response = await request(runtime, "/snapshots.info", {
|
|
164
201
|
method: "GET",
|
|
@@ -237,6 +274,22 @@ function renderSnapshotCsatComments(comments, nextCursor, snapshotId, runtime) {
|
|
|
237
274
|
}
|
|
238
275
|
renderRichText(blocks);
|
|
239
276
|
}
|
|
277
|
+
function renderSnapshotTeams(teams, snapshotId, runtime) {
|
|
278
|
+
const blocks = [ui.h1("Snapshot Teams")];
|
|
279
|
+
blocks.push(ui.p(`Displaying ${ui.bold(teams.length.toString())} teams.`));
|
|
280
|
+
blocks.push(ui.p(`Snapshot Team IDs are only valid within this snapshot and feed ${ui.code("snapshot_team_ids")} score filters. Use ${ui.code("team_id")} with ${ui.code("dx teams info --team-id")}.`));
|
|
281
|
+
blocks.push(ui.p(ui.link(snapshotWebLink(snapshotId, "drivers", runtime))));
|
|
282
|
+
if (teams.length === 0) {
|
|
283
|
+
blocks.push(ui.p(ui.dim("(None)")));
|
|
284
|
+
renderRichText(blocks);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const rootTeams = buildSnapshotTeamTree(teams);
|
|
288
|
+
rootTeams.forEach((rootTeam) => {
|
|
289
|
+
appendSnapshotTeamBlock(blocks, rootTeam, 0);
|
|
290
|
+
});
|
|
291
|
+
renderRichText(blocks);
|
|
292
|
+
}
|
|
240
293
|
function renderSnapshotInfo(snapshot, snapshotId, runtime) {
|
|
241
294
|
const teamScores = snapshot.team_scores ?? [];
|
|
242
295
|
const blocks = [ui.h1("Snapshot Information")];
|
|
@@ -298,6 +351,69 @@ function extractCsatComments(response) {
|
|
|
298
351
|
response.comments ??
|
|
299
352
|
[]);
|
|
300
353
|
}
|
|
354
|
+
class SnapshotTeamTreeNode {
|
|
355
|
+
team;
|
|
356
|
+
children = [];
|
|
357
|
+
constructor(team) {
|
|
358
|
+
this.team = team;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function buildSnapshotTeamTree(teams) {
|
|
362
|
+
const nodes = new Map();
|
|
363
|
+
teams.forEach((team) => {
|
|
364
|
+
nodes.set(team.id, new SnapshotTeamTreeNode(team));
|
|
365
|
+
});
|
|
366
|
+
const rootTeams = [];
|
|
367
|
+
teams.forEach((team) => {
|
|
368
|
+
const currentNode = nodes.get(team.id);
|
|
369
|
+
if (currentNode.team.parent_id === null) {
|
|
370
|
+
// It's a top-level root node
|
|
371
|
+
rootTeams.push(currentNode);
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
// Append it to its parent's children array
|
|
375
|
+
nodes.get(currentNode.team.parent_id).children.push(currentNode);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
const sortNodes = (items) => {
|
|
379
|
+
items.sort((left, right) => compareSnapshotTeams(left.team, right.team));
|
|
380
|
+
for (const item of items) {
|
|
381
|
+
sortNodes(item.children);
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
sortNodes(rootTeams);
|
|
385
|
+
return rootTeams;
|
|
386
|
+
}
|
|
387
|
+
function compareSnapshotTeams(left, right) {
|
|
388
|
+
const leftName = left.name.toLocaleLowerCase();
|
|
389
|
+
const rightName = right.name.toLocaleLowerCase();
|
|
390
|
+
if (leftName !== rightName) {
|
|
391
|
+
return leftName.localeCompare(rightName);
|
|
392
|
+
}
|
|
393
|
+
// If the names are the same, compare the IDs
|
|
394
|
+
const leftId = left.id.toLocaleLowerCase();
|
|
395
|
+
const rightId = right.id.toLocaleLowerCase();
|
|
396
|
+
return leftId.localeCompare(rightId);
|
|
397
|
+
}
|
|
398
|
+
function appendSnapshotTeamBlock(blocks, node, depth) {
|
|
399
|
+
blocks.push(depth === 0
|
|
400
|
+
? ui.h2(node.team.name)
|
|
401
|
+
: ui.h3(`${"↳".repeat(depth)} ${node.team.name}`));
|
|
402
|
+
const details = [
|
|
403
|
+
optionalDetail("Snapshot Team ID", formatText(node.team.id), node.team.id),
|
|
404
|
+
optionalDetail("Team ID", formatText(node.team.team_id), node.team.team_id),
|
|
405
|
+
// Match `dx teams info`: show "(None)" when the API returns a null manager,
|
|
406
|
+
// omit the row only when the field is absent entirely.
|
|
407
|
+
ui.dli("Manager", formatTeamMember(node.team.manager)),
|
|
408
|
+
optionalDetail("Parent ID", formatText(node.team.parent_id), node.team.parent_id),
|
|
409
|
+
].filter((detail) => detail !== null);
|
|
410
|
+
if (details.length > 0) {
|
|
411
|
+
blocks.push(ui.dl(details, { termWidth: 18 }));
|
|
412
|
+
}
|
|
413
|
+
node.children.forEach((child) => {
|
|
414
|
+
appendSnapshotTeamBlock(blocks, child, depth + 1);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
301
417
|
function formatCommentHeading(comment) {
|
|
302
418
|
const heading = comment.item_name ??
|
|
303
419
|
comment.driver_name ??
|
|
@@ -17,9 +17,9 @@ export function reportsCommand() {
|
|
|
17
17
|
.description("Manage Data Studio reports");
|
|
18
18
|
reports
|
|
19
19
|
.command("create")
|
|
20
|
-
.description("Create a Data Studio report from a YAML file or stdin")
|
|
21
|
-
.option("--from-file <path>", "Read a YAML file and create a report from its contents")
|
|
22
|
-
.option("--from-stdin", "Read YAML from stdin and create a report from its contents")
|
|
20
|
+
.description("Create a Data Studio report from a YAML or JSON file or stdin")
|
|
21
|
+
.option("--from-file <path>", "Read a YAML or JSON file and create a report from its contents")
|
|
22
|
+
.option("--from-stdin", "Read YAML or JSON from stdin and create a report from its contents")
|
|
23
23
|
.addHelpText("afterAll", createNoteText(VARIABLES_NOTE_TEXT))
|
|
24
24
|
.addHelpText("afterAll", createExampleText([
|
|
25
25
|
{
|
|
@@ -175,10 +175,10 @@ export function reportsCommand() {
|
|
|
175
175
|
}));
|
|
176
176
|
reports
|
|
177
177
|
.command("update")
|
|
178
|
-
.description("Update a Data Studio report from a YAML file or stdin. The `init` command can be used to initialize the report file.")
|
|
178
|
+
.description("Update a Data Studio report from a YAML or JSON file or stdin. The `init` command can be used to initialize the report file.")
|
|
179
179
|
.argument("<id>", "Studio report ID")
|
|
180
|
-
.option("--from-file <path>", "Read a YAML file and update the report with its contents")
|
|
181
|
-
.option("--from-stdin", "Read YAML from stdin and update the report with its contents")
|
|
180
|
+
.option("--from-file <path>", "Read a YAML or JSON file and update the report with its contents")
|
|
181
|
+
.option("--from-stdin", "Read YAML or JSON from stdin and update the report with its contents")
|
|
182
182
|
.addHelpText("afterAll", createNoteText(VARIABLES_NOTE_TEXT))
|
|
183
183
|
.addHelpText("afterAll", createExampleText([
|
|
184
184
|
{
|
package/dist/commands/teams.js
CHANGED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Self-service workflow configuration
|
|
2
|
+
#
|
|
3
|
+
# Edit this file, then run:
|
|
4
|
+
#
|
|
5
|
+
# ```
|
|
6
|
+
# dx workflows create --from-file <path>
|
|
7
|
+
# ```
|
|
8
|
+
|
|
9
|
+
# Unique per account. Letters, numbers, ".", "_" and "-" only; "new" is reserved.
|
|
10
|
+
identifier: ""
|
|
11
|
+
name: ""
|
|
12
|
+
description: ""
|
|
13
|
+
# icon: globe
|
|
14
|
+
color: "#38bdf8"
|
|
15
|
+
|
|
16
|
+
# scope: "GLOBAL" (not tied to a catalog entity) or "ENTITY" (run against an entity)
|
|
17
|
+
scope: GLOBAL
|
|
18
|
+
|
|
19
|
+
# execution_type: "SIMPLE" or "EVENT_DRIVEN"
|
|
20
|
+
execution_type: SIMPLE
|
|
21
|
+
|
|
22
|
+
# Who can trigger a run:
|
|
23
|
+
# ANY - all users, no approval
|
|
24
|
+
# APPROVAL - all users, but an approver must accept (set approver_user_ids)
|
|
25
|
+
# RUNNERS - only selected users and teams (set runners)
|
|
26
|
+
# ENTITY - only owners of the entity (requires scope: ENTITY)
|
|
27
|
+
trigger_type: ANY
|
|
28
|
+
# approver_user_ids: ["<active user id>"] # at least one when APPROVAL
|
|
29
|
+
# runners: # at least one when RUNNERS
|
|
30
|
+
# - user_id: "<user id>" # each runner sets exactly one of
|
|
31
|
+
# - squad_id: "<team id>" # user_id or squad_id
|
|
32
|
+
# squad_linkage_id: null # optional
|
|
33
|
+
|
|
34
|
+
# Which entities the workflow can run against (only used when scope is ENTITY).
|
|
35
|
+
# entity_filter_type: "ENTITY_TYPES" or "SQL"
|
|
36
|
+
entity_filter_type: ENTITY_TYPES
|
|
37
|
+
# Entity type identifiers (when entity_filter_type is "ENTITY_TYPES"; at least
|
|
38
|
+
# one is required when scope is ENTITY)
|
|
39
|
+
entity_filter_type_identifiers: []
|
|
40
|
+
# SQL filter expression (required when scope is ENTITY and entity_filter_type is "SQL")
|
|
41
|
+
# entity_filter_sql: ""
|
|
42
|
+
|
|
43
|
+
# The HTTP request sent when the workflow runs.
|
|
44
|
+
# The url, body, header values and form_data values can use variables:
|
|
45
|
+
# {{run.id}}, {{workflow.identifier}}, {{workflow.name}},
|
|
46
|
+
# {{triggered_by_user.id}}, {{triggered_by_user.name}}, {{triggered_by_user.email}},
|
|
47
|
+
# {{triggered_by_user.team_id}}, {{triggered_by_user.team_name}},
|
|
48
|
+
# {{entity.identifier}}, {{entity.name}} (scope: ENTITY only),
|
|
49
|
+
# {{data.<parameter identifier>}}, {{secret.<name>}}
|
|
50
|
+
http_request:
|
|
51
|
+
# method: GET, POST, PUT, PATCH or DELETE
|
|
52
|
+
method: POST
|
|
53
|
+
url: "https://example.com/hooks/my-workflow"
|
|
54
|
+
headers:
|
|
55
|
+
Content-Type: application/json
|
|
56
|
+
# A string or null. Must be null when form_data is used.
|
|
57
|
+
body: |-
|
|
58
|
+
{"run_id": "{{run.id}}"}
|
|
59
|
+
# form_data:
|
|
60
|
+
# - key: example
|
|
61
|
+
# value: "{{data.example}}"
|
|
62
|
+
|
|
63
|
+
# Inputs collected from the user when triggering a run. Each parameter needs an
|
|
64
|
+
# identifier (letters, numbers, underscores), a name, a type and is_required.
|
|
65
|
+
parameters: []
|
|
66
|
+
# parameters:
|
|
67
|
+
# - identifier: environment
|
|
68
|
+
# name: Environment
|
|
69
|
+
# description: ""
|
|
70
|
+
# # type: STRING, INTEGER, FLOAT, BOOLEAN, SELECT, USER, TEAM, EMAIL or COMPUTED
|
|
71
|
+
# type: SELECT
|
|
72
|
+
# is_required: true
|
|
73
|
+
# default_value: staging
|
|
74
|
+
# # SELECT: {options: [...]} COMPUTED: {sql: "..."} other types: omit
|
|
75
|
+
# definition:
|
|
76
|
+
# options:
|
|
77
|
+
# - staging
|
|
78
|
+
# - production
|