@timesheet/plugin-asana 1.4.1
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 +42 -0
- package/dist/handlers/handleSyncBatch.d.ts +4 -0
- package/dist/handlers/handleSyncBatch.js +101 -0
- package/dist/handlers/handleWebhook.d.ts +3 -0
- package/dist/handlers/handleWebhook.js +12 -0
- package/dist/handlers/listExternalProjects.d.ts +3 -0
- package/dist/handlers/listExternalProjects.js +14 -0
- package/dist/handlers/runFullSync.d.ts +3 -0
- package/dist/handlers/runFullSync.js +12 -0
- package/dist/handlers/syncTaskFromExternal.d.ts +3 -0
- package/dist/handlers/syncTaskFromExternal.js +15 -0
- package/dist/handlers/syncTaskToExternal.d.ts +3 -0
- package/dist/handlers/syncTaskToExternal.js +13 -0
- package/dist/handlers/syncTodoFromExternal.d.ts +3 -0
- package/dist/handlers/syncTodoFromExternal.js +12 -0
- package/dist/handlers/syncTodoToExternal.d.ts +3 -0
- package/dist/handlers/syncTodoToExternal.js +13 -0
- package/dist/handlers/testConnection.d.ts +6 -0
- package/dist/handlers/testConnection.js +19 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +23 -0
- package/dist/lib/asanaClient.d.ts +46 -0
- package/dist/lib/asanaClient.js +231 -0
- package/dist/lib/taskSync.d.ts +21 -0
- package/dist/lib/taskSync.js +853 -0
- package/dist/lib/types.d.ts +113 -0
- package/dist/lib/types.js +2 -0
- package/manifest.json +240 -0
- package/package.json +22 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { TaskDto, ToDoDto } from '@timesheet/integration-sdk';
|
|
2
|
+
export interface AsanaConfig {
|
|
3
|
+
syncDirection?: 'bidirectional' | 'timesheet-to-asana' | 'asana-to-timesheet' | 'timesheet-to-external' | 'external-to-timesheet';
|
|
4
|
+
workspaceId?: string;
|
|
5
|
+
defaultAssignee?: string;
|
|
6
|
+
webhookSecret?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface AsanaCompactRef {
|
|
9
|
+
gid: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
resource_type?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AsanaWorkspace {
|
|
14
|
+
gid: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
resource_type?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface AsanaProject {
|
|
19
|
+
gid: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
archived?: boolean;
|
|
22
|
+
workspace?: AsanaCompactRef;
|
|
23
|
+
team?: AsanaCompactRef;
|
|
24
|
+
resource_type?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface AsanaTask {
|
|
27
|
+
gid: string;
|
|
28
|
+
name?: string;
|
|
29
|
+
notes?: string;
|
|
30
|
+
completed?: boolean;
|
|
31
|
+
completed_at?: string | null;
|
|
32
|
+
created_at?: string;
|
|
33
|
+
modified_at?: string;
|
|
34
|
+
due_at?: string | null;
|
|
35
|
+
due_on?: string | null;
|
|
36
|
+
start_at?: string | null;
|
|
37
|
+
start_on?: string | null;
|
|
38
|
+
assignee?: AsanaCompactRef | null;
|
|
39
|
+
projects?: AsanaCompactRef[];
|
|
40
|
+
workspace?: AsanaCompactRef;
|
|
41
|
+
resource_type?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface AsanaTimeTrackingEntry {
|
|
44
|
+
gid: string;
|
|
45
|
+
duration_minutes?: number;
|
|
46
|
+
entered_on?: string;
|
|
47
|
+
created_at?: string;
|
|
48
|
+
created_by?: AsanaCompactRef | null;
|
|
49
|
+
task?: AsanaCompactRef;
|
|
50
|
+
resource_type?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface AsanaPagedResponse<T> {
|
|
53
|
+
data: T[];
|
|
54
|
+
next_page?: {
|
|
55
|
+
offset?: string;
|
|
56
|
+
path?: string;
|
|
57
|
+
uri?: string;
|
|
58
|
+
} | null;
|
|
59
|
+
}
|
|
60
|
+
export interface AsanaSingleResponse<T> {
|
|
61
|
+
data: T;
|
|
62
|
+
}
|
|
63
|
+
export interface AsanaWebhookEvent {
|
|
64
|
+
resource?: {
|
|
65
|
+
gid?: string;
|
|
66
|
+
resource_type?: string;
|
|
67
|
+
resource_subtype?: string;
|
|
68
|
+
};
|
|
69
|
+
parent?: {
|
|
70
|
+
gid?: string;
|
|
71
|
+
resource_type?: string;
|
|
72
|
+
} | null;
|
|
73
|
+
action?: 'changed' | 'added' | 'removed' | 'deleted' | 'undeleted';
|
|
74
|
+
change?: {
|
|
75
|
+
field?: string;
|
|
76
|
+
action?: string;
|
|
77
|
+
};
|
|
78
|
+
created_at?: string;
|
|
79
|
+
user?: {
|
|
80
|
+
gid?: string;
|
|
81
|
+
} | null;
|
|
82
|
+
}
|
|
83
|
+
export interface AsanaWebhookPayload {
|
|
84
|
+
events?: AsanaWebhookEvent[];
|
|
85
|
+
}
|
|
86
|
+
/** Discriminated payload from sync batches / direct invocations. */
|
|
87
|
+
export interface SyncInput {
|
|
88
|
+
event?: string;
|
|
89
|
+
triggerId?: string;
|
|
90
|
+
/** Local Timesheet entity id (task or todo). */
|
|
91
|
+
entityId?: string;
|
|
92
|
+
/** Convenience alias kept for backwards compatibility with task-only callers. */
|
|
93
|
+
taskId?: string;
|
|
94
|
+
/** Inline payload carried by sync changes — shape depends on entityType. */
|
|
95
|
+
item?: (Partial<TaskDto> & {
|
|
96
|
+
taskId?: string;
|
|
97
|
+
id?: string;
|
|
98
|
+
projectId?: string;
|
|
99
|
+
userId?: string;
|
|
100
|
+
todoId?: string;
|
|
101
|
+
}) | (Partial<ToDoDto> & {
|
|
102
|
+
todoId?: string;
|
|
103
|
+
id?: string;
|
|
104
|
+
projectId?: string;
|
|
105
|
+
userId?: string;
|
|
106
|
+
});
|
|
107
|
+
externalTaskId?: string;
|
|
108
|
+
method?: string;
|
|
109
|
+
headers?: Record<string, string>;
|
|
110
|
+
query?: Record<string, string>;
|
|
111
|
+
body?: unknown;
|
|
112
|
+
rawBody?: string;
|
|
113
|
+
}
|
package/manifest.json
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "asana-sync",
|
|
3
|
+
"name": "Asana Sync",
|
|
4
|
+
"version": "1.4.1",
|
|
5
|
+
"description": "Synchronize Timesheet todos and time entries with Asana",
|
|
6
|
+
"longDescription": "Bidirectional synchronization between Timesheet and Asana. Timesheet todos are mirrored 1:1 with Asana tasks in the mapped project, and Timesheet time entries (tasks) attached to a todo are logged as Asana time-tracking entries on the corresponding Asana task. Changes made in Asana flow back through webhooks and scheduled syncs.",
|
|
7
|
+
"icon": "asana",
|
|
8
|
+
"category": "project-management",
|
|
9
|
+
"tags": [
|
|
10
|
+
"sync",
|
|
11
|
+
"timesheet",
|
|
12
|
+
"asana",
|
|
13
|
+
"project-management"
|
|
14
|
+
],
|
|
15
|
+
"dataAccess": [
|
|
16
|
+
"projects",
|
|
17
|
+
"tasks",
|
|
18
|
+
"todos",
|
|
19
|
+
"colleagues",
|
|
20
|
+
"settings"
|
|
21
|
+
],
|
|
22
|
+
"externalAuth": [
|
|
23
|
+
{
|
|
24
|
+
"id": "asana",
|
|
25
|
+
"type": "oauth2",
|
|
26
|
+
"oauth": {
|
|
27
|
+
"authorizeUrl": "https://app.asana.com/-/oauth_authorize",
|
|
28
|
+
"tokenUrl": "https://app.asana.com/-/oauth_token",
|
|
29
|
+
"scopes": [
|
|
30
|
+
"default"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"configSchema": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"syncDirection": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"title": "Sync Direction",
|
|
41
|
+
"enum": [
|
|
42
|
+
"bidirectional",
|
|
43
|
+
"timesheet-to-asana",
|
|
44
|
+
"asana-to-timesheet"
|
|
45
|
+
],
|
|
46
|
+
"default": "bidirectional",
|
|
47
|
+
"x-ui-widget": "radio"
|
|
48
|
+
},
|
|
49
|
+
"workspaceId": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"title": "Asana Workspace",
|
|
52
|
+
"x-ui-help": "Optional. When set, project lookups are scoped to this Asana workspace gid."
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"required": [
|
|
56
|
+
"syncDirection"
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"mappingSchema": {
|
|
60
|
+
"system": "asana",
|
|
61
|
+
"mappings": [
|
|
62
|
+
{
|
|
63
|
+
"id": "projects",
|
|
64
|
+
"title": "Project Mapping",
|
|
65
|
+
"description": "Map Timesheet projects to Asana projects",
|
|
66
|
+
"localEntity": {
|
|
67
|
+
"type": "project",
|
|
68
|
+
"label": "Timesheet Project",
|
|
69
|
+
"display": "{{title}}"
|
|
70
|
+
},
|
|
71
|
+
"externalEntity": {
|
|
72
|
+
"type": "project",
|
|
73
|
+
"label": "Asana Project",
|
|
74
|
+
"fetchAction": "list-external-projects"
|
|
75
|
+
},
|
|
76
|
+
"required": true,
|
|
77
|
+
"minMappings": 1
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
"triggers": [
|
|
82
|
+
{
|
|
83
|
+
"id": "todo-changed",
|
|
84
|
+
"type": "event",
|
|
85
|
+
"mode": "sync",
|
|
86
|
+
"name": "Todo Changed",
|
|
87
|
+
"description": "Sync todo create/update/delete events to Asana tasks",
|
|
88
|
+
"events": [
|
|
89
|
+
"todo.create",
|
|
90
|
+
"todo.update",
|
|
91
|
+
"todo.delete"
|
|
92
|
+
],
|
|
93
|
+
"configurable": true,
|
|
94
|
+
"actionId": "handle-sync-batch"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"id": "task-changed",
|
|
98
|
+
"type": "event",
|
|
99
|
+
"mode": "sync",
|
|
100
|
+
"name": "Time Entry Changed",
|
|
101
|
+
"description": "Log Timesheet time entries as Asana time-tracking entries",
|
|
102
|
+
"events": [
|
|
103
|
+
"task.create",
|
|
104
|
+
"task.update",
|
|
105
|
+
"task.delete"
|
|
106
|
+
],
|
|
107
|
+
"configurable": true,
|
|
108
|
+
"actionId": "handle-sync-batch"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "integration-webhook",
|
|
112
|
+
"type": "webhook",
|
|
113
|
+
"name": "Asana Webhook",
|
|
114
|
+
"description": "Receive inbound task changes from Asana",
|
|
115
|
+
"configurable": false,
|
|
116
|
+
"actionId": "handle-webhook",
|
|
117
|
+
"webhook": {
|
|
118
|
+
"signature": {
|
|
119
|
+
"header": "x-hook-signature",
|
|
120
|
+
"algorithm": "hmac-sha256",
|
|
121
|
+
"secretConfigKey": "webhookSecret"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"id": "full-sync",
|
|
127
|
+
"type": "schedule",
|
|
128
|
+
"mode": "sync",
|
|
129
|
+
"name": "Scheduled Full Sync",
|
|
130
|
+
"description": "Run a complete reconciliation sync",
|
|
131
|
+
"schedule": "0 2 * * *",
|
|
132
|
+
"timezone": "UTC",
|
|
133
|
+
"configurable": true,
|
|
134
|
+
"events": [
|
|
135
|
+
"todo.create",
|
|
136
|
+
"todo.update",
|
|
137
|
+
"todo.delete",
|
|
138
|
+
"task.create",
|
|
139
|
+
"task.update",
|
|
140
|
+
"task.delete"
|
|
141
|
+
],
|
|
142
|
+
"actionId": "handle-sync-batch"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"id": "manual-sync",
|
|
146
|
+
"type": "user_action",
|
|
147
|
+
"name": "Manual Sync",
|
|
148
|
+
"description": "Run a full sync from settings",
|
|
149
|
+
"userAction": {
|
|
150
|
+
"label": "Sync Now",
|
|
151
|
+
"placement": "settings"
|
|
152
|
+
},
|
|
153
|
+
"actionId": "run-full-sync"
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
"actions": [
|
|
157
|
+
{
|
|
158
|
+
"id": "sync-todo-to-external",
|
|
159
|
+
"name": "Sync todo to Asana task",
|
|
160
|
+
"handler": "syncTodoToExternal"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"id": "sync-todo-from-external",
|
|
164
|
+
"name": "Sync todo from Asana payload",
|
|
165
|
+
"handler": "syncTodoFromExternal",
|
|
166
|
+
"internal": true
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"id": "sync-task-to-external",
|
|
170
|
+
"name": "Sync time entry to Asana",
|
|
171
|
+
"handler": "syncTaskToExternal"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"id": "handle-sync-batch",
|
|
175
|
+
"name": "Handle sync batch",
|
|
176
|
+
"handler": "handleSyncBatch"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"id": "sync-task-from-external",
|
|
180
|
+
"name": "Sync time entry from Asana",
|
|
181
|
+
"handler": "syncTaskFromExternal",
|
|
182
|
+
"internal": true
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"id": "handle-webhook",
|
|
186
|
+
"handler": "handleWebhook",
|
|
187
|
+
"internal": true
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"id": "run-full-sync",
|
|
191
|
+
"name": "Run full sync",
|
|
192
|
+
"handler": "runFullSync"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"id": "test-connection",
|
|
196
|
+
"name": "Test connection",
|
|
197
|
+
"handler": "testConnection"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"id": "list-external-projects",
|
|
201
|
+
"handler": "listExternalProjects",
|
|
202
|
+
"internal": true
|
|
203
|
+
}
|
|
204
|
+
],
|
|
205
|
+
"pages": [
|
|
206
|
+
{
|
|
207
|
+
"id": "dashboard",
|
|
208
|
+
"title": "Sync Dashboard",
|
|
209
|
+
"icon": "chart-bar",
|
|
210
|
+
"layout": "dashboard",
|
|
211
|
+
"widgets": [
|
|
212
|
+
{
|
|
213
|
+
"id": "last-sync",
|
|
214
|
+
"type": "metric",
|
|
215
|
+
"title": "Last Sync"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"id": "sync-stats",
|
|
219
|
+
"type": "metric",
|
|
220
|
+
"title": "Sync Statistics"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"id": "connection-health",
|
|
224
|
+
"type": "metric",
|
|
225
|
+
"title": "Connection Health"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"id": "manual-sync-btn",
|
|
229
|
+
"type": "action_button",
|
|
230
|
+
"title": "Manual Sync",
|
|
231
|
+
"config": {
|
|
232
|
+
"actionId": "run-full-sync",
|
|
233
|
+
"label": "Run Full Sync",
|
|
234
|
+
"variant": "primary"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
}
|
|
239
|
+
]
|
|
240
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@timesheet/plugin-asana",
|
|
3
|
+
"version": "1.4.1",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"manifest.json"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
12
|
+
"typecheck": "tsc --noEmit",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@timesheet/integration-sdk": "^0.5.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^20.19.43",
|
|
20
|
+
"typescript": "^5.9.3"
|
|
21
|
+
}
|
|
22
|
+
}
|