@get-dx/cli 0.10.0 → 0.11.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.
@@ -24,6 +24,7 @@ export function renderRelationDeleted(relation) {
24
24
  function relationContent(relation) {
25
25
  return [
26
26
  ui.dl([
27
+ ui.dli("Public ID", ui.code(relation.public_id)),
27
28
  ui.dli("Identifier", ui.code(relation.identifier)),
28
29
  ui.dli("Relation", `${ui.code(relation.source_entity_type_identifier)} ${relation.type} ${ui.code(relation.target_entity_type_identifier)}`),
29
30
  ui.dli("Type", relation.type),
@@ -28,6 +28,11 @@ entity_filter_type_identifiers:
28
28
  # SQL filter expression (when entity_filter_type is "sql")
29
29
  entity_filter_sql: ""
30
30
 
31
+ # Optional: default view when looking at the scorecard's results. Set to null to clear.
32
+ # default_display_entity_type_identifier: service
33
+ # default_display_grouping: owner_team # entity, owner_team, owner_leaf, or relation:<relation_id>
34
+ # (relation groupings require default_display_entity_type_identifier)
35
+
31
36
  # User IDs of editors
32
37
  editors: []
33
38
 
@@ -439,6 +439,8 @@ const SCORECARD_SECTION_KEYS = {
439
439
  "entity_filter_type",
440
440
  "entity_filter_sql",
441
441
  "entity_filter_type_ids",
442
+ "default_display_entity_type_identifier",
443
+ "default_display_grouping",
442
444
  "tags",
443
445
  "sql_errors",
444
446
  "levels",
@@ -0,0 +1,207 @@
1
+ import { Command, Option } from "commander";
2
+ import { createExampleText, getContext, parseCommaSeparatedValues, wrapAction, } from "../../commandHelpers.js";
3
+ import { CliError, EXIT_CODES } from "../../errors.js";
4
+ import { requestWithMessages } from "../../http.js";
5
+ import { renderJson, renderRichText } from "../../renderers.js";
6
+ import { buildRuntime } from "../../runtime.js";
7
+ import * as ui from "../../ui.js";
8
+ const PROPERTIES = [
9
+ {
10
+ key: "start_date",
11
+ label: "Start date",
12
+ valueName: "date",
13
+ description: "Start date as YYYY-MM-DD, or null to clear it",
14
+ parse: parseDate,
15
+ },
16
+ {
17
+ key: "github_username",
18
+ label: "GitHub username",
19
+ valueName: "username",
20
+ description: "GitHub username, or null to clear it",
21
+ parse: parseText,
22
+ },
23
+ {
24
+ key: "additional_github_username",
25
+ label: "Additional GitHub username",
26
+ valueName: "username",
27
+ description: "Additional GitHub username, or null to clear it",
28
+ parse: parseText,
29
+ },
30
+ {
31
+ key: "gitlab_username",
32
+ label: "GitLab username",
33
+ valueName: "username",
34
+ description: "GitLab username, or null to clear it",
35
+ parse: parseText,
36
+ },
37
+ {
38
+ key: "ai_light_adoption_date",
39
+ label: "AI light adoption date",
40
+ valueName: "date",
41
+ description: "Date the user became a light AI adopter as YYYY-MM-DD, or null to clear it",
42
+ parse: parseDate,
43
+ },
44
+ {
45
+ key: "ai_moderate_adoption_date",
46
+ label: "AI moderate adoption date",
47
+ valueName: "date",
48
+ description: "Date the user became a moderate AI adopter as YYYY-MM-DD, or null to clear it",
49
+ parse: parseDate,
50
+ },
51
+ {
52
+ key: "ai_heavy_adoption_date",
53
+ label: "AI heavy adoption date",
54
+ valueName: "date",
55
+ description: "Date the user became a heavy AI adopter as YYYY-MM-DD, or null to clear it",
56
+ parse: parseDate,
57
+ },
58
+ {
59
+ key: "sso_email",
60
+ label: "SSO email",
61
+ valueName: "email",
62
+ description: "Alternate email used to authenticate through SAML SSO, or null to clear it",
63
+ parse: parseText,
64
+ },
65
+ {
66
+ key: "additional_emails",
67
+ label: "Additional emails",
68
+ valueName: "emails",
69
+ description: "Comma-separated additional email addresses. Replaces the existing list; null clears it",
70
+ parse: parseEmails,
71
+ },
72
+ {
73
+ key: "protected",
74
+ label: "Protected",
75
+ valueName: "true|false",
76
+ description: "Whether the user's IC metrics are de-identified",
77
+ parse: (value, flag) => parseBoolean(value, flag, false),
78
+ },
79
+ {
80
+ key: "exclude_from_snapshots",
81
+ label: "Exclude from snapshots",
82
+ valueName: "true|false",
83
+ description: "Whether the user is left out of snapshots",
84
+ parse: (value, flag) => parseBoolean(value, flag, false),
85
+ },
86
+ {
87
+ key: "developer",
88
+ label: "Developer",
89
+ valueName: "true|false|null",
90
+ description: "Whether the user is a developer: true, false, or null to clear it. Clearing only works for accounts that manage developer classification manually",
91
+ parse: (value, flag) => parseBoolean(value, flag, true),
92
+ },
93
+ ];
94
+ const ERROR_MESSAGES = {
95
+ "User not found": "No active DX user has that email address. Check the spelling.",
96
+ "Developer must be true or false": "Only accounts that manage developer classification manually can clear the developer flag. Pass true or false instead.",
97
+ };
98
+ export function updateCommand() {
99
+ const update = new Command()
100
+ .name("update")
101
+ .description("Update the properties of a DX user")
102
+ .argument("<email>", "Email address of the user to update");
103
+ for (const property of PROPERTIES) {
104
+ update.addOption(new Option(`${flagFor(property)} <${property.valueName}>`, property.description));
105
+ }
106
+ return update
107
+ .addHelpText("afterAll", createExampleText([
108
+ {
109
+ label: "Set a user's start date and GitHub username",
110
+ command: "dx users update ada@example.com --start-date 2024-10-07 --github-username ada-lovelace",
111
+ },
112
+ {
113
+ label: "Clear a user's GitLab username",
114
+ command: "dx users update ada@example.com --gitlab-username null",
115
+ },
116
+ {
117
+ label: "Mark a user as not a developer and return JSON",
118
+ command: "dx users update ada@example.com --developer false --json",
119
+ },
120
+ ]))
121
+ .action(wrapAction(async (email, options, command) => {
122
+ const params = { email };
123
+ for (const property of PROPERTIES) {
124
+ const value = options[attributeNameFor(property)];
125
+ if (value !== undefined) {
126
+ params[property.key] = property.parse(value, flagFor(property));
127
+ }
128
+ }
129
+ if (Object.keys(params).length === 1) {
130
+ throw new CliError("Nothing to update. Pass at least one property option, such as --start-date or --developer. See --help for the full list.", EXIT_CODES.ARGUMENT_ERROR);
131
+ }
132
+ const runtime = await buildRuntime(getContext(command));
133
+ const response = await updateUser(runtime, params);
134
+ if (runtime.context.json) {
135
+ renderJson(response);
136
+ }
137
+ else {
138
+ renderRichText([
139
+ ui.p(`${ui.success(ui.GLYPHS.CHECK)} Updated user ${ui.code(response.user.email)}.`),
140
+ ui.dl(
141
+ // The API only returns protected when the account has protected_user_ic_metrics_disabled on.
142
+ PROPERTIES.filter(({ key }) => key !== "protected" ||
143
+ response.user.protected !== undefined).map(({ key, label }) => ui.dli(label, formatValue(response.user[key]))), {
144
+ termWidth: Math.max(...PROPERTIES.map(({ label }) => label.length)) +
145
+ 2,
146
+ }),
147
+ ]);
148
+ }
149
+ }));
150
+ }
151
+ function flagFor(property) {
152
+ return `--${property.key.replace(/_/g, "-")}`;
153
+ }
154
+ function attributeNameFor(property) {
155
+ return new Option(flagFor(property)).attributeName();
156
+ }
157
+ function formatValue(value) {
158
+ if (Array.isArray(value)) {
159
+ return value.length > 0 ? value.join(", ") : "unset";
160
+ }
161
+ return String(value ?? "unset");
162
+ }
163
+ function parseText(value) {
164
+ return value === "null" ? null : value;
165
+ }
166
+ function parseDate(value, flag) {
167
+ if (value === "null") {
168
+ return null;
169
+ }
170
+ // JS rolls 2024-02-30 over to March 1, so compare the round trip.
171
+ const date = new Date(`${value}T00:00:00Z`);
172
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value) ||
173
+ Number.isNaN(date.getTime()) ||
174
+ !date.toISOString().startsWith(value)) {
175
+ throw new CliError(`${flag} must be a date formatted as YYYY-MM-DD, or null`, EXIT_CODES.ARGUMENT_ERROR);
176
+ }
177
+ return value;
178
+ }
179
+ function parseEmails(value, flag) {
180
+ if (value === "null") {
181
+ return null;
182
+ }
183
+ const emails = parseCommaSeparatedValues(value);
184
+ if (emails === undefined) {
185
+ throw new CliError(`${flag} needs at least one email address, or null to clear the list`, EXIT_CODES.ARGUMENT_ERROR);
186
+ }
187
+ return emails;
188
+ }
189
+ function parseBoolean(value, flag, allowNull) {
190
+ const normalized = value.trim().toLowerCase();
191
+ if (normalized === "true") {
192
+ return true;
193
+ }
194
+ if (normalized === "false") {
195
+ return false;
196
+ }
197
+ if (allowNull && normalized === "null") {
198
+ return null;
199
+ }
200
+ throw new CliError(`${flag} must be ${allowNull ? "true, false, or null" : "true or false"}`, EXIT_CODES.ARGUMENT_ERROR);
201
+ }
202
+ async function updateUser(runtime, params) {
203
+ return requestWithMessages(runtime, "/users.update", {
204
+ method: "POST",
205
+ body: params,
206
+ }, ERROR_MESSAGES);
207
+ }
@@ -1,9 +1,11 @@
1
1
  import { Command } from "commander";
2
2
  import { linkableAccountsCommand } from "./users/linkableAccounts.js";
3
3
  import { linksCommand } from "./users/links.js";
4
+ import { updateCommand } from "./users/update.js";
4
5
  export function usersCommand() {
5
6
  const users = new Command().name("users").description("Manage DX users");
6
7
  users.addCommand(linkableAccountsCommand());
7
8
  users.addCommand(linksCommand());
9
+ users.addCommand(updateCommand());
8
10
  return users;
9
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@get-dx/cli",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "DX CLI",
5
5
  "author": "DX <developers@getdx.com>",
6
6
  "bugs": {