From 17983ffb047ee2a3f384dfd3b7d253c8270b65dd Mon Sep 17 00:00:00 2001 From: Arpit Date: Thu, 7 Dec 2023 18:54:05 +0530 Subject: [PATCH] [hotfix ]fix: event mapping query race condition (#8258) * fix: event mapping query race condition * fixes: duplication dq from lds and gds --- .../src/services/app_import_export.service.ts | 401 +++++++++--------- server/src/services/apps.service.ts | 108 ++--- 2 files changed, 266 insertions(+), 243 deletions(-) diff --git a/server/src/services/app_import_export.service.ts b/server/src/services/app_import_export.service.ts index 4ba42c298e..0f84d30f85 100644 --- a/server/src/services/app_import_export.service.ts +++ b/server/src/services/app_import_export.service.ts @@ -357,192 +357,203 @@ export class AppImportExportService { appResourceMappings.appDefaultEnvironmentMapping = appDefaultEnvironmentMapping; appResourceMappings.appVersionMapping = appVersionMapping; - appResourceMappings = await this.setupAppVersionAssociations( - manager, - importingAppVersions, - user, - appResourceMappings, - externalResourceMappings, - importingAppEnvironments, - importingDataSources, - importingDataSourceOptions, - importingDataQueries, - importingDefaultAppEnvironmentId, - importingPages, - importingComponents, - importingEvents - ); + /** + * as multiple operations are run within a single transaction using the transaction method provides a convenient way to handle transactions. + * The transaction will automatically committed when the function completes without throwing an error. + * If an error occurs during the function execution, the transaction will rolled back. + */ - if (!isNormalizedAppDefinitionSchema) { - for (const importingAppVersion of importingAppVersions) { - const updatedDefinition = this.replaceDataQueryIdWithinDefinitions( - importingAppVersion.definition, - appResourceMappings.dataQueryMapping - ); + await manager.transaction(async (transactionalEntityManager) => { + appResourceMappings = await this.setupAppVersionAssociations( + transactionalEntityManager, + importingAppVersions, + user, + appResourceMappings, + externalResourceMappings, + importingAppEnvironments, + importingDataSources, + importingDataSourceOptions, + importingDataQueries, + importingDefaultAppEnvironmentId, + importingPages, + importingComponents, + importingEvents + ); - let updateHomepageId = null; + if (!isNormalizedAppDefinitionSchema) { + for (const importingAppVersion of importingAppVersions) { + const updatedDefinition = this.replaceDataQueryIdWithinDefinitions( + importingAppVersion.definition, + appResourceMappings.dataQueryMapping + ); - if (updatedDefinition?.pages) { - for (const pageId of Object.keys(updatedDefinition?.pages)) { - const page = updatedDefinition.pages[pageId]; + let updateHomepageId = null; - const pageEvents = page.events || []; - const componentEvents = []; + if (updatedDefinition?.pages) { + for (const pageId of Object.keys(updatedDefinition?.pages)) { + const page = updatedDefinition.pages[pageId]; - const pagePostionIntheList = Object.keys(updatedDefinition?.pages).indexOf(pageId); + const pageEvents = page.events || []; + const componentEvents = []; - const isHompage = (updatedDefinition['homePageId'] as any) === pageId; + const pagePostionIntheList = Object.keys(updatedDefinition?.pages).indexOf(pageId); - const pageComponents = page.components; + const isHompage = (updatedDefinition['homePageId'] as any) === pageId; - const mappedComponents = transformComponentData( - pageComponents, - componentEvents, - appResourceMappings.componentsMapping, - isNormalizedAppDefinitionSchema - ); + const pageComponents = page.components; - const componentLayouts = []; + const mappedComponents = transformComponentData( + pageComponents, + componentEvents, + appResourceMappings.componentsMapping, + isNormalizedAppDefinitionSchema + ); - const newPage = manager.create(Page, { - name: page.name, - handle: page.handle, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - index: pagePostionIntheList, - disabled: page.disabled || false, - hidden: page.hidden || false, - }); - const pageCreated = await manager.save(newPage); + const componentLayouts = []; - appResourceMappings.pagesMapping[pageId] = pageCreated.id; + const newPage = transactionalEntityManager.create(Page, { + name: page.name, + handle: page.handle, + appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], + index: pagePostionIntheList, + disabled: page.disabled || false, + hidden: page.hidden || false, + }); + const pageCreated = await transactionalEntityManager.save(newPage); - mappedComponents.forEach((component) => { - component.page = pageCreated; - }); + appResourceMappings.pagesMapping[pageId] = pageCreated.id; - const savedComponents = await manager.save(Component, mappedComponents); + mappedComponents.forEach((component) => { + component.page = pageCreated; + }); - for (const componentId in pageComponents) { - const componentLayout = pageComponents[componentId]['layouts']; + const savedComponents = await transactionalEntityManager.save(Component, mappedComponents); - if (componentLayout && appResourceMappings.componentsMapping[componentId]) { - for (const type in componentLayout) { - const layout = componentLayout[type]; - const newLayout = new Layout(); - newLayout.type = type; - newLayout.top = layout.top; - newLayout.left = layout.left; - newLayout.width = layout.width; - newLayout.height = layout.height; - newLayout.componentId = appResourceMappings.componentsMapping[componentId]; + for (const componentId in pageComponents) { + const componentLayout = pageComponents[componentId]['layouts']; - componentLayouts.push(newLayout); + if (componentLayout && appResourceMappings.componentsMapping[componentId]) { + for (const type in componentLayout) { + const layout = componentLayout[type]; + const newLayout = new Layout(); + newLayout.type = type; + newLayout.top = layout.top; + newLayout.left = layout.left; + newLayout.width = layout.width; + newLayout.height = layout.height; + newLayout.componentId = appResourceMappings.componentsMapping[componentId]; + + componentLayouts.push(newLayout); + } } } - } - await manager.save(Layout, componentLayouts); + await transactionalEntityManager.save(Layout, componentLayouts); - //Event handlers + //Event handlers - if (pageEvents.length > 0) { - pageEvents.forEach(async (event, index) => { - const newEvent = { - name: event.eventId, - sourceId: pageCreated.id, - target: Target.page, - event: event, - index: pageEvents.index || index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }; + if (pageEvents.length > 0) { + pageEvents.forEach(async (event, index) => { + const newEvent = { + name: event.eventId, + sourceId: pageCreated.id, + target: Target.page, + event: event, + index: pageEvents.index || index, + appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], + }; - await manager.save(EventHandler, newEvent); - }); - } - - componentEvents.forEach((eventObj) => { - if (eventObj.event?.length === 0) return; - - eventObj.event.forEach(async (event, index) => { - const newEvent = await manager.create(EventHandler, { - name: event.eventId, - sourceId: appResourceMappings.componentsMapping[eventObj.componentId], - target: Target.component, - event: event, - index: eventObj.index || index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], + await transactionalEntityManager.save(EventHandler, newEvent); }); - - await manager.save(EventHandler, newEvent); - }); - }); - - savedComponents.forEach(async (component) => { - if (component.type === 'Table') { - const tableActions = component.properties?.actions?.value || []; - const tableColumns = component.properties?.columns?.value || []; - - const tableActionAndColumnEvents = []; - - tableActions.forEach((action) => { - const actionEvents = action.events || []; - - actionEvents.forEach((event, index) => { - tableActionAndColumnEvents.push({ - name: event.eventId, - sourceId: component.id, - target: Target.tableAction, - event: { ...event, ref: action.name }, - index: event.index ?? index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }); - }); - }); - - tableColumns.forEach((column) => { - if (column?.columnType !== 'toggle') return; - const columnEvents = column.events || []; - - columnEvents.forEach((event, index) => { - tableActionAndColumnEvents.push({ - name: event.eventId, - sourceId: component.id, - target: Target.tableColumn, - event: { ...event, ref: column.name }, - index: event.index ?? index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }); - }); - }); - - await manager.save(EventHandler, tableActionAndColumnEvents); } - }); - if (isHompage) { - updateHomepageId = pageCreated.id; + componentEvents.forEach((eventObj) => { + if (eventObj.event?.length === 0) return; + + eventObj.event.forEach(async (event, index) => { + const newEvent = transactionalEntityManager.create(EventHandler, { + name: event.eventId, + sourceId: appResourceMappings.componentsMapping[eventObj.componentId], + target: Target.component, + event: event, + index: eventObj.index || index, + appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], + }); + + await transactionalEntityManager.save(EventHandler, newEvent); + }); + }); + + savedComponents.forEach(async (component) => { + if (component.type === 'Table') { + const tableActions = component.properties?.actions?.value || []; + const tableColumns = component.properties?.columns?.value || []; + + const tableActionAndColumnEvents = []; + + tableActions.forEach((action) => { + const actionEvents = action.events || []; + + actionEvents.forEach((event, index) => { + tableActionAndColumnEvents.push({ + name: event.eventId, + sourceId: component.id, + target: Target.tableAction, + event: { ...event, ref: action.name }, + index: event.index ?? index, + appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], + }); + }); + }); + + tableColumns.forEach((column) => { + if (column?.columnType !== 'toggle') return; + const columnEvents = column.events || []; + + columnEvents.forEach((event, index) => { + tableActionAndColumnEvents.push({ + name: event.eventId, + sourceId: component.id, + target: Target.tableColumn, + event: { ...event, ref: column.name }, + index: event.index ?? index, + appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], + }); + }); + }); + + await transactionalEntityManager.save(EventHandler, tableActionAndColumnEvents); + } + }); + + if (isHompage) { + updateHomepageId = pageCreated.id; + } } } + + await transactionalEntityManager.update( + AppVersion, + { id: appResourceMappings.appVersionMapping[importingAppVersion.id] }, + { + definition: updatedDefinition, + homePageId: updateHomepageId, + } + ); } - - await manager.update( - AppVersion, - { id: appResourceMappings.appVersionMapping[importingAppVersion.id] }, - { - definition: updatedDefinition, - homePageId: updateHomepageId, - } - ); - - await this.updateEventActionsForNewVersionWithNewMappingIds( - manager, - appResourceMappings.appVersionMapping[importingAppVersion.id], - appResourceMappings.dataQueryMapping, - appResourceMappings.componentsMapping, - appResourceMappings.pagesMapping, - isNormalizedAppDefinitionSchema - ); } + }); + + const appVersionIds = Object.values(appResourceMappings.appVersionMapping); + + for (const appVersionId of appVersionIds) { + await this.updateEventActionsForNewVersionWithNewMappingIds( + manager, + appVersionId, + appResourceMappings.dataQueryMapping, + appResourceMappings.componentsMapping, + appResourceMappings.pagesMapping + ); } await this.setEditingVersionAsLatestVersion(manager, appResourceMappings.appVersionMapping, importingAppVersions); @@ -741,13 +752,14 @@ export class AppImportExportService { if (componentEvents.length > 0) { componentEvents.forEach(async (componentEvent) => { - const newEvent = new EventHandler(); - newEvent.name = componentEvent.name; - newEvent.sourceId = savedComponent.id; - newEvent.target = componentEvent.target; - newEvent.event = componentEvent.event; - newEvent.index = componentEvent.index; - newEvent.appVersionId = appResourceMappings.appVersionMapping[importingAppVersion.id]; + const newEvent = await manager.create(EventHandler, { + name: componentEvent.name, + sourceId: savedComponent.id, + target: componentEvent.target, + event: componentEvent.event, + index: componentEvent.index, + appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], + }); await manager.save(EventHandler, newEvent); }); @@ -759,14 +771,14 @@ export class AppImportExportService { if (pageEvents.length > 0) { pageEvents.forEach(async (pageEvent) => { - const newEvent = { + const newEvent = await manager.create(EventHandler, { name: pageEvent.name, sourceId: pageCreated.id, target: pageEvent.target, event: pageEvent.event, index: pageEvent.index, appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }; + }); await manager.save(EventHandler, newEvent); }); @@ -790,14 +802,14 @@ export class AppImportExportService { if (importingQueryEvents.length > 0) { importingQueryEvents.forEach(async (dataQueryEvent) => { - const newEvent = { + const newEvent = await manager.create(EventHandler, { name: dataQueryEvent.name, sourceId: mappedNewDataQuery.id, target: dataQueryEvent.target, event: dataQueryEvent.event, index: dataQueryEvent.index, appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }; + }); await manager.save(EventHandler, newEvent); }); @@ -812,14 +824,14 @@ export class AppImportExportService { if (queryEvents.length > 0) { queryEvents.forEach(async (event, index) => { - const newEvent = { + const newEvent = await manager.create(EventHandler, { name: event.eventId, sourceId: mappedNewDataQuery.id, target: Target.dataQuery, event: event, - index: queryEvents.index || index, - appVersionId: mappedNewDataQuery.appVersionId, - }; + index: event.index ?? index, + appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], + }); await manager.save(EventHandler, newEvent); }); @@ -838,18 +850,17 @@ export class AppImportExportService { ); } - const appVersionIds = Object.values(appResourceMappings.appVersionMapping); + // const appVersionIds = Object.values(appResourceMappings.appVersionMapping); - for (const appVersionId of appVersionIds) { - await this.updateEventActionsForNewVersionWithNewMappingIds( - manager, - appVersionId, - appResourceMappings.dataQueryMapping, - appResourceMappings.componentsMapping, - appResourceMappings.pagesMapping, - true - ); - } + // for (const appVersionId of appVersionIds) { + // await this.updateEventActionsForNewVersionWithNewMappingIds( + // manager, + // appVersionId, + // appResourceMappings.dataQueryMapping, + // appResourceMappings.componentsMapping, + // appResourceMappings.pagesMapping + // ); + // } return appResourceMappings; } @@ -1513,29 +1524,35 @@ export class AppImportExportService { versionId: string, oldDataQueryToNewMapping: Record, oldComponentToNewComponentMapping: Record, - oldPageToNewPageMapping: Record, - isNormalizedAppDefinitionSchema: boolean + oldPageToNewPageMapping: Record ) { - const allEvents = await manager.find(EventHandler, { - where: { appVersionId: versionId }, - }); + const allEvents = await manager + .createQueryBuilder(EventHandler, 'event') + .where('event.appVersionId = :versionId', { versionId }) + .getMany(); for (const event of allEvents) { const eventDefinition = event.event; - if (isNormalizedAppDefinitionSchema && eventDefinition?.actionId === 'run-query') { + if (eventDefinition?.actionId === 'run-query' && oldDataQueryToNewMapping[eventDefinition.queryId]) { eventDefinition.queryId = oldDataQueryToNewMapping[eventDefinition.queryId]; } - if (eventDefinition?.actionId === 'control-component') { + if ( + eventDefinition?.actionId === 'control-component' && + oldComponentToNewComponentMapping[eventDefinition.componentId] + ) { eventDefinition.componentId = oldComponentToNewComponentMapping[eventDefinition.componentId]; } - if (eventDefinition?.actionId === 'switch-page') { + if (eventDefinition?.actionId === 'switch-page' && oldPageToNewPageMapping[eventDefinition.pageId]) { eventDefinition.pageId = oldPageToNewPageMapping[eventDefinition.pageId]; } - if (eventDefinition?.actionId == 'show-modal' || eventDefinition?.actionId === 'close-modal') { + if ( + (eventDefinition?.actionId == 'show-modal' || eventDefinition?.actionId === 'close-modal') && + oldComponentToNewComponentMapping[eventDefinition.modal] + ) { eventDefinition.modal = oldComponentToNewComponentMapping[eventDefinition.modal]; } diff --git a/server/src/services/apps.service.ts b/server/src/services/apps.service.ts index 4f3727e82b..1dbd7d32d2 100644 --- a/server/src/services/apps.service.ts +++ b/server/src/services/apps.service.ts @@ -685,65 +685,71 @@ export class AppsService { newDataQueries.push(newQuery); } } - } - if (globalQueries?.length > 0) { - for (const globalQuery of globalQueries) { - const dataQueryParams = { - name: globalQuery.name, - options: globalQuery.options, - dataSourceId: globalQuery.dataSourceId, - appVersionId: appVersion.id, - }; + if (globalQueries?.length > 0) { + for (const globalQuery of globalQueries) { + const dataQueryParams = { + name: globalQuery.name, + options: globalQuery.options, + dataSourceId: globalQuery.dataSourceId, + appVersionId: appVersion.id, + }; - const newQuery = await manager.save(manager.create(DataQuery, dataQueryParams)); - const dataQueryEvents = allEvents.filter((event) => event.sourceId === globalQuery.id); + const newQuery = await manager.save(manager.create(DataQuery, dataQueryParams)); + const dataQueryEvents = allEvents.filter((event) => event.sourceId === globalQuery.id); - dataQueryEvents.forEach(async (event, index) => { - const newEvent = new EventHandler(); + dataQueryEvents.forEach(async (event, index) => { + const newEvent = new EventHandler(); - newEvent.id = uuid.v4(); - newEvent.name = event.name; - newEvent.sourceId = newQuery.id; - newEvent.target = event.target; - newEvent.event = event.event; - newEvent.index = event.index ?? index; - newEvent.appVersionId = appVersion.id; + newEvent.id = uuid.v4(); + newEvent.name = event.name; + newEvent.sourceId = newQuery.id; + newEvent.target = event.target; + newEvent.event = event.event; + newEvent.index = event.index ?? index; + newEvent.appVersionId = appVersion.id; - await manager.save(newEvent); - }); - oldDataQueryToNewMapping[globalQuery.id] = newQuery.id; - newDataQueries.push(newQuery); + await manager.save(newEvent); + }); + oldDataQueryToNewMapping[globalQuery.id] = newQuery.id; + newDataQueries.push(newQuery); + } } - } - for (const newQuery of newDataQueries) { - const newOptions = this.replaceDataQueryOptionsWithNewDataQueryIds(newQuery.options, oldDataQueryToNewMapping); - newQuery.options = newOptions; - - await manager.save(newQuery); - } - - appVersion.definition = this.replaceDataQueryIdWithinDefinitions(appVersion.definition, oldDataQueryToNewMapping); - await manager.save(appVersion); - - for (const appEnvironment of appEnvironments) { - for (const dataSource of dataSources) { - const dataSourceOption = await manager.findOneOrFail(DataSourceOptions, { - where: { dataSourceId: dataSource.id, environmentId: appEnvironment.id }, - }); - - const convertedOptions = this.convertToArrayOfKeyValuePairs(dataSourceOption.options); - const newOptions = await this.dataSourcesService.parseOptionsForCreate(convertedOptions, false, manager); - await this.setNewCredentialValueFromOldValue(newOptions, convertedOptions, manager); - - await manager.save( - manager.create(DataSourceOptions, { - options: newOptions, - dataSourceId: dataSourceMapping[dataSource.id], - environmentId: appEnvironment.id, - }) + for (const newQuery of newDataQueries) { + const newOptions = this.replaceDataQueryOptionsWithNewDataQueryIds( + newQuery.options, + oldDataQueryToNewMapping ); + newQuery.options = newOptions; + + await manager.save(newQuery); + } + + appVersion.definition = this.replaceDataQueryIdWithinDefinitions( + appVersion.definition, + oldDataQueryToNewMapping + ); + await manager.save(appVersion); + + for (const appEnvironment of appEnvironments) { + for (const dataSource of dataSources) { + const dataSourceOption = await manager.findOneOrFail(DataSourceOptions, { + where: { dataSourceId: dataSource.id, environmentId: appEnvironment.id }, + }); + + const convertedOptions = this.convertToArrayOfKeyValuePairs(dataSourceOption.options); + const newOptions = await this.dataSourcesService.parseOptionsForCreate(convertedOptions, false, manager); + await this.setNewCredentialValueFromOldValue(newOptions, convertedOptions, manager); + + await manager.save( + manager.create(DataSourceOptions, { + options: newOptions, + dataSourceId: dataSourceMapping[dataSource.id], + environmentId: appEnvironment.id, + }) + ); + } } } }