Skip to content

Commit 5f0ebf3

Browse files
committed
created_at, author, assignee
1 parent b23e662 commit 5f0ebf3

10 files changed

Lines changed: 222 additions & 73 deletions

File tree

.changeset/free-ducks-drive.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"@plotday/tool-outlook-calendar": patch
3+
"@plotday/tool-google-calendar": patch
4+
"@plotday/tool-linear": patch
5+
"@plotday/tool-asana": patch
6+
"@plotday/tool-slack": patch
7+
"@plotday/tool-jira": patch
8+
---
9+
10+
Fixed: Set author and assignee

.changeset/quiet-ties-look.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@plotday/tool-outlook-calendar": patch
3+
"@plotday/tool-google-calendar": patch
4+
"@plotday/tool-linear": patch
5+
"@plotday/tool-asana": patch
6+
"@plotday/tool-slack": patch
7+
"@plotday/tool-jira": patch
8+
"@plotday/twister": patch
9+
---
10+
11+
Added: created_at for item's original creation time in the source system

tools/asana/src/asana.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ActivityType,
66
type NewActivityWithNotes,
77
} from "@plotday/twister";
8+
import type { Actor, ActorId, NewContact } from "@plotday/twister/plot";
89
import type {
910
Project,
1011
ProjectAuth,
@@ -252,6 +253,14 @@ export class Asana extends Tool<Asana> implements ProjectTool {
252253
"completed_at",
253254
"created_at",
254255
"modified_at",
256+
"assignee",
257+
"assignee.email",
258+
"assignee.name",
259+
"assignee.photo",
260+
"created_by",
261+
"created_by.email",
262+
"created_by.name",
263+
"created_by.photo",
255264
].join(","),
256265
};
257266

@@ -317,6 +326,39 @@ export class Asana extends Tool<Asana> implements ProjectTool {
317326
task: any,
318327
projectId: string
319328
): Promise<NewActivityWithNotes> {
329+
const createdBy = task.created_by;
330+
const assignee = task.assignee;
331+
332+
// Create contacts for created_by and assignee
333+
const contacts: NewContact[] = [];
334+
if (createdBy?.email) {
335+
contacts.push({
336+
email: createdBy.email,
337+
name: createdBy.name,
338+
avatar: createdBy.photo?.image_128x128,
339+
});
340+
}
341+
if (assignee?.email && assignee.email !== createdBy?.email) {
342+
contacts.push({
343+
email: assignee.email,
344+
name: assignee.name,
345+
avatar: assignee.photo?.image_128x128,
346+
});
347+
}
348+
349+
let authorActor: Actor | undefined;
350+
let assigneeActor: Actor | undefined;
351+
352+
if (contacts.length > 0) {
353+
const actors = await this.tools.plot.addContacts(contacts);
354+
if (createdBy?.email) {
355+
authorActor = actors.find((a) => a.email === createdBy.email);
356+
}
357+
if (assignee?.email) {
358+
assigneeActor = actors.find((a) => a.email === assignee.email);
359+
}
360+
}
361+
320362
// Build notes array: description
321363
const notes: Array<{ content: string }> = [];
322364

@@ -333,6 +375,8 @@ export class Asana extends Tool<Asana> implements ProjectTool {
333375
type: ActivityType.Action,
334376
title: task.name,
335377
source: `asana:task:${projectId}:${task.gid}`,
378+
author: authorActor,
379+
assignee: assigneeActor,
336380
doneAt: task.completed ? new Date(task.completed_at || Date.now()) : null,
337381
notes,
338382
};
@@ -489,6 +533,14 @@ export class Asana extends Tool<Asana> implements ProjectTool {
489533
"completed_at",
490534
"created_at",
491535
"modified_at",
536+
"assignee",
537+
"assignee.email",
538+
"assignee.name",
539+
"assignee.photo",
540+
"created_by",
541+
"created_by.email",
542+
"created_by.name",
543+
"created_by.photo",
492544
].join(","),
493545
});
494546

tools/google-calendar/src/google-calendar.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,9 +428,22 @@ export class GoogleCalendar
428428
continue;
429429
}
430430

