diff --git a/frontend/src/Editor/EditorFunc.jsx b/frontend/src/Editor/EditorFunc.jsx index 2bc04a8450..80679fb6bf 100644 --- a/frontend/src/Editor/EditorFunc.jsx +++ b/frontend/src/Editor/EditorFunc.jsx @@ -687,20 +687,13 @@ const EditorComponent = (props) => { await fetchDataQueries(data.editing_version?.id, true, true); const appDefData = buildAppDefinition(data); - // let dataDefinition = data.definition ?? defaults(data.definition, defaultDefinition(props.darkMode)); - - // const pages = Object.entries(dataDefinition.pages).map(([pageId, page]) => ({ id: pageId, ...page })); - // const startingPageId = pages.filter((page) => page.handle === startingPageHandle)[0]?.id; - // const homePageId = !startingPageHandle || startingPageId === 'null' ? dataDefinition.homePageId : startingPageId; - - // !------ const appJson = appDefData; const pages = data.pages; + const events = data.events; + const startingPageId = pages.filter((page) => page.handle === startingPageHandle)[0]?.id; const homePageId = !startingPageId || startingPageId === 'null' ? appJson.homePageId : startingPageId; - const currentComponents = appJson.pages[homePageId]?.components ?? {}; - console.log('---arpit [fetching app] [pages] ==> ', { currentComponents }); const currentpageData = { handle: appJson.pages[homePageId]?.handle, name: appJson.pages[homePageId]?.name, @@ -708,8 +701,6 @@ const EditorComponent = (props) => { variables: {}, }; - // !------ - setCurrentPageId(homePageId); updateState({ @@ -721,6 +712,7 @@ const EditorComponent = (props) => { appName: data?.name, userId: data?.user_id, appId: data?.id, + events: events, }); useCurrentStateStore.getState().actions.setCurrentState({ @@ -751,6 +743,7 @@ const EditorComponent = (props) => { // !-------- const setAppDefinitionFromVersion = (version, shouldWeEditVersion = true) => { if (version?.id !== props.editingVersion?.id) { + // !Need to fix this // appDefinitionChanged(defaults(version.definition, defaultDefinition(props.darkMode)), { // skipAutoSave: true, // skipYmapUpdate: true, @@ -992,7 +985,7 @@ const EditorComponent = (props) => { const diffPatches = diff(appDefinition, updatedAppDefinition); if (!isEmpty(diffPatches)) { - appDefinitionChanged(updatedAppDefinition, { skipAutoSave: true, componentDefinitionChanged: true }); + appDefinitionChanged(updatedAppDefinition, { skipAutoSave: true, componentDefinitionChanged: true, ...props }); } } @@ -1464,6 +1457,7 @@ const EditorComponent = (props) => { appDefinitionChanged(newAppDefinition, { pageDefinitionChanged: true, + pageEventsChanged: true, }); }; diff --git a/frontend/src/Editor/Inspector/EventManager.jsx b/frontend/src/Editor/Inspector/EventManager.jsx index 8e787d16be..449e5f70fa 100644 --- a/frontend/src/Editor/Inspector/EventManager.jsx +++ b/frontend/src/Editor/Inspector/EventManager.jsx @@ -217,7 +217,7 @@ export const EventManager = ({ alertType: 'info', }); setEvents(newEvents); - eventsChanged(newEvents); + eventsChanged(newEvents, false, true); } //following two are functions responsible for on change and value for the control specific actions diff --git a/frontend/src/Editor/Inspector/Inspector.jsx b/frontend/src/Editor/Inspector/Inspector.jsx index f45b3647f1..765f6fdd35 100644 --- a/frontend/src/Editor/Inspector/Inspector.jsx +++ b/frontend/src/Editor/Inspector/Inspector.jsx @@ -207,7 +207,7 @@ export const Inspector = ({ componentDefinitionChanged(newComponent, { eventUpdated: true }); } - function eventsChanged(newEvents, isReordered = false) { + function eventsChanged(newEvents, isReordered = false, isNew = false) { let newComponent = JSON.parse(JSON.stringify(component)); let newDefinition = JSON.parse(JSON.stringify(newComponent.component.definition)); @@ -215,7 +215,14 @@ export const Inspector = ({ newComponent.component.definition = newDefinition; - componentDefinitionChanged(newComponent, { eventsChanged: true }); + const opts = { + componentsEventsChanged: true, + }; + + if (isReordered) opts.eventsReOrdered = true; + if (isNew) opts.newEvent = true; + + componentDefinitionChanged(newComponent, opts); } function eventOptionUpdated(event, option, value) { diff --git a/frontend/src/_services/appVersion.service.js b/frontend/src/_services/appVersion.service.js index f96833bf05..d9e97f0ac0 100644 --- a/frontend/src/_services/appVersion.service.js +++ b/frontend/src/_services/appVersion.service.js @@ -74,6 +74,10 @@ function autoSaveApp(appId, versionId, diff, type, pageId, operation, isUserSwit global_settings: { update: { ...diff }, }, + events: { + update: diff, + create: diff, + }, }; const body = !type diff --git a/frontend/src/_stores/appDataStore.js b/frontend/src/_stores/appDataStore.js index a7f06dbe91..58ec1823b8 100644 --- a/frontend/src/_stores/appDataStore.js +++ b/frontend/src/_stores/appDataStore.js @@ -17,6 +17,7 @@ const initialState = { components: [], pages: [], layouts: [], + events: [], eventHandlers: [], appDefinitionDiff: null, appDiffOptions: {}, diff --git a/frontend/src/_stores/utils.js b/frontend/src/_stores/utils.js index a0769f6f5c..a9e8b6c0f5 100644 --- a/frontend/src/_stores/utils.js +++ b/frontend/src/_stores/utils.js @@ -41,43 +41,73 @@ const updateType = Object.freeze({ componentAdded: 'components', componentDefinitionChanged: 'components', componentDeleted: 'components', + componentsEventsChanged: 'events', + pageEventsChanged: 'events', +}); + +const eventHandlerType = Object.freeze({ + componentsEventsChanged: 'components', + pageEventsChanged: 'pages', }); export const computeAppDiff = (appDiff, currentPageId, opts) => { const { updateDiff, type, operation } = updateFor(appDiff, currentPageId, opts); + console.log('----arpit [updateFor]', { updateDiff, type, operation }); return { updateDiff, type, operation }; }; -// const updateFor = (appDiff, currentPageId, opts) => { -// const componentUpdates = ['componentAdded', 'componentDefinitionChanged', 'componentDeleted', 'containerChanges']; -// const pageUpdates = ['pageDefinitionChanged', 'pageSortingChanged', 'deletePageRequest', 'addNewPage']; -// const appUpdates = ['homePageChanged']; -// const globalSettings = ['globalSettings']; +function verifyIsEventUpdates(data, eventsObj) { + if (!data.pages || Object.keys(data.pages).length === 0) { + return false; + } -// const options = _.keys(opts); + for (const pageId in data.pages) { + const components = data.pages[pageId].components; + for (const componentId in components) { + if (components[componentId].component.definition.events) { + eventsObj.components = Object.values(components[componentId].component.definition.events).map((e) => ({ + event: e, + eventType: 'component', + attachedTo: componentId, + })); -// if (_.intersection(options, componentUpdates).length > 0) { -// return computeComponentDiff(appDiff, currentPageId, opts); -// } else if (_.intersection(options, pageUpdates).length > 0) { -// return computePageUpdate(appDiff, currentPageId, opts); -// } else if (_.intersection(options, appUpdates).length > 0) { -// return { -// updateDiff: appDiff, -// type: null, -// operation: 'update', -// }; -// } else if (_.intersection(options, globalSettings).length > 0) { -// return { -// updateDiff: appDiff, -// type: 'global_settings', -// operation: 'update', -// }; -// } -// }; + return true; + } + } + } + + return false; +} + +function computeEventDiff(appDiff, currentPageId, opts = []) { + let type = 'events'; + let updateDiff; + let operation = 'update'; + + const events = { + components: [], + pages: [], + }; + verifyIsEventUpdates(appDiff, events); + + updateDiff = events[eventHandlerType[opts[0]]]; + console.log('----arpit [computeEventDiff]', { events, opts }); + + if (opts.includes('newEvent')) { + operation = 'create'; + updateDiff = updateDiff[0]; + } + + return { updateDiff, type, operation }; +} const updateFor = (appDiff, currentPageId, opts) => { const updateTypeMappings = [ + { + updateTypes: ['componentsEventsChanged', 'pageEventsChanged', 'eventsReOrdered', 'newEvent'], + processingFunction: computeEventDiff, + }, { updateTypes: ['componentAdded', 'componentDefinitionChanged', 'componentDeleted', 'containerChanges'], processingFunction: computeComponentDiff, @@ -107,8 +137,10 @@ const updateFor = (appDiff, currentPageId, opts) => { const options = _.keys(opts); for (const { updateTypes, processingFunction } of updateTypeMappings) { - if (_.intersection(options, updateTypes).length > 0) { - return processingFunction(appDiff, currentPageId, opts); + const optionsTypes = _.intersection(options, updateTypes); + + if (optionsTypes.length > 0) { + return processingFunction(appDiff, currentPageId, optionsTypes); } } @@ -122,7 +154,7 @@ const computePageUpdate = (appDiff, currentPageId, opts) => { let updateDiff; let operation = 'update'; - if (opts?.deletePageRequest) { + if (opts.includes('deletePageRequest')) { const deletePageId = _.keys(appDiff?.pages).map((pageId) => { if (appDiff?.pages[pageId]?.pageId === undefined) { return pageId; @@ -135,16 +167,16 @@ const computePageUpdate = (appDiff, currentPageId, opts) => { type = updateType.pageDefinitionChanged; operation = 'delete'; - } else if (opts?.pageSortingChanged) { + } else if (opts.includes('pageSortingChanged')) { updateDiff = appDiff?.pages; type = updateType.pageDefinitionChanged; - } else if (opts?.pageDefinitionChanged) { + } else if (opts.includes('pageDefinitionChanged')) { updateDiff = appDiff?.pages[currentPageId]; type = updateType.pageDefinitionChanged; - if (opts?.addNewPage) { + if (opts.includes('addNewPage')) { operation = 'create'; } } @@ -157,7 +189,7 @@ const computeComponentDiff = (appDiff, currentPageId, opts) => { let updateDiff; let operation = 'update'; - if (opts?.componentDeleted) { + if (opts.includes('componentDeleted')) { const currentPageComponents = appDiff?.pages[currentPageId]?.components; updateDiff = _.keys(currentPageComponents); @@ -165,12 +197,15 @@ const computeComponentDiff = (appDiff, currentPageId, opts) => { type = updateType.componentDeleted; operation = 'delete'; - } else if ((opts?.containerChanges || opts?.componentDefinitionChanged) && !opts?.componentAdded) { + } else if ( + (opts.includes('containerChanges') || opts.includes('componentDefinitionChanged')) && + !opts.includes('componentAdded') + ) { const currentPageComponents = appDiff?.pages[currentPageId]?.components; updateDiff = currentPageComponents; - type = opts?.componentDefinitionChanged ? updateType.componentDefinitionChanged : updateType.containerChanges; - } else if (opts?.componentAdded) { + type = opts.includes('containerChanges') ? updateType.containerChanges : updateType.componentDefinitionChanged; + } else if (opts.includes('componentAdded')) { const currentPageComponents = appDiff?.pages[currentPageId]?.components; updateDiff = _.toPairs(currentPageComponents ?? []).reduce((result, [id, component]) => { diff --git a/server/migrations/1691004706564-CreateEventHandlerTable.ts b/server/migrations/1691004706564-CreateEventHandlerTable.ts index 78343cd7e2..56886ab6f6 100644 --- a/server/migrations/1691004706564-CreateEventHandlerTable.ts +++ b/server/migrations/1691004706564-CreateEventHandlerTable.ts @@ -17,6 +17,11 @@ export class CreateEventHandlerTable1691004706564 implements MigrationInterface type: 'varchar', isNullable: false, }, + { + name: 'event', + type: 'jsonb', + isNullable: false, + }, { name: 'app_version_id', type: 'uuid', diff --git a/server/src/controllers/apps.controller.ts b/server/src/controllers/apps.controller.ts index af89025946..89b2bb418f 100644 --- a/server/src/controllers/apps.controller.ts +++ b/server/src/controllers/apps.controller.ts @@ -29,6 +29,7 @@ import { ValidAppInterceptor } from 'src/interceptors/valid.app.interceptor'; import { AppDecorator } from 'src/decorators/app.decorator'; import { PageService } from '@services/page.service'; +import { EventsService } from '@services/events_handler.service'; @Controller('apps') export class AppsController { @@ -36,6 +37,7 @@ export class AppsController { private appsService: AppsService, private foldersService: FoldersService, private pageService: PageService, + private eventsService: EventsService, private appsAbilityFactory: AppsAbilityFactory ) {} @@ -89,6 +91,9 @@ export class AppsController { : []; const pagesForVersion = app.editingVersion ? await this.pageService.findPagesForVersion(app.editingVersion.id) : []; + const eventsForVersion = app.editingVersion + ? await this.eventsService.findEventsForVersion(app.editingVersion.id) + : []; // serialize queries for (const query of dataQueriesForVersion) { @@ -100,6 +105,7 @@ export class AppsController { response['data_queries'] = seralizedQueries; response['definition'] = app.editingVersion?.definition; response['pages'] = pagesForVersion; + response['events'] = eventsForVersion; //! if editing version exists, camelize the definition if (app.editingVersion && app.editingVersion.definition) { diff --git a/server/src/controllers/apps.controller.v2.ts b/server/src/controllers/apps.controller.v2.ts index a7f19770c0..d3d49843a6 100644 --- a/server/src/controllers/apps.controller.v2.ts +++ b/server/src/controllers/apps.controller.v2.ts @@ -28,6 +28,7 @@ import { AppDecorator } from 'src/decorators/app.decorator'; import { ComponentsService } from '@services/components.service'; import { PageService } from '@services/page.service'; +import { EventsService } from '@services/events_handler.service'; import { AppVersionUpdateDto } from '@dto/app-version-update.dto'; @Controller({ @@ -39,6 +40,7 @@ export class AppsControllerV2 { private appsService: AppsService, private componentsService: ComponentsService, private pageService: PageService, + private eventService: EventsService, private appsAbilityFactory: AppsAbilityFactory ) {} @@ -307,4 +309,42 @@ export class AppsControllerV2 { await this.pageService.deletePage(pageId, versionId); } } + + // events api + @UseGuards(JwtAuthGuard) + @UseInterceptors(ValidAppInterceptor) + @Post(':id/versions/:versionId/events') + async createEvent(@User() user, @Param('id') id, @Param('versionId') versionId, @Body() body) { + const version = await this.appsService.findVersion(versionId); + const app = version.app; + + if (app.id !== id) { + throw new BadRequestException(); + } + const ability = await this.appsAbilityFactory.appsActions(user, id); + + if (!ability.can('updateVersions', app)) { + throw new ForbiddenException('You do not have permissions to perform this action'); + } + + return this.eventService.createEvent(body, versionId); + } + @UseGuards(JwtAuthGuard) + @UseInterceptors(ValidAppInterceptor) + @Put(':id/versions/:versionId/events') + async updateEvents(@User() user, @Param('id') id, @Param('versionId') versionId, @Body() body) { + const version = await this.appsService.findVersion(versionId); + const app = version.app; + + if (app.id !== id) { + throw new BadRequestException(); + } + const ability = await this.appsAbilityFactory.appsActions(user, id); + + if (!ability.can('updateVersions', app)) { + throw new ForbiddenException('You do not have permissions to perform this action'); + } + + return await this.eventService.updateEvent(body, versionId); + } } diff --git a/server/src/entities/event_handler.entity.ts b/server/src/entities/event_handler.entity.ts index 944ce9882e..ba24e06592 100644 --- a/server/src/entities/event_handler.entity.ts +++ b/server/src/entities/event_handler.entity.ts @@ -1,4 +1,4 @@ -import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm'; +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm'; import { AppVersion } from './app_version.entity'; enum Target { @@ -15,8 +15,8 @@ export class EventHandler { @Column({ name: 'name' }) name: string; - @Column({ name: 'app_version_id' }) - AppVersionId: string; + @Column('simple-json') + event: any; @Column({ name: 'source_id' }) sourceId: string; @@ -24,6 +24,10 @@ export class EventHandler { @Column({ name: 'target' }) target: Target; - @ManyToOne(() => AppVersion, (appVersion) => appVersion.eventHandlers) + @Column({ name: 'app_version_id' }) + appVersionId: string; + + @ManyToOne(() => AppVersion, (appVersion) => appVersion.pages) + @JoinColumn({ name: 'app_version_id' }) appVersion: AppVersion; } diff --git a/server/src/modules/apps/apps.module.ts b/server/src/modules/apps/apps.module.ts index 695a52d25b..551d81de68 100644 --- a/server/src/modules/apps/apps.module.ts +++ b/server/src/modules/apps/apps.module.ts @@ -41,6 +41,7 @@ import { Layout } from 'src/entities/layout.entity'; import { ComponentsService } from '@services/components.service'; import { PageService } from '@services/page.service'; +import { EventsService } from '@services/events_handler.service'; @Module({ imports: [ @@ -83,6 +84,7 @@ import { PageService } from '@services/page.service'; AppEnvironmentService, ComponentsService, PageService, + EventsService, ], controllers: [AppsController, AppsControllerV2, AppUsersController, AppsImportExportController], }) diff --git a/server/src/services/events_handler.service.ts b/server/src/services/events_handler.service.ts new file mode 100644 index 0000000000..1af6250dcb --- /dev/null +++ b/server/src/services/events_handler.service.ts @@ -0,0 +1,67 @@ +import { BadRequestException, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { EntityManager, Repository } from 'typeorm'; +import { Component } from 'src/entities/component.entity'; +// import { Page } from 'src/entities/page.entity'; +import { EventHandler } from 'src/entities/event_handler.entity'; +import { dbTransactionWrap } from 'src/helpers/utils.helper'; + +@Injectable() +export class EventsService { + constructor( + @InjectRepository(Component) + private eventsRepository: Repository + ) {} + + async findEventsForVersion(appVersionId: string): Promise { + return dbTransactionWrap(async (manager: EntityManager) => { + const allEvents = await manager.find(EventHandler, { + where: { appVersionId }, + }); + return allEvents; + }); + } + + async createEvent(options, versionId) { + if (Object.keys(options).length === 0) { + return new BadRequestException('No event found'); + } + + const newEvent = { + name: options.event.eventId, + sourceId: options.attachedTo, + target: options.eventType, + event: options.event, + appVersionId: versionId, + }; + + console.log('---arpit || create events', { newEvent }); + + return await dbTransactionWrap(async (manager: EntityManager) => { + const event = await manager.save(EventHandler, newEvent); + return event; + }); + } + + async updateEvent(options = [], versionId: string) { + const eventHandlers = []; + + options.forEach((option) => { + eventHandlers.push({ + event: option.event, + name: option.event.eventId, + sourceId: option.attachedTo, + target: option.eventType, + }); + }); + + console.log('---arpit || createOrUpdateEvent', { eventHandlers }); + + return { + status: 'success', + data: eventHandlers, + }; + } + + // utitlity functions +}