@@ -136,55 +136,25 @@ export class AsanaAdapter extends IntegrationAdapter {
136136 event : NormalizedWebhook | AsanaWebhookPayload ,
137137 ) : Promise < IngestResult > {
138138 try {
139- const normalized = this . normalizeEvent ( event ) ;
140- const name = readObjectName ( normalized . payload ) ;
141- const path = computeAsanaPath ( normalized . objectType , normalized . objectId , name ) ;
142- const semantics = this . computeSemantics ( normalized . objectType , normalized . objectId , normalized . payload ) ;
143-
144- if ( this . isDeleteEvent ( normalized ) ) {
145- if ( this . client . deleteFile ) {
146- await this . client . deleteFile ( { workspaceId, path } ) ;
147- return {
148- filesWritten : 0 ,
149- filesUpdated : 0 ,
150- filesDeleted : 1 ,
151- paths : [ path ] ,
152- errors : [ ] ,
153- } ;
154- }
155-
156- const deleteResult = await this . client . writeFile ( {
157- workspaceId,
158- path,
159- content : this . renderContent ( workspaceId , normalized , true ) ,
160- contentType : JSON_CONTENT_TYPE ,
161- semantics,
162- } ) ;
163- const counts = inferWriteCounts ( deleteResult , true ) ;
164- return {
165- filesWritten : counts . filesWritten ,
166- filesUpdated : counts . filesUpdated ,
167- filesDeleted : counts . filesDeleted ,
168- paths : [ path ] ,
169- errors : [ ] ,
170- } ;
171- }
172-
173- const writeResult = await this . client . writeFile ( {
174- workspaceId,
175- path,
176- content : this . renderContent ( workspaceId , normalized , false ) ,
177- contentType : JSON_CONTENT_TYPE ,
178- semantics,
179- } ) ;
180- const counts = inferWriteCounts ( writeResult , false ) ;
181- return {
182- filesWritten : counts . filesWritten ,
183- filesUpdated : counts . filesUpdated ,
139+ const normalizedEvents = this . normalizeEvents ( event ) ;
140+ const result : IngestResult = {
141+ filesWritten : 0 ,
142+ filesUpdated : 0 ,
184143 filesDeleted : 0 ,
185- paths : [ path ] ,
144+ paths : [ ] ,
186145 errors : [ ] ,
187146 } ;
147+
148+ for ( const normalized of normalizedEvents ) {
149+ const eventResult = await this . ingestNormalizedEvent ( workspaceId , normalized ) ;
150+ result . filesWritten += eventResult . filesWritten ;
151+ result . filesUpdated += eventResult . filesUpdated ;
152+ result . filesDeleted += eventResult . filesDeleted ;
153+ result . paths . push ( ...eventResult . paths ) ;
154+ result . errors . push ( ...eventResult . errors ) ;
155+ }
156+
157+ return result ;
188158 } catch ( error ) {
189159 const fallbackPath = inferFallbackPath ( event ) ;
190160 return {
@@ -202,6 +172,57 @@ export class AsanaAdapter extends IntegrationAdapter {
202172 }
203173 }
204174
175+ private async ingestNormalizedEvent ( workspaceId : string , normalized : NormalizedWebhook ) : Promise < IngestResult > {
176+ const name = readObjectName ( normalized . payload ) ;
177+ const path = computeAsanaPath ( normalized . objectType , normalized . objectId , name ) ;
178+ const semantics = this . computeSemantics ( normalized . objectType , normalized . objectId , normalized . payload ) ;
179+
180+ if ( this . isDeleteEvent ( normalized ) ) {
181+ if ( this . client . deleteFile ) {
182+ await this . client . deleteFile ( { workspaceId, path } ) ;
183+ return {
184+ filesWritten : 0 ,
185+ filesUpdated : 0 ,
186+ filesDeleted : 1 ,
187+ paths : [ path ] ,
188+ errors : [ ] ,
189+ } ;
190+ }
191+
192+ const deleteResult = await this . client . writeFile ( {
193+ workspaceId,
194+ path,
195+ content : this . renderContent ( workspaceId , normalized , true ) ,
196+ contentType : JSON_CONTENT_TYPE ,
197+ semantics,
198+ } ) ;
199+ const counts = inferWriteCounts ( deleteResult , true ) ;
200+ return {
201+ filesWritten : counts . filesWritten ,
202+ filesUpdated : counts . filesUpdated ,
203+ filesDeleted : counts . filesDeleted ,
204+ paths : [ path ] ,
205+ errors : [ ] ,
206+ } ;
207+ }
208+
209+ const writeResult = await this . client . writeFile ( {
210+ workspaceId,
211+ path,
212+ content : this . renderContent ( workspaceId , normalized , false ) ,
213+ contentType : JSON_CONTENT_TYPE ,
214+ semantics,
215+ } ) ;
216+ const counts = inferWriteCounts ( writeResult , false ) ;
217+ return {
218+ filesWritten : counts . filesWritten ,
219+ filesUpdated : counts . filesUpdated ,
220+ filesDeleted : 0 ,
221+ paths : [ path ] ,
222+ errors : [ ] ,
223+ } ;
224+ }
225+
205226 override computePath ( objectType : string , objectId : string , name ?: string ) : string {
206227 return computeAsanaPath ( objectType , objectId , name ) ;
207228 }
@@ -260,7 +281,7 @@ export class AsanaAdapter extends IntegrationAdapter {
260281 return compactSemantics ( semantics ) ;
261282 }
262283
263- private normalizeEvent ( event : NormalizedWebhook | AsanaWebhookPayload ) : NormalizedWebhook {
284+ private normalizeEvents ( event : NormalizedWebhook | AsanaWebhookPayload ) : NormalizedWebhook [ ] {
264285 if ( isNormalizedWebhook ( event ) ) {
265286 const normalized : NormalizedWebhook = {
266287 provider : event . provider || this . config . provider || ASANA_PROVIDER_NAME ,
@@ -273,38 +294,46 @@ export class AsanaAdapter extends IntegrationAdapter {
273294 if ( connectionId ) {
274295 normalized . connectionId = connectionId ;
275296 }
276- return normalized ;
297+ return [ normalized ] ;
277298 }
278299
279- const firstEvent = event . events . find ( isRecord ) ;
280- if ( ! firstEvent ) {
281- throw new Error ( 'Asana webhook payload is missing events[0] ' ) ;
300+ const eventItems = event . events . filter ( isRecord ) ;
301+ if ( eventItems . length === 0 ) {
302+ throw new Error ( 'Asana webhook payload is missing events' ) ;
282303 }
283304
284- const resource = getRecord ( firstEvent . resource ) ;
285- const objectType = normalizeAsanaObjectType (
286- asString ( resource ?. resource_type ) ?? asString ( firstEvent . type ) ?? 'task' ,
287- ) ;
288- const objectId = asString ( resource ?. gid ) ?? asString ( firstEvent . gid ) ;
289- if ( ! objectId ) {
290- throw new Error ( `Asana ${ objectType } webhook is missing resource.gid` ) ;
305+ const normalizedEvents : NormalizedWebhook [ ] = [ ] ;
306+ for ( const eventItem of eventItems ) {
307+ const resource = getRecord ( eventItem . resource ) ;
308+ const objectType = normalizeAsanaObjectType (
309+ asString ( resource ?. resource_type ) ?? asString ( eventItem . type ) ?? 'task' ,
310+ ) ;
311+ const objectId = asString ( resource ?. gid ) ?? asString ( eventItem . gid ) ;
312+ if ( ! objectId ) {
313+ continue ;
314+ }
315+
316+ const action = normalizeAction (
317+ asString ( eventItem . action ) ?? asString ( getRecord ( eventItem . change ) ?. action ) ?? 'changed' ,
318+ ) ;
319+ const payload = mergeAsanaPayload ( event , eventItem , objectType , objectId , action ) ;
320+ const normalized : NormalizedWebhook = {
321+ provider : this . config . provider || ASANA_PROVIDER_NAME ,
322+ eventType : `${ objectType } .${ action } ` ,
323+ objectType,
324+ objectId,
325+ payload,
326+ } ;
327+ if ( this . config . connectionId ) {
328+ normalized . connectionId = this . config . connectionId ;
329+ }
330+ normalizedEvents . push ( normalized ) ;
291331 }
292332
293- const action = normalizeAction (
294- asString ( firstEvent . action ) ?? asString ( getRecord ( firstEvent . change ) ?. action ) ?? 'changed' ,
295- ) ;
296- const payload = mergeAsanaPayload ( event , firstEvent , objectType , objectId , action ) ;
297- const normalized : NormalizedWebhook = {
298- provider : this . config . provider || ASANA_PROVIDER_NAME ,
299- eventType : `${ objectType } .${ action } ` ,
300- objectType,
301- objectId,
302- payload,
303- } ;
304- if ( this . config . connectionId ) {
305- normalized . connectionId = this . config . connectionId ;
333+ if ( normalizedEvents . length === 0 ) {
334+ throw new Error ( 'Asana webhook payload is missing resource.gid' ) ;
306335 }
307- return normalized ;
336+ return normalizedEvents ;
308337 }
309338
310339 private isDeleteEvent ( event : NormalizedWebhook ) : boolean {
0 commit comments