@markwharton/liquidplanner 1.2.0 → 1.4.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/client.d.ts +34 -2
- package/dist/client.js +151 -29
- package/dist/constants.d.ts +0 -4
- package/dist/constants.js +0 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/types.d.ts +32 -7
- package/dist/utils.js +6 -0
- package/dist/workflows.js +4 -5
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @see https://api-docs.liquidplanner.com/
|
|
8
8
|
*/
|
|
9
|
-
import type { LPConfig, LPWorkspace, LPMember, LPItem, LPCostCode, LPSyncResult, LPTimesheetEntry, LPTimesheetEntryWithId, LPUpsertOptions, LPAssignmentWithContext } from './types.js';
|
|
9
|
+
import type { LPConfig, LPWorkspace, LPMember, LPItem, LPCostCode, LPSyncResult, LPTimesheetEntry, LPTimesheetEntryWithId, LPUpsertOptions, LPAssignmentWithContext, LPAncestor } from './types.js';
|
|
10
10
|
/**
|
|
11
11
|
* LiquidPlanner API Client
|
|
12
12
|
*
|
|
@@ -33,6 +33,7 @@ export declare class LPClient {
|
|
|
33
33
|
private readonly apiToken;
|
|
34
34
|
private readonly workspaceId;
|
|
35
35
|
private readonly baseUrl;
|
|
36
|
+
private readonly onRequest?;
|
|
36
37
|
constructor(config: LPConfig);
|
|
37
38
|
/**
|
|
38
39
|
* Make an authenticated request to the LP API
|
|
@@ -66,6 +67,30 @@ export declare class LPClient {
|
|
|
66
67
|
item?: LPItem;
|
|
67
68
|
error?: string;
|
|
68
69
|
}>;
|
|
70
|
+
/**
|
|
71
|
+
* Get multiple items by ID in a single request (batch fetch)
|
|
72
|
+
*
|
|
73
|
+
* Uses the id[in] filter to fetch multiple items efficiently.
|
|
74
|
+
* This reduces N individual requests to a single paginated request.
|
|
75
|
+
*
|
|
76
|
+
* @param itemIds - Array of item IDs to fetch
|
|
77
|
+
*/
|
|
78
|
+
getItems(itemIds: number[]): Promise<{
|
|
79
|
+
items?: LPItem[];
|
|
80
|
+
error?: string;
|
|
81
|
+
}>;
|
|
82
|
+
/**
|
|
83
|
+
* Get the ancestry chain for an item
|
|
84
|
+
*
|
|
85
|
+
* Returns ancestors from root to immediate parent (excludes the item itself).
|
|
86
|
+
* Uses the items/{itemId}/ancestors endpoint.
|
|
87
|
+
*
|
|
88
|
+
* @param itemId - The item ID to get ancestors for
|
|
89
|
+
*/
|
|
90
|
+
getItemAncestors(itemId: number): Promise<{
|
|
91
|
+
ancestors?: LPAncestor[];
|
|
92
|
+
error?: string;
|
|
93
|
+
}>;
|
|
69
94
|
/**
|
|
70
95
|
* Find all assignments under a task (with pagination)
|
|
71
96
|
*/
|
|
@@ -87,14 +112,21 @@ export declare class LPClient {
|
|
|
87
112
|
* Get assignments for a member with parent task names resolved
|
|
88
113
|
*
|
|
89
114
|
* This is a convenience method that fetches assignments and enriches
|
|
90
|
-
* them with parent task names
|
|
115
|
+
* them with parent task names using batch fetching.
|
|
116
|
+
*
|
|
117
|
+
* Request counts:
|
|
118
|
+
* - Default: 2 requests (assignments + tasks)
|
|
119
|
+
* - includeProject: 3 requests (assignments + tasks + projects)
|
|
120
|
+
* - includeHierarchy: 1 + N requests (assignments + N ancestors calls)
|
|
91
121
|
*
|
|
92
122
|
* @param memberId - The member ID to get assignments for
|
|
93
123
|
* @param options - Options for including additional context
|
|
94
124
|
* @param options.includeProject - If true, also fetch grandparent project names
|
|
125
|
+
* @param options.includeHierarchy - If true, fetch full ancestry and build hierarchy path
|
|
95
126
|
*/
|
|
96
127
|
getMyAssignmentsWithContext(memberId: number, options?: {
|
|
97
128
|
includeProject?: boolean;
|
|
129
|
+
includeHierarchy?: boolean;
|
|
98
130
|
}): Promise<{
|
|
99
131
|
assignments?: LPAssignmentWithContext[];
|
|
100
132
|
error?: string;
|
package/dist/client.js
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { buildAuthHeader, hoursToMinutes, normalizeItemType, filterIs, filterIn, paginatedFetch, } from './utils.js';
|
|
10
10
|
import { parseLPErrorResponse, getErrorMessage } from './errors.js';
|
|
11
|
-
import { LP_API_BASE
|
|
11
|
+
import { LP_API_BASE } from './constants.js';
|
|
12
12
|
/** Transform raw API item to LPItem */
|
|
13
13
|
function transformItem(raw) {
|
|
14
14
|
return {
|
|
15
15
|
id: raw.id,
|
|
16
|
-
name: raw.name ||
|
|
16
|
+
name: raw.name || null,
|
|
17
17
|
itemType: normalizeItemType(raw.itemType),
|
|
18
18
|
parentId: raw.parentId,
|
|
19
19
|
costCodeId: raw.costCodeId,
|
|
@@ -47,12 +47,15 @@ export class LPClient {
|
|
|
47
47
|
this.apiToken = config.apiToken;
|
|
48
48
|
this.workspaceId = config.workspaceId;
|
|
49
49
|
this.baseUrl = config.baseUrl ?? LP_API_BASE;
|
|
50
|
+
this.onRequest = config.onRequest;
|
|
50
51
|
}
|
|
51
52
|
/**
|
|
52
53
|
* Make an authenticated request to the LP API
|
|
53
54
|
*/
|
|
54
55
|
async fetch(url, options = {}) {
|
|
55
|
-
const { method = 'GET', body } = options;
|
|
56
|
+
const { method = 'GET', body, description } = options;
|
|
57
|
+
// Notify listener of request (for debugging)
|
|
58
|
+
this.onRequest?.({ method, url, description });
|
|
56
59
|
return fetch(url, {
|
|
57
60
|
method,
|
|
58
61
|
headers: {
|
|
@@ -114,7 +117,7 @@ export class LPClient {
|
|
|
114
117
|
* Get all members in the workspace (with pagination)
|
|
115
118
|
*/
|
|
116
119
|
async getWorkspaceMembers() {
|
|
117
|
-
const baseUrl = `${this.baseUrl}/
|
|
120
|
+
const baseUrl = `${this.baseUrl}/workspaces/${this.workspaceId}/users/v1`;
|
|
118
121
|
const { results, error } = await paginatedFetch({
|
|
119
122
|
fetchFn: (url) => this.fetch(url),
|
|
120
123
|
baseUrl,
|
|
@@ -154,6 +157,58 @@ export class LPClient {
|
|
|
154
157
|
return { error: getErrorMessage(error) };
|
|
155
158
|
}
|
|
156
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Get multiple items by ID in a single request (batch fetch)
|
|
162
|
+
*
|
|
163
|
+
* Uses the id[in] filter to fetch multiple items efficiently.
|
|
164
|
+
* This reduces N individual requests to a single paginated request.
|
|
165
|
+
*
|
|
166
|
+
* @param itemIds - Array of item IDs to fetch
|
|
167
|
+
*/
|
|
168
|
+
async getItems(itemIds) {
|
|
169
|
+
if (itemIds.length === 0)
|
|
170
|
+
return { items: [] };
|
|
171
|
+
const baseUrl = `${this.baseUrl}/workspaces/${this.workspaceId}/items/v1?${filterIn('id', itemIds)}`;
|
|
172
|
+
const { results, error } = await paginatedFetch({
|
|
173
|
+
fetchFn: (url) => this.fetch(url),
|
|
174
|
+
baseUrl,
|
|
175
|
+
transform: (data) => data.map(transformItem),
|
|
176
|
+
});
|
|
177
|
+
return error ? { error } : { items: results };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get the ancestry chain for an item
|
|
181
|
+
*
|
|
182
|
+
* Returns ancestors from root to immediate parent (excludes the item itself).
|
|
183
|
+
* Uses the items/{itemId}/ancestors endpoint.
|
|
184
|
+
*
|
|
185
|
+
* @param itemId - The item ID to get ancestors for
|
|
186
|
+
*/
|
|
187
|
+
async getItemAncestors(itemId) {
|
|
188
|
+
const url = `${this.baseUrl}/workspaces/${this.workspaceId}/items/v1/${itemId}/ancestors`;
|
|
189
|
+
try {
|
|
190
|
+
const response = await this.fetch(url, {
|
|
191
|
+
description: `Get ancestors for item ${itemId}`,
|
|
192
|
+
});
|
|
193
|
+
if (!response.ok) {
|
|
194
|
+
const errorText = await response.text();
|
|
195
|
+
const { message } = parseLPErrorResponse(errorText, response.status);
|
|
196
|
+
return { error: message };
|
|
197
|
+
}
|
|
198
|
+
const json = (await response.json());
|
|
199
|
+
// Handle both { data: [...] } and direct array responses
|
|
200
|
+
const rawData = Array.isArray(json) ? json : (json.data || []);
|
|
201
|
+
const ancestors = rawData.map((a) => ({
|
|
202
|
+
id: a.id,
|
|
203
|
+
name: a.name || null,
|
|
204
|
+
itemType: normalizeItemType(a.itemType),
|
|
205
|
+
}));
|
|
206
|
+
return { ancestors };
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
return { error: getErrorMessage(error) };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
157
212
|
/**
|
|
158
213
|
* Find all assignments under a task (with pagination)
|
|
159
214
|
*/
|
|
@@ -187,11 +242,17 @@ export class LPClient {
|
|
|
187
242
|
* Get assignments for a member with parent task names resolved
|
|
188
243
|
*
|
|
189
244
|
* This is a convenience method that fetches assignments and enriches
|
|
190
|
-
* them with parent task names
|
|
245
|
+
* them with parent task names using batch fetching.
|
|
246
|
+
*
|
|
247
|
+
* Request counts:
|
|
248
|
+
* - Default: 2 requests (assignments + tasks)
|
|
249
|
+
* - includeProject: 3 requests (assignments + tasks + projects)
|
|
250
|
+
* - includeHierarchy: 1 + N requests (assignments + N ancestors calls)
|
|
191
251
|
*
|
|
192
252
|
* @param memberId - The member ID to get assignments for
|
|
193
253
|
* @param options - Options for including additional context
|
|
194
254
|
* @param options.includeProject - If true, also fetch grandparent project names
|
|
255
|
+
* @param options.includeHierarchy - If true, fetch full ancestry and build hierarchy path
|
|
195
256
|
*/
|
|
196
257
|
async getMyAssignmentsWithContext(memberId, options) {
|
|
197
258
|
// 1. Get raw assignments
|
|
@@ -200,35 +261,96 @@ export class LPClient {
|
|
|
200
261
|
return { error };
|
|
201
262
|
if (assignments.length === 0)
|
|
202
263
|
return { assignments: [] };
|
|
203
|
-
// 2.
|
|
204
|
-
|
|
205
|
-
// 3. Batch fetch all parent tasks
|
|
206
|
-
const taskResults = await Promise.all(taskIds.map(id => this.getItem(id)));
|
|
207
|
-
const taskMap = new Map();
|
|
208
|
-
for (const result of taskResults) {
|
|
209
|
-
if (result.item)
|
|
210
|
-
taskMap.set(result.item.id, result.item);
|
|
211
|
-
}
|
|
212
|
-
// 4. Optionally fetch grandparent projects
|
|
264
|
+
// 2. Handle based on options
|
|
265
|
+
let taskMap = new Map();
|
|
213
266
|
let projectMap = new Map();
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
267
|
+
let ancestorMap = new Map();
|
|
268
|
+
if (options?.includeHierarchy) {
|
|
269
|
+
// Optimized path: fetch ancestors for assignments (includes task info)
|
|
270
|
+
// This avoids the separate task batch fetch since task is first ancestor
|
|
271
|
+
// Deduplicate by parentId - only one ancestors call per unique parent task
|
|
272
|
+
const assignmentsByParent = new Map();
|
|
273
|
+
for (const a of assignments) {
|
|
274
|
+
if (a.parentId && !assignmentsByParent.has(a.parentId)) {
|
|
275
|
+
assignmentsByParent.set(a.parentId, a);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const ancestorResults = await Promise.all([...assignmentsByParent.entries()].map(async ([parentId, assignment]) => {
|
|
279
|
+
const { ancestors } = await this.getItemAncestors(assignment.id);
|
|
280
|
+
return { parentId, ancestors };
|
|
281
|
+
}));
|
|
282
|
+
for (const { parentId, ancestors } of ancestorResults) {
|
|
283
|
+
ancestorMap.set(parentId, ancestors);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
// Original path: batch fetch tasks first
|
|
288
|
+
const taskIds = [...new Set(assignments.map(a => a.parentId).filter((id) => id !== undefined))];
|
|
289
|
+
const { items: tasks, error: taskError } = await this.getItems(taskIds);
|
|
290
|
+
if (taskError)
|
|
291
|
+
return { error: taskError };
|
|
292
|
+
for (const task of tasks || []) {
|
|
293
|
+
taskMap.set(task.id, task);
|
|
294
|
+
}
|
|
295
|
+
if (options?.includeProject) {
|
|
296
|
+
// Also fetch grandparent projects
|
|
297
|
+
const projectIds = [...new Set([...taskMap.values()].map(t => t.parentId).filter((id) => id !== undefined))];
|
|
298
|
+
const { items: projects, error: projectError } = await this.getItems(projectIds);
|
|
299
|
+
if (projectError)
|
|
300
|
+
return { error: projectError };
|
|
301
|
+
for (const project of projects || []) {
|
|
302
|
+
projectMap.set(project.id, project);
|
|
303
|
+
}
|
|
220
304
|
}
|
|
221
305
|
}
|
|
222
|
-
//
|
|
306
|
+
// 3. Merge context into assignments
|
|
223
307
|
return {
|
|
224
308
|
assignments: assignments.map(a => {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
309
|
+
const result = { ...a };
|
|
310
|
+
if (options?.includeHierarchy && a.parentId) {
|
|
311
|
+
// Full hierarchy mode - extract task name from ancestors
|
|
312
|
+
const ancestors = ancestorMap.get(a.parentId);
|
|
313
|
+
result.ancestors = ancestors;
|
|
314
|
+
if (ancestors && ancestors.length > 0) {
|
|
315
|
+
// Extract task name from first Task ancestor
|
|
316
|
+
const taskAncestor = ancestors.find(anc => anc.itemType === 'Task');
|
|
317
|
+
result.taskName = taskAncestor?.name ?? null;
|
|
318
|
+
// Build hierarchyPath from Project and Folder ancestors
|
|
319
|
+
// Exclude system containers (Package, WorkspaceRoot) and Tasks
|
|
320
|
+
const hierarchyAncestors = ancestors
|
|
321
|
+
.filter(anc => anc.itemType === 'Project' || anc.itemType === 'Folder')
|
|
322
|
+
.reverse(); // LP returns child→root, we want root→child
|
|
323
|
+
if (hierarchyAncestors.length > 0) {
|
|
324
|
+
result.hierarchyPath = hierarchyAncestors
|
|
325
|
+
.map(anc => anc.name ?? `[${anc.id}]`)
|
|
326
|
+
.join(' › ');
|
|
327
|
+
// Set projectId/projectName from root (first in reversed array)
|
|
328
|
+
result.projectId = hierarchyAncestors[0].id;
|
|
329
|
+
result.projectName = hierarchyAncestors[0].name;
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
result.projectName = null;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
result.taskName = null;
|
|
337
|
+
result.projectName = null;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
// Original path - use taskMap
|
|
342
|
+
const task = a.parentId ? taskMap.get(a.parentId) : undefined;
|
|
343
|
+
result.taskName = task?.name ?? null;
|
|
344
|
+
if (options?.includeProject) {
|
|
345
|
+
const project = task?.parentId ? projectMap.get(task.parentId) : undefined;
|
|
346
|
+
result.projectId = project?.id;
|
|
347
|
+
result.projectName = project?.name ?? null;
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
result.projectName = null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return result;
|
|
232
354
|
}),
|
|
233
355
|
};
|
|
234
356
|
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -5,7 +5,3 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/** Default LP API base URL */
|
|
7
7
|
export declare const LP_API_BASE = "https://next.liquidplanner.com/api";
|
|
8
|
-
/** Default name for items when not available */
|
|
9
|
-
export declare const DEFAULT_ITEM_NAME = "-";
|
|
10
|
-
/** Default name for assignments when not available */
|
|
11
|
-
export declare const DEFAULT_ASSIGNMENT_NAME = "-";
|
package/dist/constants.js
CHANGED
|
@@ -5,7 +5,3 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/** Default LP API base URL */
|
|
7
7
|
export const LP_API_BASE = 'https://next.liquidplanner.com/api';
|
|
8
|
-
/** Default name for items when not available */
|
|
9
|
-
export const DEFAULT_ITEM_NAME = '-';
|
|
10
|
-
/** Default name for assignments when not available */
|
|
11
|
-
export const DEFAULT_ASSIGNMENT_NAME = '-';
|
package/dist/index.d.ts
CHANGED
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
*/
|
|
29
29
|
export { LPClient } from './client.js';
|
|
30
30
|
export { resolveTaskToAssignment } from './workflows.js';
|
|
31
|
-
export type { LPConfig, LPItemType, LPItem, LPWorkspace, LPMember, LPCostCode, LPSyncResult, LPTimesheetEntry, LPTimesheetEntryWithId, LPTaskResolution, LPResult, LPUpsertOptions, LPAssignmentWithContext, } from './types.js';
|
|
31
|
+
export type { LPConfig, LPItemType, LPItem, LPAncestor, LPWorkspace, LPMember, LPCostCode, LPSyncResult, LPTimesheetEntry, LPTimesheetEntryWithId, LPTaskResolution, LPResult, LPUpsertOptions, LPAssignmentWithContext, } from './types.js';
|
|
32
32
|
export { hoursToMinutes, normalizeItemType, buildAuthHeader, filterIs, filterIn, paginatedFetch, } from './utils.js';
|
|
33
33
|
export type { PaginateOptions } from './utils.js';
|
|
34
|
-
export { LP_API_BASE
|
|
34
|
+
export { LP_API_BASE } from './constants.js';
|
|
35
35
|
export { LPError, parseLPErrorResponse, getErrorMessage } from './errors.js';
|
|
36
36
|
export type { LPParsedError } from './errors.js';
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,6 @@ export { resolveTaskToAssignment } from './workflows.js';
|
|
|
33
33
|
// Utilities
|
|
34
34
|
export { hoursToMinutes, normalizeItemType, buildAuthHeader, filterIs, filterIn, paginatedFetch, } from './utils.js';
|
|
35
35
|
// Constants
|
|
36
|
-
export { LP_API_BASE
|
|
36
|
+
export { LP_API_BASE } from './constants.js';
|
|
37
37
|
// Errors
|
|
38
38
|
export { LPError, parseLPErrorResponse, getErrorMessage } from './errors.js';
|
package/dist/types.d.ts
CHANGED
|
@@ -7,15 +7,28 @@
|
|
|
7
7
|
/**
|
|
8
8
|
* LiquidPlanner item types in the hierarchy
|
|
9
9
|
*/
|
|
10
|
-
export type LPItemType = 'Task' | 'Assignment' | 'Folder' | 'Milestone' | 'Event';
|
|
10
|
+
export type LPItemType = 'Task' | 'Assignment' | 'Folder' | 'Project' | 'Package' | 'WorkspaceRoot' | 'Milestone' | 'Event';
|
|
11
|
+
/**
|
|
12
|
+
* An ancestor item in the hierarchy chain
|
|
13
|
+
*
|
|
14
|
+
* Returned by getItemAncestors() in order from root to immediate parent.
|
|
15
|
+
*/
|
|
16
|
+
export interface LPAncestor {
|
|
17
|
+
/** Unique identifier */
|
|
18
|
+
id: number;
|
|
19
|
+
/** Display name (null if not set) */
|
|
20
|
+
name: string | null;
|
|
21
|
+
/** Type of item in LP hierarchy */
|
|
22
|
+
itemType: LPItemType;
|
|
23
|
+
}
|
|
11
24
|
/**
|
|
12
25
|
* An item from LiquidPlanner (Task, Assignment, Folder, etc.)
|
|
13
26
|
*/
|
|
14
27
|
export interface LPItem {
|
|
15
28
|
/** Unique identifier */
|
|
16
29
|
id: number;
|
|
17
|
-
/** Display name */
|
|
18
|
-
name: string;
|
|
30
|
+
/** Display name (null if not set) */
|
|
31
|
+
name: string | null;
|
|
19
32
|
/** Type of item in LP hierarchy */
|
|
20
33
|
itemType: LPItemType;
|
|
21
34
|
/** Parent item ID (e.g., Assignment's parent is a Task) */
|
|
@@ -89,6 +102,12 @@ export interface LPConfig {
|
|
|
89
102
|
apiToken: string;
|
|
90
103
|
/** Base URL for LP API (defaults to https://next.liquidplanner.com/api) */
|
|
91
104
|
baseUrl?: string;
|
|
105
|
+
/** Optional callback invoked on each API request (for debugging/logging) */
|
|
106
|
+
onRequest?: (info: {
|
|
107
|
+
method: string;
|
|
108
|
+
url: string;
|
|
109
|
+
description?: string;
|
|
110
|
+
}) => void;
|
|
92
111
|
}
|
|
93
112
|
/**
|
|
94
113
|
* Result of a timesheet sync operation
|
|
@@ -154,8 +173,14 @@ export interface LPUpsertOptions {
|
|
|
154
173
|
* grandparent project name, and cost code name.
|
|
155
174
|
*/
|
|
156
175
|
export interface LPAssignmentWithContext extends LPItem {
|
|
157
|
-
/** Parent task name */
|
|
158
|
-
taskName?: string;
|
|
159
|
-
/** Grandparent project
|
|
160
|
-
|
|
176
|
+
/** Parent task name (null if not found) */
|
|
177
|
+
taskName?: string | null;
|
|
178
|
+
/** Grandparent project ID (undefined if not requested/found) */
|
|
179
|
+
projectId?: number;
|
|
180
|
+
/** Grandparent project name (null if not requested/found) */
|
|
181
|
+
projectName?: string | null;
|
|
182
|
+
/** Full ancestry from root to parent task (undefined if not requested) */
|
|
183
|
+
ancestors?: LPAncestor[];
|
|
184
|
+
/** Formatted hierarchy path like "Project A › Subfolder B" (undefined if not requested) */
|
|
185
|
+
hierarchyPath?: string;
|
|
161
186
|
}
|
package/dist/utils.js
CHANGED
|
@@ -80,12 +80,18 @@ export function normalizeItemType(apiItemType) {
|
|
|
80
80
|
'tasks': 'Task',
|
|
81
81
|
'assignments': 'Assignment',
|
|
82
82
|
'folders': 'Folder',
|
|
83
|
+
'projects': 'Project',
|
|
84
|
+
'packages': 'Package',
|
|
85
|
+
'workspaceRoots': 'WorkspaceRoot',
|
|
83
86
|
'milestones': 'Milestone',
|
|
84
87
|
'events': 'Event',
|
|
85
88
|
// Already-normalized values (for safety)
|
|
86
89
|
'Task': 'Task',
|
|
87
90
|
'Assignment': 'Assignment',
|
|
88
91
|
'Folder': 'Folder',
|
|
92
|
+
'Project': 'Project',
|
|
93
|
+
'Package': 'Package',
|
|
94
|
+
'WorkspaceRoot': 'WorkspaceRoot',
|
|
89
95
|
'Milestone': 'Milestone',
|
|
90
96
|
'Event': 'Event',
|
|
91
97
|
};
|
package/dist/workflows.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* Higher-level functions that combine multiple API calls to accomplish
|
|
5
5
|
* common tasks, like resolving a Task ID to the correct Assignment ID.
|
|
6
6
|
*/
|
|
7
|
-
import { DEFAULT_ITEM_NAME, DEFAULT_ASSIGNMENT_NAME } from './constants.js';
|
|
8
7
|
/**
|
|
9
8
|
* Resolve an item ID to the correct Assignment ID for logging time
|
|
10
9
|
*
|
|
@@ -41,7 +40,7 @@ export async function resolveTaskToAssignment(client, itemId, lpMemberId) {
|
|
|
41
40
|
const { item, error: fetchError } = await client.getItem(itemId);
|
|
42
41
|
if (fetchError || !item) {
|
|
43
42
|
return {
|
|
44
|
-
inputItem: { id: itemId, name:
|
|
43
|
+
inputItem: { id: itemId, name: null, itemType: 'Task' },
|
|
45
44
|
assignmentId: 0,
|
|
46
45
|
error: fetchError || 'Item not found',
|
|
47
46
|
};
|
|
@@ -53,7 +52,7 @@ export async function resolveTaskToAssignment(client, itemId, lpMemberId) {
|
|
|
53
52
|
return {
|
|
54
53
|
inputItem: item,
|
|
55
54
|
assignmentId: item.id,
|
|
56
|
-
assignmentName: item.name
|
|
55
|
+
assignmentName: item.name ?? undefined,
|
|
57
56
|
assignmentUserId: item.userId,
|
|
58
57
|
};
|
|
59
58
|
case 'Task': {
|
|
@@ -78,7 +77,7 @@ export async function resolveTaskToAssignment(client, itemId, lpMemberId) {
|
|
|
78
77
|
return {
|
|
79
78
|
inputItem: item,
|
|
80
79
|
assignmentId: assignments[0].id,
|
|
81
|
-
assignmentName: assignments[0].name
|
|
80
|
+
assignmentName: assignments[0].name ?? undefined,
|
|
82
81
|
assignmentUserId: assignments[0].userId,
|
|
83
82
|
};
|
|
84
83
|
}
|
|
@@ -90,7 +89,7 @@ export async function resolveTaskToAssignment(client, itemId, lpMemberId) {
|
|
|
90
89
|
return {
|
|
91
90
|
inputItem: item,
|
|
92
91
|
assignmentId: myAssignments[0].id,
|
|
93
|
-
assignmentName: myAssignments[0].name
|
|
92
|
+
assignmentName: myAssignments[0].name ?? undefined,
|
|
94
93
|
assignmentUserId: myAssignments[0].userId,
|
|
95
94
|
};
|
|
96
95
|
}
|