@@ -8,7 +8,6 @@ import { auth } from '../auth/middleware.js';
88import type {
99 ActivityDirective ,
1010 ActivityDirectiveInsertInput ,
11- ActivityDirectiveSetInput ,
1211 ImportPlanPayload ,
1312 PlanInsertInput ,
1413 PlanSchema ,
@@ -53,6 +52,7 @@ export async function importPlan(req: Request, res: Response) {
5352 } ;
5453
5554 let createdPlan : PlanSchema | null = null ;
55+ let createdTags : Tag [ ] = [ ] ;
5656
5757 try {
5858 const { activities, simulation_arguments } : PlanTransfer = await new Promise ( resolve => {
@@ -123,36 +123,6 @@ export async function importPlan(req: Request, res: Response) {
123123 // insert all the imported activities into the plan
124124 logger . info ( `POST /importPlan: Importing activities: ${ name } ` ) ;
125125
126- const activityTags = activities . reduce (
127- ( prevActivitiesTagsMap : Record < string , Pick < Tag , 'color' | 'name' > > , { tags } ) => {
128- const tagsMap =
129- tags ?. reduce ( ( prevTagsMap : Record < string , Pick < Tag , 'color' | 'name' > > , { tag } ) => {
130- return {
131- ...prevTagsMap ,
132- [ tag . name ] : {
133- color : tag . color ,
134- name : tag . name ,
135- } ,
136- } ;
137- } , { } ) ?? { } ;
138-
139- return {
140- ...prevActivitiesTagsMap ,
141- ...tagsMap ,
142- } ;
143- } ,
144- { } ,
145- ) ;
146-
147- await fetch ( GQL_API_URL , {
148- body : JSON . stringify ( {
149- query : gql . CREATE_TAGS ,
150- variables : { tags : Object . values ( activityTags ) } ,
151- } ) ,
152- headers,
153- method : 'POST' ,
154- } ) ;
155-
156126 const tagsResponse = await fetch ( GQL_API_URL , {
157127 body : JSON . stringify ( {
158128 query : gql . GET_TAGS ,
@@ -180,88 +150,153 @@ export async function importPlan(req: Request, res: Response) {
180150 } , { } ) ;
181151 }
182152
153+ // derive a map of uniquely named tags from the list of activities that doesn't already exist in the database
154+ const activityTags = activities . reduce (
155+ ( prevActivitiesTagsMap : Record < string , Pick < Tag , 'color' | 'name' > > , { tags } ) => {
156+ const currentTagsMap =
157+ tags ?. reduce (
158+ ( prevTagsMap : Record < string , Pick < Tag , 'color' | 'name' > > , { tag : { name : tagName , color } } ) => {
159+ // If the tag doesn't exist already, add it
160+ if ( tagsMap [ tagName ] === undefined ) {
161+ return {
162+ ...prevTagsMap ,
163+ [ tagName ] : {
164+ color,
165+ name : tagName ,
166+ } ,
167+ } ;
168+ }
169+ return prevTagsMap ;
170+ } ,
171+ { } ,
172+ ) ?? { } ;
173+
174+ return {
175+ ...prevActivitiesTagsMap ,
176+ ...currentTagsMap ,
177+ } ;
178+ } ,
179+ { } ,
180+ ) ;
181+
182+ const createdTagsResponse = await fetch ( GQL_API_URL , {
183+ body : JSON . stringify ( {
184+ query : gql . CREATE_TAGS ,
185+ variables : { tags : Object . values ( activityTags ) } ,
186+ } ) ,
187+ headers,
188+ method : 'POST' ,
189+ } ) ;
190+
191+ const { data } = ( await createdTagsResponse . json ( ) ) as {
192+ data : {
193+ insert_tags : { returning : Tag [ ] } ;
194+ } ;
195+ } ;
196+
197+ if ( data && data . insert_tags && data . insert_tags . returning . length ) {
198+ // track the newly created tags for cleanup if an error occurs during plan import
199+ createdTags = data . insert_tags . returning ;
200+ }
201+
202+ // add the newly created tags to the `tagsMap`
203+ tagsMap = createdTags . reduce (
204+ ( prevTagsMap : Record < string , Tag > , tag ) => ( {
205+ ...prevTagsMap ,
206+ [ tag . name ] : tag ,
207+ } ) ,
208+ tagsMap ,
209+ ) ;
210+
183211 const activityRemap : Record < number , number > = { } ;
184- await Promise . all (
185- activities . map (
186- async ( {
212+ const activityDirectivesInsertInput = activities . map (
213+ ( {
214+ anchored_to_start : anchoredToStart ,
215+ arguments : activityArguments ,
216+ metadata,
217+ name : activityName ,
218+ start_offset : startOffset ,
219+ tags,
220+ type,
221+ } ) => {
222+ const activityDirectiveInsertInput : ActivityDirectiveInsertInput = {
223+ anchor_id : null ,
187224 anchored_to_start : anchoredToStart ,
188225 arguments : activityArguments ,
189- id,
190226 metadata,
191227 name : activityName ,
228+ plan_id : ( createdPlan as PlanSchema ) . id ,
192229 start_offset : startOffset ,
193- tags,
230+ tags : {
231+ data :
232+ tags ?. map ( ( { tag : { name } } ) => ( {
233+ tag_id : tagsMap [ name ] . id ,
234+ } ) ) ?? [ ] ,
235+ } ,
194236 type,
195- } ) => {
196- const activityDirectiveInsertInput : ActivityDirectiveInsertInput = {
197- anchor_id : null ,
198- anchored_to_start : anchoredToStart ,
199- arguments : activityArguments ,
200- metadata,
201- name : activityName ,
202- plan_id : ( createdPlan as PlanSchema ) . id ,
203- start_offset : startOffset ,
204- tags : {
205- data :
206- tags ?. map ( ( { tag : { name } } ) => ( {
207- tag_id : tagsMap [ name ] . id ,
208- } ) ) ?? [ ] ,
209- } ,
210- type,
211- } ;
212-
213- const createdActivityDirectiveResponse = await fetch ( GQL_API_URL , {
214- body : JSON . stringify ( {
215- query : gql . CREATE_ACTIVITY_DIRECTIVE ,
216- variables : { activityDirectiveInsertInput } ,
217- } ) ,
218- headers,
219- method : 'POST' ,
220- } ) ;
221-
222- const createdActivityDirectiveData = ( await createdActivityDirectiveResponse . json ( ) ) as {
223- data : {
224- insert_activity_directive_one : ActivityDirective ;
225- } ;
226- } | null ;
227-
228- if ( createdActivityDirectiveData ) {
229- const {
230- data : { insert_activity_directive_one : createdActivityDirective } ,
231- } = createdActivityDirectiveData ;
232- activityRemap [ id ] = createdActivityDirective . id ;
233- }
234- } ,
235- ) ,
237+ } ;
238+
239+ return activityDirectiveInsertInput ;
240+ } ,
236241 ) ;
237242
243+ const createdActivitiesResponse = await fetch ( GQL_API_URL , {
244+ body : JSON . stringify ( {
245+ query : gql . CREATE_ACTIVITY_DIRECTIVES ,
246+ variables : {
247+ activityDirectivesInsertInput,
248+ } ,
249+ } ) ,
250+ headers,
251+ method : 'POST' ,
252+ } ) ;
253+
254+ const createdActivityDirectivesData = ( await createdActivitiesResponse . json ( ) ) as {
255+ data : {
256+ insert_activity_directive : {
257+ returning : ActivityDirective [ ] ;
258+ } ;
259+ } ;
260+ } | null ;
261+
262+ if ( createdActivityDirectivesData ) {
263+ const {
264+ data : {
265+ insert_activity_directive : { returning : createdActivityDirectives } ,
266+ } ,
267+ } = createdActivityDirectivesData ;
268+
269+ if ( createdActivityDirectives . length === activities . length ) {
270+ createdActivityDirectives . forEach ( ( createdActivityDirective , index ) => {
271+ const { id } = activities [ index ] ;
272+
273+ activityRemap [ id ] = createdActivityDirective . id ;
274+ } ) ;
275+ } else {
276+ throw new Error ( 'Activity insertion failed.' ) ;
277+ }
278+ }
279+
238280 // remap all the anchor ids to the newly created activity directives
239281 logger . info ( `POST /importPlan: Re-assigning anchors: ${ name } ` ) ;
240- await Promise . all (
241- activities . map ( async ( { anchor_id : anchorId , id } ) => {
242- if ( anchorId !== null && activityRemap [ anchorId ] != null && activityRemap [ id ] != null ) {
243- logger . info (
244- `POST /importPlan: Re-assigning anchor ${ anchorId } to ${ activityRemap [ anchorId ] } for activity ${ activityRemap [ id ] } : ${ name } ` ,
245- ) ;
246- const activityDirectiveSetInput : ActivityDirectiveSetInput = {
247- anchor_id : activityRemap [ anchorId ] ,
248- } ;
249-
250- return fetch ( GQL_API_URL , {
251- body : JSON . stringify ( {
252- query : gql . UPDATE_ACTIVITY_DIRECTIVE ,
253- variables : {
254- activityDirectiveSetInput,
255- id : activityRemap [ id ] ,
256- plan_id : ( createdPlan as PlanSchema ) . id ,
257- } ,
258- } ) ,
259- headers,
260- method : 'POST' ,
261- } ) ;
262- }
282+
283+ const activityDirectivesSetInput = activities
284+ . filter ( ( { anchor_id : anchorId } ) => anchorId !== null )
285+ . map ( ( { anchor_id : anchorId , id } ) => ( {
286+ _set : { anchor_id : activityRemap [ anchorId as number ] } ,
287+ where : { id : { _eq : activityRemap [ id ] } , plan_id : { _eq : ( createdPlan as PlanSchema ) . id } } ,
288+ } ) ) ;
289+
290+ await fetch ( GQL_API_URL , {
291+ body : JSON . stringify ( {
292+ query : gql . UPDATE_ACTIVITY_DIRECTIVES ,
293+ variables : {
294+ updates : activityDirectivesSetInput ,
295+ } ,
263296 } ) ,
264- ) ;
297+ headers,
298+ method : 'POST' ,
299+ } ) ;
265300
266301 // associate the tags with the newly created plan
267302 logger . info ( `POST /importPlan: Importing plan tags: ${ name } ` ) ;
@@ -288,14 +323,23 @@ export async function importPlan(req: Request, res: Response) {
288323 logger . error ( `POST /importPlan: Error occurred during plan ${ name } import` ) ;
289324 logger . error ( error ) ;
290325
326+ // cleanup the imported plan if it failed along the way
291327 if ( createdPlan ) {
328+ // delete the plan - activities associated to the plan will be automatically cleaned up
292329 await fetch ( GQL_API_URL , {
293330 body : JSON . stringify ( { query : gql . DELETE_PLAN , variables : { id : createdPlan . id } } ) ,
294331 headers,
295332 method : 'POST' ,
296333 } ) ;
334+
335+ // if any activity tags were created as a result of this import, remove them
336+ await fetch ( GQL_API_URL , {
337+ body : JSON . stringify ( { query : gql . DELETE_TAGS , variables : { tagIds : createdTags . map ( ( { id } ) => id ) } } ) ,
338+ headers,
339+ method : 'POST' ,
340+ } ) ;
297341 }
298- res . send ( 500 ) ;
342+ res . sendStatus ( 500 ) ;
299343 }
300344}
301345
0 commit comments