431-
// Extract and create contacts from attendees
431+
// Extract and create contacts from organizer and attendees
432432
let actorIds: ActorId[] = [];
433433
let validAttendees: typeof event.attendees = [];
434+
let authorActor = undefined;
435+
436+
// Create contact for organizer (author)
437+
if (event.organizer?.email) {
438+
const organizerContact: NewContact = {
439+
email: event.organizer.email,
440+
name: event.organizer.displayName,
441+
};
442+
const [author] = await this.tools.plot.addContacts([organizerContact]);
443+
authorActor = author;
444+
}
445+
446+
// Create contacts for attendees
434447
if (event.attendees && event.attendees.length > 0) {
435448
// Filter to get only valid attendees (with email, not resources)
436449
validAttendees = event.attendees.filter(
@@ -557,6 +570,7 @@ export class GoogleCalendar
557570
recurrenceCount: activityData.recurrenceCount || null,
558571
doneAt: null,
559572
title: activityData.title || null,
573+
author: authorActor,
560574
recurrenceRule: activityData.recurrenceRule || null,
561575
recurrenceExdates: activityData.recurrenceExdates || null,
562576
recurrenceDates: activityData.recurrenceDates || null,

tools/jira/src/jira.ts

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import { Version3Client } from "jira.js";
33
import {
44
type ActivityLink,
55
ActivityType,
6+
ActivityUpdate,
67
type NewActivityWithNotes,
78
} from "@plotday/twister";
9+
import type { Actor, ActorId, NewContact } from "@plotday/twister/plot";
810
import type {
911
Project,
1012
ProjectAuth,
@@ -90,11 +92,7 @@ export class Jira extends Tool<Jira> implements ProjectTool {
9092
? R
9193
: []
9294
): Promise<ActivityLink> {
93-
const jiraScopes = [
94-
"read:jira-work",
95-
"write:jira-work",
96-
"read:jira-user",
97-
];
95+
const jiraScopes = ["read:jira-work", "write:jira-work", "read:jira-user"];
9896

9997
// Generate opaque token for authorization
10098
const authToken = crypto.randomUUID();
@@ -155,19 +153,13 @@ export class Jira extends Tool<Jira> implements ProjectTool {
155153
* Start syncing issues from a Jira project
156154
*/
157155
async startSync<
158-
TCallback extends (
159-
issue: NewActivityWithNotes,
160-
...args: any[]
161-
) => any
156+
TCallback extends (issue: NewActivityWithNotes, ...args: any[]) => any
162157
>(
163158
authToken: string,
164159
projectId: string,
165160
callback: TCallback,
166161
options?: ProjectSyncOptions,
167-
...extraArgs: TCallback extends (
168-
issue: any,
169-
...rest: infer R
170-
) => any
162+
...extraArgs: TCallback extends (issue: any, ...rest: infer R) => any
171163
? R
172164
: []
173165
): Promise<void> {
@@ -265,6 +257,8 @@ export class Jira extends Tool<Jira> implements ProjectTool {
265257
"description",
266258
"status",
267259
"assignee",
260+
"reporter",
261+
"creator",
268262
"comment",
269263
"created",
270264
"updated",
@@ -280,10 +274,7 @@ export class Jira extends Tool<Jira> implements ProjectTool {
280274
// Set unread based on sync type (false for initial sync to avoid notification overload)
281275
activityWithNotes.unread = !state.initialSync;
282276
// Execute the callback using the callback token
283-
await this.tools.callbacks.run(
284-
callbackToken,
285-
activityWithNotes
286-
);
277+
await this.tools.callbacks.run(callbackToken, activityWithNotes);
287278
}
288279

289280
// Check if more pages
@@ -294,7 +285,8 @@ export class Jira extends Tool<Jira> implements ProjectTool {
294285
await this.set(`sync_state_${projectId}`, {
295286
startAt: nextStartAt,
296287
batchNumber: state.batchNumber + 1,
297-
issuesProcessed: state.issuesProcessed + (searchResult.issues?.length || 0),
288+
issuesProcessed:
289+
state.issuesProcessed + (searchResult.issues?.length || 0),
298290
initialSync: state.initialSync,
299291
});
300292

@@ -322,6 +314,38 @@ export class Jira extends Tool<Jira> implements ProjectTool {
322314
const fields = issue.fields || {};
323315
const status = fields.status?.name;
324316
const comments = fields.comment?.comments || [];
317+
const reporter = fields.reporter || fields.creator;
318+
const assignee = fields.assignee;
319+
320+
// Create contacts for reporter and assignee
321+
const contacts: NewContact[] = [];
322+
if (reporter?.emailAddress) {
323+
contacts.push({
324+
email: reporter.emailAddress,
325+
name: reporter.displayName,
326+
avatar: reporter.avatarUrls?.["48x48"],
327+
});
328+
}
329+
if (assignee?.emailAddress && assignee.emailAddress !== reporter?.emailAddress) {
330+
contacts.push({
331+
email: assignee.emailAddress,
332+
name: assignee.displayName,
333+
avatar: assignee.avatarUrls?.["48x48"],
334+
});
335+
}
336+
337+
let authorActor: Actor | undefined;
338+
let assigneeActor: Actor | undefined;
339+
340+
if (contacts.length > 0) {
341+
const actors = await this.tools.plot.addContacts(contacts);
342+
if (reporter?.emailAddress) {
343+
authorActor = actors.find((a) => a.email === reporter.emailAddress);
344+
}
345+
if (assignee?.emailAddress) {
346+
assigneeActor = actors.find((a) => a.email === assignee.emailAddress);
347+
}
348+
}
325349

326350
// Build notes array: description + comments
327351
const notes: Array<{ content: string }> = [];
@@ -352,6 +376,8 @@ export class Jira extends Tool<Jira> implements ProjectTool {
352376
type: ActivityType.Action,
353377
title: fields.summary || issue.key,
354378
source: `jira:issue:${projectId}:${issue.key}`,
379+
author: authorActor,
380+
assignee: assigneeActor,
355381
doneAt:
356382
status === "Done" || status === "Closed" || status === "Resolved"
357383
? new Date()
@@ -396,10 +422,7 @@ export class Jira extends Tool<Jira> implements ProjectTool {
396422
* @param authToken - Authorization token
397423
* @param update - ActivityUpdate with changed fields
398424
*/
399-
async updateIssue(
400-
authToken: string,
401-
update: import("@plotday/twister").ActivityUpdate
402-
): Promise<void> {
425+
async updateIssue(authToken: string, update: ActivityUpdate): Promise<void> {
403426
// Extract Jira issue key from source
404427
const issueKey = update.source?.split(":").pop();
405428
if (!issueKey) {

tools/linear/src/linear.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ActivityType,
66
type NewActivityWithNotes,
77
} from "@plotday/twister";
8+
import type { Actor, ActorId, NewContact } from "@plotday/twister/plot";
89
import type {
910
Project,
1011
ProjectAuth,
@@ -320,9 +321,40 @@ export class Linear extends Tool<Linear> implements ProjectTool {
320321
projectId: string
321322
): Promise<NewActivityWithNotes> {
322323
const state = await issue.state;
324+
const creator = await issue.creator;
323325
const assignee = await issue.assignee;
324326
const comments = await issue.comments();
325327

328+
// Create contacts for creator and assignee
329+
const contacts: NewContact[] = [];
330+
if (creator?.email) {
331+
contacts.push({
332+
email: creator.email,
333+
name: creator.name,
334+
avatar: creator.avatarUrl,
335+
});
336+
}
337+
if (assignee?.email && assignee.email !== creator?.email) {
338+
contacts.push({
339+
email: assignee.email,
340+
name: assignee.name,
341+
avatar: assignee.avatarUrl,
342+
});
343+
}
344+
345+
let authorActor: Actor | undefined;
346+
let assigneeActor: Actor | undefined;
347+
348+
if (contacts.length > 0) {
349+
const actors = await this.tools.plot.addContacts(contacts);
350+
if (creator?.email) {
351+
authorActor = actors.find((a) => a.email === creator.email);
352+
}
353+
if (assignee?.email) {
354+
assigneeActor = actors.find((a) => a.email === assignee.email);
355+
}
356+
}
357+
326358
// Build notes array: description + comments
327359
const notes: Array<{ content: string }> = [];
328360

@@ -338,6 +370,8 @@ export class Linear extends Tool<Linear> implements ProjectTool {
338370
type: ActivityType.Action,
339371
title: issue.title,
340372
source: `linear:issue:${projectId}:${issue.id}`,
373+
author: authorActor,
374+
assignee: assigneeActor,
341375
doneAt:
342376
state?.name === "Done" || state?.name === "Completed"
343377
? new Date()

tools/outlook-calendar/src/outlook-calendar.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,9 +379,22 @@ export class OutlookCalendar
379379
continue;
380380
}
381381

382-
// Extract and create contacts from attendees
382+
// Extract and create contacts from organizer and attendees
383383
let actorIds: ActorId[] = [];
384384
let validAttendees: typeof outlookEvent.attendees = [];
385+
let authorActor = undefined;
386+
387+
// Create contact for organizer (author)
388+
if (outlookEvent.organizer?.emailAddress?.address) {
389+
const organizerContact: NewContact = {
390+
email: outlookEvent.organizer.emailAddress.address,
391+
name: outlookEvent.organizer.emailAddress.name,
392+
};
393+
const [author] = await this.tools.plot.addContacts([organizerContact]);
394+
authorActor = author;
395+
}
396+
397+
// Create contacts for attendees
385398
if (outlookEvent.attendees && outlookEvent.attendees.length > 0) {
386399
// Filter to get only valid attendees (with email, not resources)
387400
validAttendees = outlookEvent.attendees.filter(
@@ -491,6 +504,7 @@ export class OutlookCalendar
491504
// Build NewActivityWithNotes from the transformed activity
492505
const activityWithNotes: NewActivityWithNotes = {
493506
...activity,
507+
author: authorActor,
494508
meta: activity.meta,
495509
tags: tags && Object.keys(tags).length > 0 ? tags : activity.tags,
496510
notes,

0 commit comments

Comments
 (0)