mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 00:48:25 +00:00
events api and computing events diff from client
This commit is contained in:
parent
49681f05bd
commit
5e4fa096ad
12 changed files with 218 additions and 53 deletions
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const initialState = {
|
|||
components: [],
|
||||
pages: [],
|
||||
layouts: [],
|
||||
events: [],
|
||||
eventHandlers: [],
|
||||
appDefinitionDiff: null,
|
||||
appDiffOptions: {},
|
||||
|
|
|
|||
|
|
@ -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]) => {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
})
|
||||
|
|
|
|||
67
server/src/services/events_handler.service.ts
Normal file
67
server/src/services/events_handler.service.ts
Normal file
|
|
@ -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<EventHandler>
|
||||
) {}
|
||||
|
||||
async findEventsForVersion(appVersionId: string): Promise<EventHandler[]> {
|
||||
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
|
||||
}
|
||||
Loading…
Reference in a new issue