not-manage 0.2.0 → 0.2.2
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 +77 -27
- package/package.json +7 -4
- package/src/cli-options.js +278 -10
- package/src/cli.js +8 -51
- package/src/clio-api.js +33 -4
- package/src/commands-activities.js +11 -119
- package/src/commands-practice-areas.js +13 -66
- package/src/redaction-policy.js +98 -3
- package/src/redaction.js +5 -1
- package/src/resource-display.js +100 -0
- package/src/resource-handlers.js +81 -0
- package/src/resource-metadata.js +1639 -2
- package/src/resource-query-builder.js +80 -0
- package/src/resource-utils.js +48 -0
- package/src/commands-billable-clients.js +0 -105
- package/src/commands-billable-matters.js +0 -106
- package/src/commands-bills.js +0 -192
- package/src/commands-contacts.js +0 -121
- package/src/commands-matters.js +0 -156
- package/src/commands-tasks.js +0 -155
- package/src/commands-users.js +0 -134
- package/src/postinstall.js +0 -140
package/src/clio-api.js
CHANGED
|
@@ -5,6 +5,10 @@ function createError(message, responseText) {
|
|
|
5
5
|
return new Error(`${message}.${suffix}`.trim());
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
function isPlainObject(value) {
|
|
9
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
async function postForm(url, formFields, headers = {}) {
|
|
9
13
|
const response = await fetch(url, {
|
|
10
14
|
method: "POST",
|
|
@@ -136,21 +140,45 @@ function parseTrustedApiUrl(config, url, expectedPathPrefix = "/api/v4/") {
|
|
|
136
140
|
function buildUrlWithQuery(baseUrl, query = {}) {
|
|
137
141
|
const url = new URL(baseUrl);
|
|
138
142
|
|
|
139
|
-
|
|
143
|
+
function appendQueryValue(key, value) {
|
|
140
144
|
if (value === undefined || value === null || value === "") {
|
|
141
145
|
return;
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
if (Array.isArray(value)) {
|
|
145
149
|
value.forEach((item) => {
|
|
146
|
-
|
|
147
|
-
|
|
150
|
+
appendQueryValue(key, item);
|
|
151
|
+
});
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (isPlainObject(value)) {
|
|
156
|
+
Object.entries(value).forEach(([nestedKey, nestedValue]) => {
|
|
157
|
+
if (nestedValue === undefined || nestedValue === null || nestedValue === "") {
|
|
158
|
+
return;
|
|
148
159
|
}
|
|
160
|
+
|
|
161
|
+
const compositeKey = `${key}[${nestedKey}]`;
|
|
162
|
+
if (Array.isArray(nestedValue)) {
|
|
163
|
+
const serialized = nestedValue
|
|
164
|
+
.filter((item) => item !== undefined && item !== null && item !== "")
|
|
165
|
+
.map((item) => String(item));
|
|
166
|
+
if (serialized.length > 0) {
|
|
167
|
+
url.searchParams.append(compositeKey, `[${serialized.join(", ")}]`);
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
appendQueryValue(compositeKey, nestedValue);
|
|
149
173
|
});
|
|
150
174
|
return;
|
|
151
175
|
}
|
|
152
176
|
|
|
153
|
-
url.searchParams.
|
|
177
|
+
url.searchParams.append(key, String(value));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
181
|
+
appendQueryValue(key, value);
|
|
154
182
|
});
|
|
155
183
|
|
|
156
184
|
return url.toString();
|
|
@@ -382,6 +410,7 @@ module.exports = {
|
|
|
382
410
|
fetchWhoAmI,
|
|
383
411
|
getValidAccessToken,
|
|
384
412
|
__private: {
|
|
413
|
+
buildUrlWithQuery,
|
|
385
414
|
parseTrustedApiUrl,
|
|
386
415
|
},
|
|
387
416
|
};
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
const { fetchResourceById, fetchResourcePage } = require("./clio-api");
|
|
2
2
|
const {
|
|
3
|
-
|
|
3
|
+
createDetailPrinter,
|
|
4
|
+
createListPrinter,
|
|
5
|
+
} = require("./resource-display");
|
|
6
|
+
const { buildListQueryFromResource } = require("./resource-query-builder");
|
|
7
|
+
const {
|
|
4
8
|
compactQuery,
|
|
5
9
|
fetchPages,
|
|
6
|
-
|
|
7
|
-
formatMoney,
|
|
8
|
-
parseLimit,
|
|
9
|
-
printKeyValueRows,
|
|
10
|
-
readMatterLabel,
|
|
11
|
-
readUserName,
|
|
10
|
+
readHours,
|
|
12
11
|
} = require("./resource-utils");
|
|
13
12
|
const {
|
|
14
13
|
buildSummaryMessage,
|
|
@@ -21,122 +20,12 @@ const { getResourceMetadata } = require("./resource-metadata");
|
|
|
21
20
|
const ACTIVITY_RESOURCE = getResourceMetadata("activities");
|
|
22
21
|
const MATTER_RESOURCE = getResourceMetadata("matters");
|
|
23
22
|
|
|
24
|
-
function readHours(activity) {
|
|
25
|
-
const quantityInHours = Number(activity?.quantity_in_hours);
|
|
26
|
-
if (Number.isFinite(quantityInHours)) {
|
|
27
|
-
return quantityInHours.toFixed(2);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const quantity = Number(activity?.quantity);
|
|
31
|
-
if (Number.isFinite(quantity)) {
|
|
32
|
-
return (quantity / 3600).toFixed(2);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return "-";
|
|
36
|
-
}
|
|
37
|
-
|
|
38
23
|
function buildActivityQuery(options) {
|
|
39
|
-
return
|
|
40
|
-
activity_description_id: options.activityDescriptionId || undefined,
|
|
41
|
-
created_since: options.createdSince || undefined,
|
|
42
|
-
end_date: options.endDate || undefined,
|
|
43
|
-
fields: options.fields || ACTIVITY_RESOURCE.defaultFields.list,
|
|
44
|
-
flat_rate:
|
|
45
|
-
options.flatRate === undefined || options.flatRate === null
|
|
46
|
-
? undefined
|
|
47
|
-
: Boolean(options.flatRate),
|
|
48
|
-
limit: parseLimit(options.limit),
|
|
49
|
-
matter_id: options.matterId || undefined,
|
|
50
|
-
only_unaccounted_for: options.onlyUnaccountedFor ? true : undefined,
|
|
51
|
-
order: options.order || undefined,
|
|
52
|
-
page_token: options.pageToken || undefined,
|
|
53
|
-
query: options.query || undefined,
|
|
54
|
-
start_date: options.startDate || undefined,
|
|
55
|
-
status: options.status || undefined,
|
|
56
|
-
task_id: options.taskId || undefined,
|
|
57
|
-
type: options.type || undefined,
|
|
58
|
-
updated_since: options.updatedSince || undefined,
|
|
59
|
-
user_id: options.userId || undefined,
|
|
60
|
-
});
|
|
24
|
+
return buildListQueryFromResource(ACTIVITY_RESOURCE, options, ACTIVITY_RESOURCE.listQuery);
|
|
61
25
|
}
|
|
62
26
|
|
|
63
27
|
function formatActivityRow(activity) {
|
|
64
|
-
return
|
|
65
|
-
billed: formatBoolean(activity.billed),
|
|
66
|
-
date: String(activity.date || "-"),
|
|
67
|
-
hours: readHours(activity),
|
|
68
|
-
id: String(activity.id || "-"),
|
|
69
|
-
matter: readMatterLabel(activity.matter),
|
|
70
|
-
note: String(activity.note || "-"),
|
|
71
|
-
total: formatMoney(activity.total),
|
|
72
|
-
type: String(activity.type || "-"),
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function printActivityList(rows, options) {
|
|
77
|
-
if (rows.length === 0) {
|
|
78
|
-
console.log("No activities found for the selected filters.");
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const visibleRows = rows.slice(0, 50);
|
|
83
|
-
console.log("ID TYPE DATE HOURS TOTAL BILLED MATTER NOTE");
|
|
84
|
-
console.log("-------- ----------- ---------- ----- ---------- ------ -------------------- ------------------------------");
|
|
85
|
-
|
|
86
|
-
visibleRows.forEach((row) => {
|
|
87
|
-
const line = [
|
|
88
|
-
clip(row.id, 8).padEnd(8, " "),
|
|
89
|
-
clip(row.type, 11).padEnd(11, " "),
|
|
90
|
-
clip(row.date, 10).padEnd(10, " "),
|
|
91
|
-
clip(row.hours, 5).padEnd(5, " "),
|
|
92
|
-
clip(row.total, 10).padEnd(10, " "),
|
|
93
|
-
clip(row.billed, 6).padEnd(6, " "),
|
|
94
|
-
clip(row.matter, 20).padEnd(20, " "),
|
|
95
|
-
clip(row.note, 30),
|
|
96
|
-
].join(" ");
|
|
97
|
-
|
|
98
|
-
console.log(line);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
if (rows.length > visibleRows.length) {
|
|
102
|
-
console.log(`Showing ${visibleRows.length} of ${rows.length} activities. Use --json for full output.`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (!options.all && options.nextPageUrl) {
|
|
106
|
-
console.log("");
|
|
107
|
-
console.log("More results are available.");
|
|
108
|
-
if (options.pageTokenSupported === false) {
|
|
109
|
-
console.log("Run again with `--all` to fetch every matching activity.");
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
console.log("Run again with `--all` or pass `--page-token` from `--json` output.");
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function printActivity(activity) {
|
|
117
|
-
printKeyValueRows([
|
|
118
|
-
["ID", activity.id],
|
|
119
|
-
["Type", activity.type],
|
|
120
|
-
["Date", activity.date],
|
|
121
|
-
["Hours", readHours(activity)],
|
|
122
|
-
["Price", formatMoney(activity.price)],
|
|
123
|
-
["Total", formatMoney(activity.total)],
|
|
124
|
-
["Billed", formatBoolean(activity.billed)],
|
|
125
|
-
["On Bill", formatBoolean(activity.on_bill)],
|
|
126
|
-
["Non-Billable", formatBoolean(activity.non_billable)],
|
|
127
|
-
["No Charge", formatBoolean(activity.no_charge)],
|
|
128
|
-
["Flat Rate", formatBoolean(activity.flat_rate)],
|
|
129
|
-
["Contingency Fee", formatBoolean(activity.contingency_fee)],
|
|
130
|
-
["User", readUserName(activity.user)],
|
|
131
|
-
["Matter", readMatterLabel(activity.matter)],
|
|
132
|
-
["Activity Description", activity.activity_description?.name],
|
|
133
|
-
["Bill", activity.bill?.number],
|
|
134
|
-
["Bill State", activity.bill?.state],
|
|
135
|
-
["Reference", activity.reference],
|
|
136
|
-
["Note", activity.note],
|
|
137
|
-
["Created", activity.created_at],
|
|
138
|
-
["Updated", activity.updated_at],
|
|
139
|
-
]);
|
|
28
|
+
return ACTIVITY_RESOURCE.display.list.formatRow(activity);
|
|
140
29
|
}
|
|
141
30
|
|
|
142
31
|
async function fetchMatterIdsForClient(config, accessToken, clientId) {
|
|
@@ -302,6 +191,9 @@ function printActivitySummary({ result, rows }) {
|
|
|
302
191
|
);
|
|
303
192
|
}
|
|
304
193
|
|
|
194
|
+
const printActivityList = createListPrinter(ACTIVITY_RESOURCE.display.list);
|
|
195
|
+
const printActivity = createDetailPrinter(ACTIVITY_RESOURCE.display.get);
|
|
196
|
+
|
|
305
197
|
const activitiesList = createListCommand({
|
|
306
198
|
apiPath: ACTIVITY_RESOURCE.apiPath,
|
|
307
199
|
buildJsonMeta: buildActivityJsonMeta,
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
const { fetchResourceById } = require("./clio-api");
|
|
2
2
|
const {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} = require("./resource-utils");
|
|
3
|
+
createDetailPrinter,
|
|
4
|
+
createListPrinter,
|
|
5
|
+
} = require("./resource-display");
|
|
6
|
+
const { buildListQueryFromResource } = require("./resource-query-builder");
|
|
8
7
|
const {
|
|
9
8
|
createGetCommand,
|
|
10
9
|
createListCommand,
|
|
@@ -17,16 +16,11 @@ const PRACTICE_AREA_RESOURCE = getResourceMetadata("practice-areas");
|
|
|
17
16
|
const MATTER_LOOKUP_FIELDS = "id,practice_area{id}";
|
|
18
17
|
|
|
19
18
|
function buildPracticeAreaQuery(options) {
|
|
20
|
-
return
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
name: options.name || undefined,
|
|
26
|
-
order: options.order || undefined,
|
|
27
|
-
page_token: options.pageToken || undefined,
|
|
28
|
-
updated_since: options.updatedSince || undefined,
|
|
29
|
-
});
|
|
19
|
+
return buildListQueryFromResource(
|
|
20
|
+
PRACTICE_AREA_RESOURCE,
|
|
21
|
+
options,
|
|
22
|
+
PRACTICE_AREA_RESOURCE.listQuery
|
|
23
|
+
);
|
|
30
24
|
}
|
|
31
25
|
|
|
32
26
|
function matchesTimestampOnOrAfter(value, threshold) {
|
|
@@ -120,57 +114,7 @@ async function practiceAreasListForMatter(config, accessToken, options = {}) {
|
|
|
120
114
|
}
|
|
121
115
|
|
|
122
116
|
function formatPracticeAreaRow(practiceArea) {
|
|
123
|
-
return
|
|
124
|
-
category: String(practiceArea.category || "-"),
|
|
125
|
-
code: String(practiceArea.code || "-"),
|
|
126
|
-
id: String(practiceArea.id || "-"),
|
|
127
|
-
name: String(practiceArea.name || "-"),
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function printPracticeAreaList(rows, options) {
|
|
132
|
-
if (rows.length === 0) {
|
|
133
|
-
console.log("No practice areas found for the selected filters.");
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const visibleRows = rows.slice(0, 50);
|
|
138
|
-
console.log("ID CODE NAME CATEGORY");
|
|
139
|
-
console.log("-------- ------------ ---------------------------- ------------------------------");
|
|
140
|
-
|
|
141
|
-
visibleRows.forEach((row) => {
|
|
142
|
-
const line = [
|
|
143
|
-
clip(row.id, 8).padEnd(8, " "),
|
|
144
|
-
clip(row.code, 12).padEnd(12, " "),
|
|
145
|
-
clip(row.name, 28).padEnd(28, " "),
|
|
146
|
-
clip(row.category, 30),
|
|
147
|
-
].join(" ");
|
|
148
|
-
|
|
149
|
-
console.log(line);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
if (rows.length > visibleRows.length) {
|
|
153
|
-
console.log(
|
|
154
|
-
`Showing ${visibleRows.length} of ${rows.length} practice areas. Use --json for full output.`
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (!options.all && options.nextPageUrl) {
|
|
159
|
-
console.log("");
|
|
160
|
-
console.log("More results are available.");
|
|
161
|
-
console.log("Run again with `--all` or pass `--page-token` from `--json` output.");
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function printPracticeArea(practiceArea) {
|
|
166
|
-
printKeyValueRows([
|
|
167
|
-
["ID", practiceArea.id],
|
|
168
|
-
["Code", practiceArea.code],
|
|
169
|
-
["Name", practiceArea.name],
|
|
170
|
-
["Category", practiceArea.category],
|
|
171
|
-
["Created", practiceArea.created_at],
|
|
172
|
-
["Updated", practiceArea.updated_at],
|
|
173
|
-
]);
|
|
117
|
+
return PRACTICE_AREA_RESOURCE.display.list.formatRow(practiceArea);
|
|
174
118
|
}
|
|
175
119
|
|
|
176
120
|
async function fetchPracticeAreaListResult({ accessToken, apiPath, config, options, query }) {
|
|
@@ -187,6 +131,9 @@ async function fetchPracticeAreaListResult({ accessToken, apiPath, config, optio
|
|
|
187
131
|
});
|
|
188
132
|
}
|
|
189
133
|
|
|
134
|
+
const printPracticeAreaList = createListPrinter(PRACTICE_AREA_RESOURCE.display.list);
|
|
135
|
+
const printPracticeArea = createDetailPrinter(PRACTICE_AREA_RESOURCE.display.get);
|
|
136
|
+
|
|
190
137
|
const practiceAreasList = createListCommand({
|
|
191
138
|
apiPath: PRACTICE_AREA_RESOURCE.apiPath,
|
|
192
139
|
buildQuery: buildPracticeAreaQuery,
|
package/src/redaction-policy.js
CHANGED
|
@@ -1,36 +1,131 @@
|
|
|
1
|
+
const SET_KEYS = [
|
|
2
|
+
"clientObjectKeys",
|
|
3
|
+
"freeTextFields",
|
|
4
|
+
"labelFields",
|
|
5
|
+
"matterLabelFields",
|
|
6
|
+
"safeIdentityObjectKeys",
|
|
7
|
+
];
|
|
8
|
+
|
|
1
9
|
const DEFAULT_POLICY = {
|
|
2
10
|
clientObjectKeys: new Set(["client", "clients", "contact", "contacts"]),
|
|
3
11
|
contactLikeResource: false,
|
|
4
|
-
freeTextFields: new Set([
|
|
5
|
-
|
|
12
|
+
freeTextFields: new Set([
|
|
13
|
+
"body",
|
|
14
|
+
"caption",
|
|
15
|
+
"comment",
|
|
16
|
+
"content",
|
|
17
|
+
"description",
|
|
18
|
+
"detail",
|
|
19
|
+
"display_value",
|
|
20
|
+
"field_value",
|
|
21
|
+
"instructions",
|
|
22
|
+
"location",
|
|
23
|
+
"memo",
|
|
24
|
+
"message",
|
|
25
|
+
"note",
|
|
26
|
+
"primary_detail",
|
|
27
|
+
"reference",
|
|
28
|
+
"secondary_detail",
|
|
29
|
+
"snippet",
|
|
30
|
+
"subject",
|
|
31
|
+
"summary",
|
|
32
|
+
"text",
|
|
33
|
+
"value",
|
|
34
|
+
"value_text",
|
|
35
|
+
]),
|
|
36
|
+
labelFields: new Set([
|
|
37
|
+
"display_value",
|
|
38
|
+
"display_number",
|
|
39
|
+
"file_name",
|
|
40
|
+
"filename",
|
|
41
|
+
"identifier",
|
|
42
|
+
"name",
|
|
43
|
+
"number",
|
|
44
|
+
"option",
|
|
45
|
+
"secondary_identifier",
|
|
46
|
+
"summary",
|
|
47
|
+
"tertiary_identifier",
|
|
48
|
+
"title",
|
|
49
|
+
]),
|
|
6
50
|
matterLabelFields: new Set(["display_number", "number"]),
|
|
7
51
|
safeIdentityObjectKeys: new Set([
|
|
52
|
+
"author",
|
|
8
53
|
"user",
|
|
9
54
|
"assignee",
|
|
10
55
|
"assigner",
|
|
56
|
+
"calendar_owner",
|
|
57
|
+
"created_by",
|
|
58
|
+
"creator",
|
|
11
59
|
"responsible_attorney",
|
|
12
60
|
"responsible_staff",
|
|
13
61
|
"originating_attorney",
|
|
62
|
+
"updated_by",
|
|
14
63
|
]),
|
|
15
64
|
safeIdentityResource: false,
|
|
16
65
|
};
|
|
17
66
|
|
|
18
67
|
const RESOURCE_POLICY_OVERRIDES = {
|
|
68
|
+
"calendar-entry": {
|
|
69
|
+
clientObjectKeys: new Set(["attendees"]),
|
|
70
|
+
freeTextFields: new Set(["summary", "location"]),
|
|
71
|
+
labelFields: new Set(["summary"]),
|
|
72
|
+
},
|
|
19
73
|
"billable-client": {
|
|
20
74
|
contactLikeResource: true,
|
|
21
75
|
},
|
|
76
|
+
communication: {
|
|
77
|
+
clientObjectKeys: new Set(["senders", "receivers"]),
|
|
78
|
+
freeTextFields: new Set(["body", "content", "detail", "message"]),
|
|
79
|
+
},
|
|
22
80
|
contact: {
|
|
23
81
|
contactLikeResource: true,
|
|
24
82
|
},
|
|
83
|
+
conversation: {
|
|
84
|
+
freeTextFields: new Set(["body", "content", "message", "snippet"]),
|
|
85
|
+
},
|
|
86
|
+
"conversation-message": {
|
|
87
|
+
clientObjectKeys: new Set(["receivers", "sender"]),
|
|
88
|
+
freeTextFields: new Set(["body", "content", "message", "text"]),
|
|
89
|
+
},
|
|
90
|
+
"custom-field": {
|
|
91
|
+
freeTextFields: new Set(["value", "display_value", "value_text", "field_value"]),
|
|
92
|
+
labelFields: new Set(["name", "title"]),
|
|
93
|
+
},
|
|
94
|
+
document: {
|
|
95
|
+
freeTextFields: new Set(["name", "summary"]),
|
|
96
|
+
labelFields: new Set(["file_name", "filename", "name", "title"]),
|
|
97
|
+
},
|
|
98
|
+
note: {
|
|
99
|
+
freeTextFields: new Set(["detail", "message", "text"]),
|
|
100
|
+
},
|
|
101
|
+
"my-event": {
|
|
102
|
+
freeTextFields: new Set([
|
|
103
|
+
"description",
|
|
104
|
+
"message",
|
|
105
|
+
"primary_detail",
|
|
106
|
+
"secondary_detail",
|
|
107
|
+
"title",
|
|
108
|
+
]),
|
|
109
|
+
labelFields: new Set(["primary_detail", "secondary_detail", "title"]),
|
|
110
|
+
},
|
|
25
111
|
user: {
|
|
26
112
|
safeIdentityResource: true,
|
|
27
113
|
},
|
|
28
114
|
};
|
|
29
115
|
|
|
30
116
|
function getRedactionPolicy(resourceType) {
|
|
117
|
+
const overrides = RESOURCE_POLICY_OVERRIDES[resourceType] || {};
|
|
118
|
+
|
|
31
119
|
return {
|
|
32
120
|
...DEFAULT_POLICY,
|
|
33
|
-
...
|
|
121
|
+
...overrides,
|
|
122
|
+
...SET_KEYS.reduce((merged, key) => {
|
|
123
|
+
merged[key] = new Set([
|
|
124
|
+
...(DEFAULT_POLICY[key] || []),
|
|
125
|
+
...(overrides[key] || []),
|
|
126
|
+
]);
|
|
127
|
+
return merged;
|
|
128
|
+
}, {}),
|
|
34
129
|
};
|
|
35
130
|
}
|
|
36
131
|
|
package/src/redaction.js
CHANGED
|
@@ -400,6 +400,10 @@ function isMatterLabelContext(policy, path, key) {
|
|
|
400
400
|
return path[path.length - 1] === "matter" && policy.matterLabelFields.has(key);
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
+
function isLabelContext(policy, key) {
|
|
404
|
+
return policy.labelFields.has(key);
|
|
405
|
+
}
|
|
406
|
+
|
|
403
407
|
function redactStringValue(
|
|
404
408
|
policy,
|
|
405
409
|
text,
|
|
@@ -414,7 +418,7 @@ function redactStringValue(
|
|
|
414
418
|
output = replaceKnownSensitiveValues(output, replacements);
|
|
415
419
|
output = redactPatternPii(output);
|
|
416
420
|
|
|
417
|
-
if (isMatterLabelContext(policy, path, key)) {
|
|
421
|
+
if (isMatterLabelContext(policy, path, key) || isLabelContext(policy, key)) {
|
|
418
422
|
output = replaceMatterLabelDerivedNames(output, derivedLabelReplacements);
|
|
419
423
|
}
|
|
420
424
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const { clip, printKeyValueRows } = require("./resource-utils");
|
|
2
|
+
|
|
3
|
+
function resolveDescriptorValue(item, descriptor) {
|
|
4
|
+
if (typeof descriptor.value === "function") {
|
|
5
|
+
return descriptor.value(item);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return item?.[descriptor.key];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function createListPrinter(config) {
|
|
12
|
+
const {
|
|
13
|
+
columns,
|
|
14
|
+
emptyMessage,
|
|
15
|
+
moreResults,
|
|
16
|
+
noun,
|
|
17
|
+
rowLimit = 50,
|
|
18
|
+
} = config;
|
|
19
|
+
|
|
20
|
+
const headerLine = columns
|
|
21
|
+
.map((column) => {
|
|
22
|
+
if (column.width) {
|
|
23
|
+
return String(column.header).padEnd(column.width, " ");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return String(column.header);
|
|
27
|
+
})
|
|
28
|
+
.join(" ");
|
|
29
|
+
|
|
30
|
+
const dividerLine = columns
|
|
31
|
+
.map((column) => "-".repeat(column.width || String(column.header).length))
|
|
32
|
+
.join(" ");
|
|
33
|
+
|
|
34
|
+
return function printList(rows, options = {}) {
|
|
35
|
+
if (rows.length === 0) {
|
|
36
|
+
console.log(emptyMessage);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const visibleRows = rows.slice(0, rowLimit);
|
|
41
|
+
console.log(headerLine);
|
|
42
|
+
console.log(dividerLine);
|
|
43
|
+
|
|
44
|
+
visibleRows.forEach((row) => {
|
|
45
|
+
const line = columns
|
|
46
|
+
.map((column) => {
|
|
47
|
+
const rawValue = row?.[column.key];
|
|
48
|
+
const text = clip(
|
|
49
|
+
rawValue === undefined || rawValue === null ? "-" : String(rawValue),
|
|
50
|
+
column.width || String(rawValue || "-").length
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (!column.width || column.pad === false) {
|
|
54
|
+
return text;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return text.padEnd(column.width, " ");
|
|
58
|
+
})
|
|
59
|
+
.join(" ");
|
|
60
|
+
|
|
61
|
+
console.log(line);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (rows.length > visibleRows.length) {
|
|
65
|
+
console.log(`Showing ${visibleRows.length} of ${rows.length} ${noun}. Use --json for full output.`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!options.all && options.nextPageUrl) {
|
|
69
|
+
const lines =
|
|
70
|
+
typeof moreResults === "function"
|
|
71
|
+
? moreResults(options)
|
|
72
|
+
: [
|
|
73
|
+
"More results are available.",
|
|
74
|
+
"Run again with `--all` or pass `--page-token` from `--json` output.",
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
if (Array.isArray(lines) && lines.length > 0) {
|
|
78
|
+
console.log("");
|
|
79
|
+
lines.forEach((line) => {
|
|
80
|
+
console.log(line);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function createDetailPrinter(config) {
|
|
88
|
+
const { fields } = config;
|
|
89
|
+
|
|
90
|
+
return function printItem(item = {}) {
|
|
91
|
+
printKeyValueRows(
|
|
92
|
+
fields.map((field) => [field.label, resolveDescriptorValue(item, field)])
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
createDetailPrinter,
|
|
99
|
+
createListPrinter,
|
|
100
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const { activitiesGet, activitiesList } = require("./commands-activities");
|
|
2
|
+
const { practiceAreasGet, practiceAreasList } = require("./commands-practice-areas");
|
|
3
|
+
const {
|
|
4
|
+
createDetailPrinter,
|
|
5
|
+
createListPrinter,
|
|
6
|
+
} = require("./resource-display");
|
|
7
|
+
const { buildListQueryFromResource } = require("./resource-query-builder");
|
|
8
|
+
const { createGetCommand, createListCommand } = require("./resource-command-runner");
|
|
9
|
+
|
|
10
|
+
const RESOURCE_HANDLERS = {
|
|
11
|
+
activities: {
|
|
12
|
+
get: activitiesGet,
|
|
13
|
+
list: activitiesList,
|
|
14
|
+
},
|
|
15
|
+
"practice-areas": {
|
|
16
|
+
get: practiceAreasGet,
|
|
17
|
+
list: practiceAreasList,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const GENERIC_HANDLER_CACHE = new Map();
|
|
22
|
+
|
|
23
|
+
function createGenericHandlers(resourceMetadata) {
|
|
24
|
+
if (!resourceMetadata || !resourceMetadata.display) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const cached = GENERIC_HANDLER_CACHE.get(resourceMetadata.handlerKey);
|
|
29
|
+
if (cached) {
|
|
30
|
+
return cached;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const handlers = {};
|
|
34
|
+
|
|
35
|
+
if (resourceMetadata.capabilities.list.enabled && resourceMetadata.display.list) {
|
|
36
|
+
const printList = createListPrinter(resourceMetadata.display.list);
|
|
37
|
+
handlers.list = createListCommand({
|
|
38
|
+
apiPath: resourceMetadata.apiPath,
|
|
39
|
+
buildQuery: (options) =>
|
|
40
|
+
buildListQueryFromResource(resourceMetadata, options, resourceMetadata.listQuery),
|
|
41
|
+
formatRow: resourceMetadata.display.list.formatRow,
|
|
42
|
+
pluralLabel: resourceMetadata.summaryLabels.plural,
|
|
43
|
+
printList,
|
|
44
|
+
redactionResourceType: resourceMetadata.redaction.resourceType,
|
|
45
|
+
singularLabel: resourceMetadata.summaryLabels.singular,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (resourceMetadata.capabilities.get.enabled && resourceMetadata.display.get) {
|
|
50
|
+
const printItem = createDetailPrinter(resourceMetadata.display.get);
|
|
51
|
+
handlers.get = createGetCommand({
|
|
52
|
+
apiPath: resourceMetadata.apiPath,
|
|
53
|
+
defaultFields: resourceMetadata.defaultFields.get,
|
|
54
|
+
printItem,
|
|
55
|
+
redactionResourceType: resourceMetadata.redaction.resourceType,
|
|
56
|
+
usage: `Usage: not-manage ${resourceMetadata.handlerKey} get <id> [--fields ...] [--json]`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
GENERIC_HANDLER_CACHE.set(resourceMetadata.handlerKey, handlers);
|
|
61
|
+
return handlers;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getResourceHandler(resourceMetadata, subcommand) {
|
|
65
|
+
if (!resourceMetadata) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const explicitHandler = RESOURCE_HANDLERS[resourceMetadata.handlerKey]?.[subcommand];
|
|
70
|
+
if (explicitHandler) {
|
|
71
|
+
return explicitHandler;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const genericHandlers = createGenericHandlers(resourceMetadata);
|
|
75
|
+
return genericHandlers?.[subcommand] || null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
RESOURCE_HANDLERS,
|
|
80
|
+
getResourceHandler,
|
|
81
|
+
};
|