From b132098c4ca87c72b797a4d3cb5434130e91e090 Mon Sep 17 00:00:00 2001 From: arpitnath Date: Thu, 10 Aug 2023 17:01:03 +0530 Subject: [PATCH] moved out pages from app definition --- frontend/src/Editor/Container.jsx | 16 +++++- frontend/src/Editor/EditorFunc.jsx | 28 ++++++---- frontend/src/_services/appVersion.service.js | 3 +- frontend/src/_stores/appDataStore.js | 11 +++- frontend/src/_stores/utils.js | 55 +++++++++++++++++++ ...> 1691004576222-UpdateAppVersionEntity.ts} | 2 +- .../1691004576333-CreatePageTable.ts | 15 ++--- server/src/controllers/apps.controller.ts | 16 ++++++ server/src/dto/version-edit.dto.ts | 3 + server/src/entities/component.entity.ts | 5 +- server/src/entities/page.entity.ts | 14 ++--- server/src/modules/apps/apps.module.ts | 3 + server/src/services/apps.service.ts | 34 +++++++++++- server/src/services/components.service.ts | 20 +++++++ 14 files changed, 189 insertions(+), 36 deletions(-) rename server/migrations/{1691006886775-UpdateAppVersionEntity.ts => 1691004576222-UpdateAppVersionEntity.ts} (94%) create mode 100644 server/src/services/components.service.ts diff --git a/frontend/src/Editor/Container.jsx b/frontend/src/Editor/Container.jsx index c4a1bce38b..74c812d46e 100644 --- a/frontend/src/Editor/Container.jsx +++ b/frontend/src/Editor/Container.jsx @@ -84,6 +84,7 @@ export const Container = ({ const [newThread, addNewThread] = useState({}); const [isContainerFocused, setContainerFocus] = useState(false); const [canvasHeight, setCanvasHeight] = useState(null); + const router = useRouter(); const canvasRef = useRef(null); const focusedParentIdRef = useRef(undefined); @@ -181,7 +182,20 @@ export const Container = ({ }, }; - appDefinitionChanged(newDefinition, { containerChanges: true }); + //need to check if a new component is added or deleted + + const oldComponents = appDefinition.pages[currentPageId]?.components ?? {}; + const newComponents = boxes; + + const componendAdded = Object.keys(newComponents).length > Object.keys(oldComponents).length; + + const opts = { containerChanges: true }; + + if (componendAdded) { + opts.componentAdded = true; + } + + appDefinitionChanged(newDefinition, opts); // eslint-disable-next-line react-hooks/exhaustive-deps }, [boxes]); diff --git a/frontend/src/Editor/EditorFunc.jsx b/frontend/src/Editor/EditorFunc.jsx index 22b0aab833..2e2e058c49 100644 --- a/frontend/src/Editor/EditorFunc.jsx +++ b/frontend/src/Editor/EditorFunc.jsx @@ -58,7 +58,7 @@ import { useDataQueries, useDataQueriesStore } from '@/_stores/dataQueriesStore' import { useAppVersionStore } from '@/_stores/appVersionStore'; import { useQueryPanelStore } from '@/_stores/queryPanelStore'; import { useCurrentStateStore, useCurrentState } from '@/_stores/currentStateStore'; -import { resetAllStores } from '@/_stores/utils'; +import { computeAppDiff, resetAllStores } from '@/_stores/utils'; import { setCookie } from '@/_helpers/cookie'; import { shallow } from 'zustand/shallow'; import { useEditorActions, useEditorState, useEditorStore } from '@/_stores/editorStore'; @@ -105,7 +105,7 @@ const EditorComponent = (props) => { const { socket } = createWebsocketConnection(props?.params?.id); const mounted = useMounted(); - const { updateState, updateAppDefinitionDiff } = useAppDataActions(); + const { updateState, updateAppDefinitionDiff, updateAppVersion } = useAppDataActions(); const { updateEditorState, updateQueryConfirmationList } = useEditorActions(); const { noOfVersionsSupported, @@ -127,7 +127,8 @@ const EditorComponent = (props) => { const dataQueries = useDataQueries(); - const { isMaintenanceOn, appId, app, currentUser, currentVersionId, appDefinitionDiff } = useAppInfo(); + const { isMaintenanceOn, appId, app, currentUser, currentVersionId, appDefinitionDiff, appDiffOptions } = + useAppInfo(); const [currentPageId, setCurrentPageId] = useState(null); const [zoomLevel, setZoomLevel] = useState(1); @@ -213,6 +214,7 @@ const EditorComponent = (props) => { if (currentUser?.current_organization_id) { fetchGlobalDataSources(); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(currentUser?.current_organization_id)]); // Handle appDefinition updates @@ -630,6 +632,7 @@ const EditorComponent = (props) => { }; //!-------- + const fetchApp = async (startingPageHandle) => { const _appId = props?.params?.id; @@ -638,7 +641,7 @@ const EditorComponent = (props) => { useAppVersionStore.getState().actions.updateReleasedVersionId(data.current_version_id); await fetchDataSources(data.editing_version?.id); await fetchDataQueries(data.editing_version?.id, true, true); - + console.log('---piku [fetching app]---]', { data, defaultDef: defaultDefinition(props.darkMode) }); let dataDefinition = data.definition ?? defaults(data.definition, defaultDefinition(props.darkMode)); const pages = Object.entries(dataDefinition.pages).map(([pageId, page]) => ({ id: pageId, ...page })); @@ -774,6 +777,7 @@ const EditorComponent = (props) => { }); const diffPatches = diff(appDefinition, updatedAppDefinition); + const inversePatches = diff(updatedAppDefinition, appDefinition); const shouldUpdate = !_.isEmpty(diffPatches) && !isEqual(appDefinitionDiff, diffPatches); @@ -784,6 +788,9 @@ const EditorComponent = (props) => { setUndoStack((prev) => [...prev, undoPatches]); updateAppDefinitionDiff(diffPatches); + updateState({ + appDiffOptions: opts, + }); updateEditorState({ isSaving: true, appDefinition: updatedAppDefinition, @@ -804,13 +811,11 @@ const EditorComponent = (props) => { isSaving: false, }); } else if (!isEmpty(props?.editingVersion)) { - appVersionService - .save( - appId, - props.editingVersion?.id, - { definition: appDefinition, diff: appDefinitionDiff }, - isUserSwitchedVersion - ) + const componentDiff = computeAppDiff(appDefinitionDiff, currentPageId, appDiffOptions); + + // console.log('---piku [componentDiff]--', componentDiff); + + updateAppVersion(appId, props.editingVersion?.id, appDefinition, componentDiff, isUserSwitchedVersion) .then(() => { const _editingVersion = { ...props.editingVersion, @@ -918,6 +923,7 @@ const EditorComponent = (props) => { }, [JSON.stringify(undoStack), JSON.stringify(redoStack)]); const componentDefinitionChanged = (componentDefinition, props) => { + console.log('---arpit checking:::: ', { props }); if (props?.isVersionReleased) { useAppVersionStore.getState().actions.enableReleasedVersionPopupState(); return; diff --git a/frontend/src/_services/appVersion.service.js b/frontend/src/_services/appVersion.service.js index fd73304379..26c6893851 100644 --- a/frontend/src/_services/appVersion.service.js +++ b/frontend/src/_services/appVersion.service.js @@ -44,11 +44,12 @@ function del(appId, versionId) { } function save(appId, versionId, values, isUserSwitchedVersion = false) { + console.log('---piku [version saved]', { values }); const body = { is_user_switched_version: isUserSwitchedVersion }; if (values.definition) body['definition'] = values.definition; if (values.name) body['name'] = values.name; + if (values.diff) body['app_diff'] = values.diff; - console.log('---arpit [app version service]', { values }); const requestOptions = { method: 'PUT', headers: authHeader(), diff --git a/frontend/src/_stores/appDataStore.js b/frontend/src/_stores/appDataStore.js index 5fa8a424ab..716672c656 100644 --- a/frontend/src/_stores/appDataStore.js +++ b/frontend/src/_stores/appDataStore.js @@ -1,4 +1,4 @@ -import { shallow } from 'zustand/shallow'; +import { appVersionService } from '@/_services'; import { create, zustandDevTools } from './utils'; const initialState = { @@ -19,6 +19,7 @@ const initialState = { layouts: [], eventHandlers: [], appDefinitionDiff: null, + appDiffOptions: {}, }; export const useAppDataStore = create( @@ -30,6 +31,14 @@ export const useAppDataStore = create( updateApps: (apps) => set(() => ({ apps: apps })), updateState: (state) => set((prev) => ({ ...prev, ...state })), updateAppDefinitionDiff: (appDefinitionDiff) => set(() => ({ appDefinitionDiff: appDefinitionDiff })), + updateAppVersion: async (appId, versionId, appDefinition, appDefinitionDiff, isUserSwitchedVersion = false) => { + return await appVersionService.save( + appId, + versionId, + { definition: appDefinition, diff: appDefinitionDiff }, + isUserSwitchedVersion + ); + }, }, }), { name: 'App Data Store' } diff --git a/frontend/src/_stores/utils.js b/frontend/src/_stores/utils.js index 8b883b726d..0d76eecfdf 100644 --- a/frontend/src/_stores/utils.js +++ b/frontend/src/_stores/utils.js @@ -1,5 +1,9 @@ import { create as _create } from 'zustand'; import { devtools } from 'zustand/middleware'; +// eslint-disable-next-line import/no-unresolved +import { diff } from 'deep-object-diff'; +import { componentTypes } from '@/Editor/WidgetManager/components'; +import _ from 'lodash'; export const zustandDevTools = (fn, options = {}) => devtools(fn, { ...options, enabled: process.env.NODE_ENV === 'production' ? false : true }); @@ -21,3 +25,54 @@ export const resetAllStores = () => { resetter(); } }; + +const defaultComponent = { + name: '', + properties: {}, + styles: {}, + validation: {}, + events: [], +}; + +const updateType = Object.freeze({ + pageDefinitionChanged: 'pages', + containerChanges: 'layout', + componentAdded: 'components', + componentDefinitionChanged: 'components', +}); + +export const computeAppDiff = (appDiff, currentPageId, opts) => { + let type; + let updateDiff; + + if (opts?.pageDefinitionChanged) { + updateDiff = appDiff?.pages[currentPageId]; + + type = updateType.pageDefinitionChanged; + } else if ((opts?.containerChanges || opts?.componentDefinitionChanged) && !opts?.componentAdded) { + const currentPageComponents = appDiff?.pages[currentPageId]?.components; + + updateDiff = currentPageComponents; + type = opts?.componentDefinitionChanged ? updateType.componentDefinitionChanged : updateType.containerChanges; + } else if (opts?.componentAdded) { + const currentPageComponents = appDiff?.pages[currentPageId]?.components; + + updateDiff = _.toPairs(currentPageComponents ?? []).reduce((result, [id, component]) => { + const componentMeta = componentTypes.find((comp) => comp.component === component.component.component); + + const metaDiff = diff(componentMeta, component.component); + + result[id] = _.defaultsDeep(metaDiff, defaultComponent); + + return result; + // result[id].componentId = id; + // return { ..._.defaultsDeep(metaDiff, defaultComponent), componentId: id }; + }, {}); + + type = updateType.componentDefinitionChanged; + } + + console.log('---piku [currentPageComponents]', { updateDiff, opts, type }); + + return { updateDiff, type }; +}; diff --git a/server/migrations/1691006886775-UpdateAppVersionEntity.ts b/server/migrations/1691004576222-UpdateAppVersionEntity.ts similarity index 94% rename from server/migrations/1691006886775-UpdateAppVersionEntity.ts rename to server/migrations/1691004576222-UpdateAppVersionEntity.ts index 91dc1162f0..c1e7fac828 100644 --- a/server/migrations/1691006886775-UpdateAppVersionEntity.ts +++ b/server/migrations/1691004576222-UpdateAppVersionEntity.ts @@ -1,6 +1,6 @@ import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'; -export class UpdateAppVersionEntity1691006886775 implements MigrationInterface { +export class UpdateAppVersionEntity1691006886222 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { // Add the new columns to the app_versions table await queryRunner.addColumn( diff --git a/server/migrations/1691004576333-CreatePageTable.ts b/server/migrations/1691004576333-CreatePageTable.ts index 73a42c0c9e..98694a5e34 100644 --- a/server/migrations/1691004576333-CreatePageTable.ts +++ b/server/migrations/1691004576333-CreatePageTable.ts @@ -17,21 +17,16 @@ export class CreatePageTable1691004576333 implements MigrationInterface { type: 'varchar', isNullable: false, }, + { + name: 'page_handle', + type: 'varchar', + isNullable: false, + }, { name: 'app_version_id', type: 'uuid', isNullable: false, }, - { - name: 'update_id', - type: 'varchar', - isNullable: false, - }, - { - name: 'last_hashed_diff', - type: 'varchar', - isNullable: false, - }, ], }) ); diff --git a/server/src/controllers/apps.controller.ts b/server/src/controllers/apps.controller.ts index dc9a00d34b..022253f8e9 100644 --- a/server/src/controllers/apps.controller.ts +++ b/server/src/controllers/apps.controller.ts @@ -28,11 +28,14 @@ import { EntityManager } from 'typeorm'; import { ValidAppInterceptor } from 'src/interceptors/valid.app.interceptor'; import { AppDecorator } from 'src/decorators/app.decorator'; +import { ComponentsService } from '@services/components.service'; + @Controller('apps') export class AppsController { constructor( private appsService: AppsService, private foldersService: FoldersService, + private componentsService: ComponentsService, private appsAbilityFactory: AppsAbilityFactory ) {} @@ -79,11 +82,16 @@ export class AppsController { } const response = decamelizeKeys(app); + const seralizedQueries = []; const dataQueriesForVersion = app.editingVersion ? await this.appsService.findDataQueriesForVersion(app.editingVersion.id) : []; + const pagesForVersion = app.editingVersion ? await this.appsService.findPagesForVersion(app.editingVersion.id) : []; + + console.log('------arpit [pagesforVersion]', { pagesForVersion }); + // serialize queries for (const query of dataQueriesForVersion) { const decamelizedQuery = decamelizeKeys(query); @@ -93,6 +101,7 @@ export class AppsController { response['data_queries'] = seralizedQueries; response['definition'] = app.editingVersion?.definition; + response['pages'] = decamelizeKeys(pagesForVersion); //! if editing version exists, camelize the definition if (app.editingVersion && app.editingVersion.definition) { @@ -311,6 +320,13 @@ export class AppsController { throw new ForbiddenException('You do not have permissions to perform this action'); } + const updateType = versionEditDto.app_diff?.type; + console.log('----arpit apps controller => ', { updateType }); + + // if(updateType=== 'components') { + // await this.componentsService.createOrUpdate(versionEditDto.app_diff.components, versionId) + // } + await this.appsService.updateVersion(version, versionEditDto, app.organizationId); return; } diff --git a/server/src/dto/version-edit.dto.ts b/server/src/dto/version-edit.dto.ts index 5bbdf2a90c..305055af69 100644 --- a/server/src/dto/version-edit.dto.ts +++ b/server/src/dto/version-edit.dto.ts @@ -20,4 +20,7 @@ export class VersionEditDto { @IsOptional() @IsBoolean() is_user_switched_version: boolean; + + @IsOptional() + app_diff: any; } diff --git a/server/src/entities/component.entity.ts b/server/src/entities/component.entity.ts index 68649090f4..ec01f4baa5 100644 --- a/server/src/entities/component.entity.ts +++ b/server/src/entities/component.entity.ts @@ -1,4 +1,4 @@ -import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany } from 'typeorm'; +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, JoinColumn } from 'typeorm'; import { Page } from './page.entity'; import { Layout } from './layout.entity'; @@ -11,7 +11,7 @@ export class Component { name: string; @Column({ name: 'page_id' }) - PageId: string; + pageId: string; @Column('simple-json') properties: any; @@ -23,6 +23,7 @@ export class Component { validations: any; @ManyToOne(() => Page, (page) => page.components) + @JoinColumn({ name: 'page_id' }) page: Page; @OneToMany(() => Layout, (layout) => layout.component) diff --git a/server/src/entities/page.entity.ts b/server/src/entities/page.entity.ts index 0260851136..247ae1178a 100644 --- a/server/src/entities/page.entity.ts +++ b/server/src/entities/page.entity.ts @@ -1,4 +1,4 @@ -import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany } from 'typeorm'; +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, JoinColumn } from 'typeorm'; import { AppVersion } from './app_version.entity'; import { Component } from './component.entity'; @@ -10,16 +10,14 @@ export class Page { @Column() name: string; - @Column() - AppVersionId: string; + @Column({ name: 'page_handle' }) + pageHandle: string; - @Column() - updateId: string; - - @Column() - lastHashedDiff: string; + @Column({ name: 'app_version_id' }) + appVersionId: string; @ManyToOne(() => AppVersion, (appVersion) => appVersion.pages) + @JoinColumn({ name: 'app_version_id' }) appVersion: AppVersion; @OneToMany(() => Component, (component) => component.page) diff --git a/server/src/modules/apps/apps.module.ts b/server/src/modules/apps/apps.module.ts index a197ec5088..f89d7a0dde 100644 --- a/server/src/modules/apps/apps.module.ts +++ b/server/src/modules/apps/apps.module.ts @@ -38,6 +38,8 @@ import { Page } from 'src/entities/page.entity'; import { EventHandler } from 'src/entities/event_handler.entity'; import { Layout } from 'src/entities/layout.entity'; +import { ComponentsService } from '@services/components.service'; + @Module({ imports: [ TypeOrmModule.forFeature([ @@ -77,6 +79,7 @@ import { Layout } from 'src/entities/layout.entity'; PluginsService, PluginsHelper, AppEnvironmentService, + ComponentsService, ], controllers: [AppsController, AppUsersController, AppsImportExportController], }) diff --git a/server/src/services/apps.service.ts b/server/src/services/apps.service.ts index a3f8c68b3f..09193c2caf 100644 --- a/server/src/services/apps.service.ts +++ b/server/src/services/apps.service.ts @@ -28,6 +28,7 @@ import { AppEnvironmentService } from './app_environments.service'; import { decode } from 'js-base64'; import { DataSourceScopes } from 'src/helpers/data_source.constants'; import { DataBaseConstraints } from 'src/helpers/db_constraints.constants'; +import { Page } from 'src/entities/page.entity'; @Injectable() export class AppsService { @@ -117,7 +118,29 @@ export class AppsService { ); //create default app version - await this.createVersion(user, app, 'v1', null, null, manager); + const appVersion = await this.createVersion(user, app, 'v1', null, null, manager); + + const defaultHomePage = await manager.save( + manager.create(Page, { + name: 'Home', + pageHandle: 'home', + appVersionId: appVersion.id, + }) + ); + + // Set default values for app version + appVersion.showViewerNavigation = true; + appVersion.homePageId = defaultHomePage.id; + appVersion.globalSettings = { + hideHeader: false, + appInMaintenance: false, + canvasMaxWidth: 1292, + canvasMaxWidthType: 'px', + canvasMaxHeight: 2400, + canvasBackgroundColor: '#edeff5', + backgroundFxQuery: '', + }; + await manager.save(appVersion); await manager.save( manager.create(AppUser, { @@ -363,6 +386,15 @@ export class AppsService { }); } + async findPagesForVersion(appVersionId: string): Promise { + return await dbTransactionWrap(async (manager: EntityManager) => { + return manager + .createQueryBuilder(Page, 'pages') + .where('pages.appVersionId = :appVersionId', { appVersionId }) // Replace 'AppVersionId' with the actual column name + .getMany(); + }); + } + async createNewDataSourcesAndQueriesForVersion( manager: EntityManager, appVersion: AppVersion, diff --git a/server/src/services/components.service.ts b/server/src/services/components.service.ts new file mode 100644 index 0000000000..d25bbb8cb6 --- /dev/null +++ b/server/src/services/components.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Component } from 'src/entities/component.entity'; // Adjust the import path as per your project structure + +@Injectable() +export class ComponentsService { + constructor( + @InjectRepository(Component) + private componentsRepository: Repository + ) {} + + async findOne(id: string): Promise { + return this.componentsRepository.findOne(id); + } + + async createOrUpdate(componentDiff: any) { + console.log('----arpit:::: component service', { componentDiff }); + } +}