diff --git a/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx b/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx index 3ac7048900..2801f278b0 100644 --- a/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx +++ b/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx @@ -58,7 +58,7 @@ export const CreateVersion = ({ }); appVersionService - .getOne(appId, data.id) + .getAppVersionData(appId, data.id) .then((data) => { setAppDefinitionFromVersion(data); }) diff --git a/frontend/src/Editor/Container.jsx b/frontend/src/Editor/Container.jsx index 3eedb75b96..756b371d98 100644 --- a/frontend/src/Editor/Container.jsx +++ b/frontend/src/Editor/Container.jsx @@ -484,17 +484,17 @@ export const Container = ({ const paramUpdated = useCallback( (id, param, value) => { - if (Object.keys(value).length > 0) { + if (Object.keys(value)?.length > 0) { setBoxes((boxes) => update(boxes, { [id]: { $merge: { component: { - ...boxes[id].component, + ...boxes[id]?.component, definition: { - ...boxes[id].component.definition, + ...boxes[id]?.component?.definition, properties: { - ...boxes[id].component.definition.properties, + ...boxes?.[id]?.component?.definition?.properties, [param]: value, }, }, @@ -505,7 +505,7 @@ export const Container = ({ ); } }, - [setBoxes] + [boxes, setBoxes] ); const handleAddThread = async (e) => { diff --git a/frontend/src/Editor/EditorFunc.jsx b/frontend/src/Editor/EditorFunc.jsx index e5b7713060..ef57fd09de 100644 --- a/frontend/src/Editor/EditorFunc.jsx +++ b/frontend/src/Editor/EditorFunc.jsx @@ -55,11 +55,11 @@ import { useDataSourcesStore } from '@/_stores/dataSourcesStore'; import { useDataQueries, useDataQueriesStore } from '@/_stores/dataQueriesStore'; import { useAppVersionStore, useAppVersionActions, useAppVersionState } from '@/_stores/appVersionStore'; import { useQueryPanelStore } from '@/_stores/queryPanelStore'; -import { useCurrentStateStore, useCurrentState } from '@/_stores/currentStateStore'; +import { useCurrentStateStore, useCurrentState, getCurrentState } from '@/_stores/currentStateStore'; import { computeAppDiff, computeComponentPropertyDiff, isParamFromTableColumn, resetAllStores } from '@/_stores/utils'; import { setCookie } from '@/_helpers/cookie'; import { useEditorActions, useEditorState, useEditorStore } from '@/_stores/editorStore'; -import { useAppDataActions, useAppInfo } from '@/_stores/appDataStore'; +import { useAppDataActions, useAppInfo, useAppDataStore } from '@/_stores/appDataStore'; import { useMounted } from '@/_hooks/use-mount'; // eslint-disable-next-line import/no-unresolved import { diff } from 'deep-object-diff'; @@ -81,7 +81,8 @@ const EditorComponent = (props) => { const { updateState, updateAppDefinitionDiff, updateAppVersion, setIsSaving, createAppVersionEventHandlers } = useAppDataActions(); - const { updateEditorState, updateQueryConfirmationList, setSelectedComponents } = useEditorActions(); + const { updateEditorState, updateQueryConfirmationList, setSelectedComponents, setCurrentPageId } = + useEditorActions(); const { setAppVersions } = useAppVersionActions(); const { isVersionReleased, editingVersion, releasedVersionId } = useAppVersionState(); @@ -101,6 +102,7 @@ const EditorComponent = (props) => { showComments, showLeftSidebar, queryConfirmationList, + currentPageId, } = useEditorState(); const dataQueries = useDataQueries(); @@ -121,7 +123,6 @@ const EditorComponent = (props) => { const currentState = useCurrentState(); - const [currentPageId, setCurrentPageId] = useState(null); const [zoomLevel, setZoomLevel] = useState(1); const [isQueryPaneDragging, setIsQueryPaneDragging] = useState(false); const [isQueryPaneExpanded, setIsQueryPaneExpanded] = useState(false); //!check where this is used @@ -179,11 +180,16 @@ const EditorComponent = (props) => { updateState({ currentUser: appUserDetails, }); - useCurrentStateStore.getState().actions.setCurrentState({ globals: { ...currentState.globals, + theme: { name: props?.darkMode ? 'dark' : 'light' }, + urlparams: JSON.parse(JSON.stringify(queryString.parse(props.location.search))), currentUser: userVars, + /* Constant value.it will only change for viewer */ + mode: { + value: 'edit', + }, }, }); } @@ -251,7 +257,7 @@ const EditorComponent = (props) => { canvasContainerRef.current.scrollLeft += editorMarginLeft; } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [editorMarginLeft]); + }, [editorMarginLeft, canvasContainerRef?.current]); useEffect(() => { if (mounted) { @@ -270,6 +276,18 @@ const EditorComponent = (props) => { } }; + const getEditorRef = () => { + const editorRef = { + appDefinition: useEditorStore.getState().appDefinition, + queryConfirmationList: useEditorStore.getState().queryConfirmationList, + updateQueryConfirmationList: updateQueryConfirmationList, + navigate: props.navigate, + switchPage: switchPage, + currentPageId: useEditorStore.getState().currentPageId, + }; + return editorRef; + }; + const fetchApps = async (page) => { const { apps } = await appService.getAll(page); @@ -380,22 +398,15 @@ const EditorComponent = (props) => { threshold: 0, }, }); - - const globals = { - ...currentState.globals, - theme: { name: props?.darkMode ? 'dark' : 'light' }, - urlparams: JSON.parse(JSON.stringify(queryString.parse(props.location.search))), - }; - updateState({ appId: props?.params?.id }); - useCurrentStateStore.getState().actions.setCurrentState({ globals }); - getCanvasWidth(); initEditorWalkThrough(); }; const fetchDataQueries = async (id, selectFirstQuery = false, runQueriesOnAppLoad = false) => { - await useDataQueriesStore.getState().actions.fetchDataQueries(id, selectFirstQuery, runQueriesOnAppLoad); + await useDataQueriesStore + .getState() + .actions.fetchDataQueries(id, selectFirstQuery, runQueriesOnAppLoad, getEditorRef()); }; const fetchDataSources = (id) => { @@ -532,7 +543,7 @@ const EditorComponent = (props) => { }; const handleEvent = (eventName, event, options) => { - return onEvent(editorRef, eventName, event, options, 'edit'); + return onEvent(getEditorRef(), eventName, event, options, 'edit'); }; const handleRunQuery = (queryId, queryName) => runQuery(editorRef, queryId, queryName); @@ -663,11 +674,12 @@ const EditorComponent = (props) => { const appVersions = await appEnvironmentService.getVersionsByEnvironment(data?.id); setAppVersions(appVersions.appVersions); + const currentOrgId = data?.organization_id || data?.organizationId; updateState({ slug: data.slug, isMaintenanceOn: data?.is_maintenance_on, - organizationId: data?.organization_id, + organizationId: currentOrgId, isPublic: data?.is_public, appName: data?.name, userId: data?.user_id, @@ -709,7 +721,7 @@ const EditorComponent = (props) => { await fetchDataQueries(data.editing_version?.id, true, true); const currentPageEvents = data.events.filter((event) => event.target === 'page' && event.sourceId === homePageId); - await handleEvent('onPageLoad', currentPageEvents); + await handleEvent('onPageLoad', currentPageEvents, {}, true); }; const fetchApp = async (startingPageHandle, onMount = false) => { @@ -995,8 +1007,22 @@ const EditorComponent = (props) => { setUndoStack((prev) => prev.slice(0, prev.length - 1)); setRedoStack((prev) => [...prev, diffToPatches(_diffPatches)]); + let undoOpts = optsStack.undo[optsStack.undo.length - 1]; + + if (undoOpts?.componentDeleted) { + undoOpts = { + componentAdded: true, + }; + } + + if (undoOpts?.componentAdded) { + undoOpts = { + componentDeleted: true, + }; + } + updateState({ - appDiffOptions: optsStack.undo[optsStack.undo.length - 1], + appDiffOptions: undoOpts, }); setOptsStack((prev) => ({ @@ -1270,10 +1296,17 @@ const EditorComponent = (props) => { switchPage: true, pageId: newPageId, }); + props?.navigate(`/${getWorkspaceId()}/apps/${appId}/${newHandle}`); }; const switchPage = (pageId, queryParams = []) => { - if (currentPageId === pageId && currentState.page.handle === appDefinition?.pages[pageId]?.handle) { + // This are fetched from store to handle runQueriesOnAppLoad + const currentPageId = useEditorStore.getState().currentPageId; + const appDefinition = useEditorStore.getState().appDefinition; + const appId = useAppDataStore.getState()?.appId; + const pageHandle = getCurrentState().pageHandle; + + if (currentPageId === pageId && pageHandle === appDefinition?.pages[pageId]?.handle) { return; } const { name, handle } = appDefinition.pages[pageId]; diff --git a/frontend/src/Editor/Inspector/ActionConfigurationPanels/SwitchPage.jsx b/frontend/src/Editor/Inspector/ActionConfigurationPanels/SwitchPage.jsx index cd3dffe52e..dc697c4797 100644 --- a/frontend/src/Editor/Inspector/ActionConfigurationPanels/SwitchPage.jsx +++ b/frontend/src/Editor/Inspector/ActionConfigurationPanels/SwitchPage.jsx @@ -69,7 +69,7 @@ export function SwitchPage({ getPages, event, handlerChanged, eventIndex, darkMo
queryParamChangeHandler(index, 0, value)} mode="javascript" className="form-control codehinter-query-editor-input" @@ -79,7 +79,7 @@ export function SwitchPage({ getPages, event, handlerChanged, eventIndex, darkMo
queryParamChangeHandler(index, 1, value)} mode="javascript" className="form-control codehinter-query-editor-input" diff --git a/frontend/src/Editor/Inspector/Components/Chart.jsx b/frontend/src/Editor/Inspector/Components/Chart.jsx index 4c2c20c890..88e55d1e01 100644 --- a/frontend/src/Editor/Inspector/Components/Chart.jsx +++ b/frontend/src/Editor/Inspector/Components/Chart.jsx @@ -56,17 +56,16 @@ class Chart extends React.Component { } render() { - const { dataQueries, component, paramUpdated, componentMeta, components, currentState } = this.state; - const data = this.state.component.component.definition.properties.data; + const { dataQueries, component, paramUpdated, componentMeta, components, currentState } = this.props; + const data = this.props.component.component.definition.properties.data; // since component is not unmounting on every render in current scenario - const jsonDescription = this.state.component.component.definition.properties.jsonDescription; + const jsonDescription = this.props.component.component.definition.properties.jsonDescription; const plotFromJson = resolveReferences( - this.state.component.component.definition.properties.plotFromJson?.value, + this.props.component.component.definition.properties.plotFromJson?.value, currentState ); - - const chartType = this.state.component.component.definition.properties.type.value; + const chartType = this.props.component.component.definition.properties.type.value; let items = []; diff --git a/frontend/src/Editor/Inspector/EventManager.jsx b/frontend/src/Editor/Inspector/EventManager.jsx index 55559b6898..8a4ba36e59 100644 --- a/frontend/src/Editor/Inspector/EventManager.jsx +++ b/frontend/src/Editor/Inspector/EventManager.jsx @@ -909,7 +909,6 @@ export const EventManager = ({
{events.map((event, index) => { const actionMeta = ActionTypes.find((action) => action.id === event.event.actionId); - // const rowClassName = `card-body p-0 ${focusedEventIndex === index ? ' bg-azure-lt' : ''}`; return ( diff --git a/frontend/src/Editor/Viewer.jsx b/frontend/src/Editor/Viewer.jsx index e87724e976..4583505b90 100644 --- a/frontend/src/Editor/Viewer.jsx +++ b/frontend/src/Editor/Viewer.jsx @@ -226,7 +226,7 @@ class ViewerComponent extends React.Component { runQueries = (data_queries) => { data_queries.forEach((query) => { if (query.options.runOnPageLoad && isQueryRunnable(query)) { - runQuery(this, query.id, query.name, undefined, 'view'); + runQuery(this.getViewerRef(), query.id, query.name, undefined, 'view'); } }); }; @@ -553,17 +553,19 @@ class ViewerComponent extends React.Component { ); }; - handleEvent = (eventName, events, options) => { - const { appDefinition, currentPageId } = this.state; - const viewerRef = { - appDefinition: appDefinition, + getViewerRef() { + return { + appDefinition: this.state.appDefinition, queryConfirmationList: this.props.queryConfirmationList, updateQueryConfirmationList: this.updateQueryConfirmationList, navigate: this.props.navigate, switchPage: this.switchPage, - currentPageId: currentPageId, + currentPageId: this.state.currentPageId, }; - onEvent(viewerRef, eventName, events, options, 'view'); + } + + handleEvent = (eventName, events, options) => { + onEvent(this.getViewerRef(), eventName, events, options, 'view'); }; computeCanvasMaxWidth = () => { diff --git a/frontend/src/HomePage/ExportAppModal.jsx b/frontend/src/HomePage/ExportAppModal.jsx index fa2b74e0fc..a52fdd5eae 100644 --- a/frontend/src/HomePage/ExportAppModal.jsx +++ b/frontend/src/HomePage/ExportAppModal.jsx @@ -54,7 +54,7 @@ export default function ExportAppModal({ title, show, closeModal, customClassNam const requestBody = { ...appOpts, ...(exportTjDb && { tooljet_database: tables }), - organization_id: app.organization_id, + organization_id: app.organization_id ?? app.organizationId, }; appService diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index 7744e1380a..a0b852ca72 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -1703,24 +1703,34 @@ export function snapToGrid(canvasWidth, x, y) { return [snappedX, snappedY]; } export const removeSelectedComponent = (pageId, newDefinition, selectedComponents, updateAppDefinition) => { - selectedComponents.forEach((component) => { - let childComponents = []; + const toDeleteComponents = []; - if (newDefinition.pages[pageId].components[component.id]?.component?.component === 'Tabs') { - childComponents = Object.keys(newDefinition.pages[pageId].components).filter((key) => - newDefinition.pages[pageId].components[key].parent?.startsWith(component.id) - ); - } else { - childComponents = Object.keys(newDefinition.pages[pageId].components).filter( - (key) => newDefinition.pages[pageId].components[key].parent === component.id - ); + if (selectedComponents.length < 1) return getSelectedText(); + + const { components: allComponents } = newDefinition.pages[pageId]; + + const findAllChildComponents = (componentId) => { + if (!toDeleteComponents.includes(componentId)) { + toDeleteComponents.push(componentId); + + // Find the children of this component + const children = getAllChildComponents(allComponents, componentId).map((child) => child.componentId); + + if (children.length > 0) { + // Recursively find children of children + children.forEach((child) => { + findAllChildComponents(child); + }); + } } + }; - childComponents.forEach((componentId) => { - delete newDefinition.pages[pageId].components[componentId]; - }); + selectedComponents.forEach((component) => { + findAllChildComponents(component.id); + }); - delete newDefinition.pages[pageId].components[component.id]; + toDeleteComponents.forEach((componentId) => { + delete newDefinition.pages[pageId].components[componentId]; }); updateAppDefinition(newDefinition, { componentDefinitionChanged: true, componentDeleted: true }); diff --git a/frontend/src/_stores/dataQueriesStore.js b/frontend/src/_stores/dataQueriesStore.js index 557e690435..72f4e22c99 100644 --- a/frontend/src/_stores/dataQueriesStore.js +++ b/frontend/src/_stores/dataQueriesStore.js @@ -31,7 +31,7 @@ export const useDataQueriesStore = create( ...initialState, actions: { // TODO: Remove editor state while changing currentState - fetchDataQueries: async (appVersionId, selectFirstQuery = false, runQueriesOnAppLoad = false) => { + fetchDataQueries: async (appVersionId, selectFirstQuery = false, runQueriesOnAppLoad = false, ref) => { set({ loadingDataQueries: true }); const data = await dataqueryService.getAll(appVersionId); set((state) => ({ @@ -62,7 +62,7 @@ export const useDataQueriesStore = create( } // Runs query on loading application - if (runQueriesOnAppLoad) runQueries(data.data_queries, {}); + if (runQueriesOnAppLoad) runQueries(data.data_queries, ref); }, setDataQueries: (dataQueries) => set({ dataQueries }), deleteDataQueries: (queryId) => { diff --git a/frontend/src/_stores/editorStore.js b/frontend/src/_stores/editorStore.js index 27c2c898d1..a42dc2ae88 100644 --- a/frontend/src/_stores/editorStore.js +++ b/frontend/src/_stores/editorStore.js @@ -38,57 +38,58 @@ const initialState = { defaultComponentStateComputed: false, showLeftSidebar: true, queryConfirmationList: [], + currentPageId: null, }; export const useEditorStore = create( - zustandDevTools( - (set, get) => ({ - ...initialState, - actions: { - setShowComments: (showComments) => - set({ showComments }, false, { - type: ACTIONS.SET_HOVERED_COMPONENT, - showComments, - }), - toggleComments: () => - set({ showComments: !get().showComments }, false, { - type: ACTIONS.TOGGLE_COMMENTS, - }), - toggleCurrentLayout: (currentLayout) => - set({ currentLayout }, false, { - type: ACTIONS.TOGGLE_CURRENT_LAYOUT, - currentLayout, - }), - setIsEditorActive: (isEditorActive) => set(() => ({ isEditorActive })), - updateEditorState: (state) => set((prev) => ({ ...prev, ...state })), - updateQueryConfirmationList: (queryConfirmationList) => set({ queryConfirmationList }), - setHoveredComponent: (hoveredComponent) => - set({ hoveredComponent }, false, { - type: ACTIONS.SET_HOVERED_COMPONENT, - hoveredComponent, - }), - setSelectionInProgress: (isSelectionInProgress) => { - set( - { - isSelectionInProgress, - }, - false, - { type: ACTIONS.SET_SELECTION_IN_PROGRESS } - ); - }, - setSelectedComponents: (selectedComponents, isMulti = false) => { - const newSelectedComponents = isMulti - ? [...get().selectedComponents, ...selectedComponents] - : selectedComponents; - - set({ - selectedComponents: newSelectedComponents, - }); - }, + // Dev tools for this store are disabled comments since its freezing chrome tab + (set, get) => ({ + ...initialState, + actions: { + setShowComments: (showComments) => + set({ showComments }, false, { + type: ACTIONS.SET_HOVERED_COMPONENT, + showComments, + }), + toggleComments: () => + set({ showComments: !get().showComments }, false, { + type: ACTIONS.TOGGLE_COMMENTS, + }), + toggleCurrentLayout: (currentLayout) => + set({ currentLayout }, false, { + type: ACTIONS.TOGGLE_CURRENT_LAYOUT, + currentLayout, + }), + setIsEditorActive: (isEditorActive) => set(() => ({ isEditorActive })), + updateEditorState: (state) => set((prev) => ({ ...prev, ...state })), + updateQueryConfirmationList: (queryConfirmationList) => set({ queryConfirmationList }), + setHoveredComponent: (hoveredComponent) => + set({ hoveredComponent }, false, { + type: ACTIONS.SET_HOVERED_COMPONENT, + hoveredComponent, + }), + setSelectionInProgress: (isSelectionInProgress) => { + set( + { + isSelectionInProgress, + }, + false, + { type: ACTIONS.SET_SELECTION_IN_PROGRESS } + ); }, - }), - { name: STORE_NAME } - ) + setSelectedComponents: (selectedComponents, isMulti = false) => { + const newSelectedComponents = isMulti + ? [...get().selectedComponents, ...selectedComponents] + : selectedComponents; + + set({ + selectedComponents: newSelectedComponents, + }); + }, + setCurrentPageId: (currentPageId) => set({ currentPageId }), + }, + }), + { name: STORE_NAME } ); export const useEditorActions = () => useEditorStore((state) => state.actions); diff --git a/server/data-migrations/1695914619976-MigrateAppsDefinitionSchemaTransition.ts b/server/data-migrations/1695914619976-MigrateAppsDefinitionSchemaTransition.ts deleted file mode 100644 index bf3d5a9471..0000000000 --- a/server/data-migrations/1695914619976-MigrateAppsDefinitionSchemaTransition.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; -import { AppVersion } from '../src/entities/app_version.entity'; -import { Component } from 'src/entities/component.entity'; -import { Page } from 'src/entities/page.entity'; -import { Layout } from 'src/entities/layout.entity'; -import { EventHandler, Target } from 'src/entities/event_handler.entity'; - -export class MigrateAppsDefinitionSchemaTransition1695914619976 implements MigrationInterface { - public async up(queryRunner: QueryRunner): Promise { - let progress = 0; - - const entityManager = queryRunner.manager; - - const queryBuilder = queryRunner.connection.createQueryBuilder(); - - const appVersionRepository = entityManager.getRepository(AppVersion); - - const appVersions = await appVersionRepository.find(); - - console.log(`MigrateAppsDefinitionSchemaTransition1695902112489 Progress ${progress} %`); - - for (const version of appVersions) { - progress++; - const definition = version['definition']; - - const dataQueries = await queryBuilder - .select() - .from('data_queries', 'data_queries') - .where('app_version_id = :appVersionId', { appVersionId: version.id }) - .getRawMany(); - - let updateHomepageId = null; - - if (definition?.pages) { - for (const pageId of Object.keys(definition?.pages)) { - const page = definition.pages[pageId]; - - const pageEvents = page.events || []; - - const componentEvents = []; - - const pagePostionIntheList = Object.keys(definition?.pages).indexOf(pageId); - - const isHompage = (definition['homePageId'] as any) === pageId; - - const pageComponents = page.components; - - const componentLayouts = []; - - const mappedComponents = this.transformComponentData(pageComponents, componentEvents); - - const newPage = entityManager.create(Page, { - name: page.name, - - handle: page.handle, - - appVersionId: version.id, - - index: pagePostionIntheList, - }); - - const pageCreated = await entityManager.save(newPage); - - mappedComponents.forEach((component) => { - component.page = pageCreated; - }); - - const savedComponents = await entityManager.save(Component, mappedComponents); - - savedComponents.forEach((component) => { - const componentLayout = pageComponents[component.id]['layouts']; - - if (componentLayout) { - 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.component = component; - - componentLayouts.push(newLayout); - } - } - }); - - await entityManager.save(Layout, componentLayouts); - - 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: version.id, - }; - - await entityManager.save(EventHandler, newEvent); - }); - } - - componentEvents.forEach((eventObj) => { - if (eventObj.event?.length === 0) return; - - eventObj.event.forEach(async (event, index) => { - const newEvent = { - name: event.eventId, - - sourceId: eventObj.componentId, - - target: Target.component, - - event: event, - - index: eventObj.index || index, - - appVersionId: version.id, - }; - - await entityManager.save(EventHandler, newEvent); - }); - }); - - if (isHompage) { - updateHomepageId = pageCreated.id; - } - } - } - - for (const dataQuery of dataQueries) { - const queryEvents = dataQuery?.options?.events || []; - - if (queryEvents.length > 0) { - queryEvents.forEach(async (event, index) => { - const newEvent = { - name: event.eventId, - sourceId: dataQuery.id, - target: Target.dataQuery, - event: event, - index: queryEvents.index || index, - appVersionId: version.id, - }; - - await entityManager.save(EventHandler, newEvent); - }); - } - } - console.log( - `MigrateAppsDefinitionSchemaTransition1695902112489 Progress ${Math.round( - (progress / appVersions.length) * 100 - )} %` - ); - await entityManager.update( - AppVersion, - { id: version.id }, - { - homePageId: updateHomepageId, - showViewerNavigation: definition.showViewerNavigation || true, - globalSettings: definition.globalSettings, - } - ); - } - } - - private transformComponentData(data: object, componentEvents: any[]): Component[] { - const transformedComponents: Component[] = []; - - for (const componentId in data) { - const componentData = data[componentId]['component']; - - const transformedComponent: Component = new Component(); - - transformedComponent.id = componentId; - - transformedComponent.name = componentData.name; - - transformedComponent.type = componentData.component; - - transformedComponent.properties = componentData.definition.properties || {}; - - transformedComponent.styles = componentData.definition.styles || {}; - - transformedComponent.validation = componentData.definition.validation || {}; - - transformedComponent.parent = data[componentId].parent || null; - - transformedComponents.push(transformedComponent); - - componentEvents.push({ - componentId: componentId, - - event: componentData.definition.events, - }); - } - - return transformedComponents; - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query('DELETE FROM page'); - await queryRunner.query('DELETE FROM component'); - await queryRunner.query('DELETE FROM layout'); - await queryRunner.query('DELETE FROM event_handler'); - - await queryRunner.query('ALTER TABLE app_version DROP COLUMN IF EXISTS homePageId'); - await queryRunner.query('ALTER TABLE app_version DROP COLUMN IF EXISTS globalSettings'); - await queryRunner.query('ALTER TABLE app_version DROP COLUMN IF EXISTS showViewerNavigation'); - } -} diff --git a/server/data-migrations/1697473340856-MigrateAppsDefinitionSchemaTransition.ts b/server/data-migrations/1697473340856-MigrateAppsDefinitionSchemaTransition.ts new file mode 100644 index 0000000000..d90faa240a --- /dev/null +++ b/server/data-migrations/1697473340856-MigrateAppsDefinitionSchemaTransition.ts @@ -0,0 +1,240 @@ +import { In, MigrationInterface, QueryRunner } from 'typeorm'; +import { AppVersion } from '../src/entities/app_version.entity'; +import { Component } from 'src/entities/component.entity'; +import { Page } from 'src/entities/page.entity'; +import { Layout } from 'src/entities/layout.entity'; +import { EventHandler, Target } from 'src/entities/event_handler.entity'; +import { DataQuery } from 'src/entities/data_query.entity'; +import { MigrationProgress, processDataInBatches } from 'src/helpers/utils.helper'; + +export class MigrateAppsDefinitionSchemaTransition1697473340856 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + // let progress = 0; + const entityManager = queryRunner.manager; + const appVersionRepository = entityManager.getRepository(AppVersion); + const appVersions = await appVersionRepository.find(); + const totalApps = appVersions.length; + + const migrationProgress = new MigrationProgress('MigrateAppsDefinitionSchemaTransition1697473340856', totalApps); + + const batchSize = 100; // Number of apps to migrate at a time + + await processDataInBatches( + entityManager, + async (entityManager, skip, take) => { + return entityManager.find(AppVersion, { + where: { id: In(appVersions.map((appVersion) => appVersion.id)) }, + take, + skip, + }); + }, + async (entityManager, versions: AppVersion[]) => { + for (const version of versions) { + const definition = version['definition']; + + if (!definition) return; + + const dataQueriesRepository = entityManager.getRepository(DataQuery); + const dataQueries = await dataQueriesRepository.find({ + where: { appVersionId: version.id }, + }); + + let updateHomepageId = null; + + if (definition?.pages) { + for (const pageId of Object.keys(definition?.pages)) { + const page = definition.pages[pageId]; + const pageEvents = page.events || []; + const componentEvents = []; + const pagePositionInTheList = Object.keys(definition?.pages).indexOf(pageId); + const isHomepage = (definition['homePageId'] as any) === pageId; + const pageComponents = page.components; + const componentLayouts = []; + const mappedComponents = this.transformComponentData(pageComponents, componentEvents); + const newPage = entityManager.create(Page, { + name: page.name, + handle: page.handle, + appVersionId: version.id, + disabled: page.disabled || false, + hidden: page.hidden || false, + index: pagePositionInTheList, + }); + + const pageCreated = await entityManager.save(newPage); + mappedComponents.forEach((component) => { + component.page = pageCreated; + }); + + const savedComponents = await entityManager.save(Component, mappedComponents); + + savedComponents.forEach((component) => { + const componentLayout = pageComponents[component.id]['layouts']; + + if (componentLayout) { + 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.component = component; + componentLayouts.push(newLayout); + } + } + }); + + await entityManager.save(Layout, componentLayouts); + + 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: version.id, + }; + + await entityManager.save(EventHandler, newEvent); + }); + } + + componentEvents.forEach((eventObj) => { + if (eventObj.event?.length === 0) return; + + eventObj.event.forEach(async (event, index) => { + const newEvent = { + name: event.eventId, + sourceId: eventObj.componentId, + target: Target.component, + event: event, + index: eventObj.index || index, + appVersionId: version.id, + }; + + await entityManager.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: version.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: version.id, + }); + }); + }); + + await entityManager.save(EventHandler, tableActionAndColumnEvents); + } + }); + + if (isHomepage) { + updateHomepageId = pageCreated.id; + } + } + } + + for (const dataQuery of dataQueries) { + const queryEvents = dataQuery?.options?.events || []; + + if (queryEvents.length > 0) { + queryEvents.forEach(async (event, index) => { + const newEvent = { + name: event.eventId, + sourceId: dataQuery.id, + target: Target.dataQuery, + event: event, + index: queryEvents.index || index, + appVersionId: version.id, + }; + + await entityManager.save(EventHandler, newEvent); + }); + } + } + + migrationProgress.show(); + await entityManager.update( + AppVersion, + { id: version.id }, + { + homePageId: updateHomepageId, + showViewerNavigation: definition?.showViewerNavigation || true, + globalSettings: definition.globalSettings, + } + ); + } + }, + batchSize + ); + } + + private transformComponentData(data: object, componentEvents: any[]): Component[] { + const transformedComponents: Component[] = []; + + for (const componentId in data) { + const componentData = data[componentId]['component']; + + const transformedComponent: Component = new Component(); + + transformedComponent.id = componentId; + transformedComponent.name = componentData.name; + transformedComponent.type = componentData.component; + transformedComponent.properties = componentData.definition.properties || {}; + transformedComponent.styles = componentData.definition.styles || {}; + transformedComponent.validation = componentData.definition.validation || {}; + transformedComponent.parent = data[componentId].parent || null; + transformedComponents.push(transformedComponent); + + componentEvents.push({ + componentId: componentId, + event: componentData.definition.events, + }); + } + + return transformedComponents; + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query('DELETE FROM page'); + await queryRunner.query('DELETE FROM component'); + await queryRunner.query('DELETE FROM layout'); + await queryRunner.query('DELETE FROM event_handler'); + + await queryRunner.query('ALTER TABLE app_version DROP COLUMN IF EXISTS homePageId'); + await queryRunner.query('ALTER TABLE app_version DROP COLUMN IF EXISTS globalSettings'); + await queryRunner.query('ALTER TABLE app_version DROP COLUMN IF EXISTS showViewerNavigation'); + } +} diff --git a/server/src/controllers/apps.controller.v2.ts b/server/src/controllers/apps.controller.v2.ts index 7c972581ef..aa3b791466 100644 --- a/server/src/controllers/apps.controller.v2.ts +++ b/server/src/controllers/apps.controller.v2.ts @@ -13,6 +13,7 @@ import { UseInterceptors, } from '@nestjs/common'; import { JwtAuthGuard } from '../../src/modules/auth/jwt-auth.guard'; +import { AppAuthGuard } from 'src/modules/auth/app-auth.guard'; import { AppsService } from '../services/apps.service'; import { camelizeKeys, decamelizeKeys } from 'humps'; import { AppsAbilityFactory } from 'src/modules/casl/abilities/apps-ability.factory'; @@ -101,6 +102,8 @@ export class AppsControllerV2 { return response; } + @UseGuards(AppAuthGuard) // This guard will allow access for unauthenticated user if the app is public + @Get('slugs/:slug') async appFromSlug(@User() user, @AppDecorator() app: App) { if (user) { const ability = await this.appsAbilityFactory.appsActions(user, app.id); diff --git a/server/src/services/app_import_export.service.ts b/server/src/services/app_import_export.service.ts index d9decca543..259ab9587a 100644 --- a/server/src/services/app_import_export.service.ts +++ b/server/src/services/app_import_export.service.ts @@ -355,8 +355,6 @@ export class AppImportExportService { appResourceMappings.dataQueryMapping ); - // !----- - let updateHomepageId = null; if (updatedDefinition?.pages) { @@ -381,10 +379,15 @@ export class AppImportExportService { handle: page.handle, appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], index: pagePostionIntheList, + disabled: page.disabled || false, + hidden: page.hidden || false, }); const pageCreated = await manager.save(newPage); + appResourceMappings.pagesMapping[pageId] = pageCreated.id; + mappedComponents.forEach((component) => { + appResourceMappings.componentsMapping[component.id] = component.id; component.page = pageCreated; }); @@ -445,14 +448,54 @@ export class AppImportExportService { }); }); + 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; } } } - //!---- - await manager.update( AppVersion, { id: appResourceMappings.appVersionMapping[importingAppVersion.id] }, @@ -464,6 +507,19 @@ export class AppImportExportService { } } + const appVersionIds = Object.values(appResourceMappings.appVersionMapping); + + for (const appVersionId of appVersionIds) { + await this.updateEventActionsForNewVersionWithNewMappingIds( + manager, + appVersionId, + appResourceMappings.dataQueryMapping, + appResourceMappings.componentsMapping, + appResourceMappings.pagesMapping, + isNormalizedAppDefinitionSchema + ); + } + await this.setEditingVersionAsLatestVersion(manager, appResourceMappings.appVersionMapping, importingAppVersions); return appResourceMappings; @@ -576,12 +632,30 @@ export class AppImportExportService { appResourceMappings.dataQueryMapping = dataQueryMapping; } - for (const page of importingPages) { + const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentId = undefined) => { + if (componentParentId) { + const parentId = component?.parent?.split('-').slice(0, -1).join('-'); + + const parentComponent = allComponents.find((comp) => comp.id === parentId); + + if (parentComponent) { + return parentComponent.type === 'Tabs' || parentComponent.type === 'Calendar'; + } + } + + return false; + }; + + const pagesOfAppVersion = importingPages.filter((page) => page.appVersionId === importingAppVersion.id); + + for (const page of pagesOfAppVersion) { const newPage = manager.create(Page, { name: page.name, handle: page.handle, appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], index: page.index, + disabled: page.disabled || false, + hidden: page.hidden || false, }); const pageCreated = await manager.save(newPage); @@ -599,12 +673,26 @@ export class AppImportExportService { for (const component of pageComponents) { const newComponent = new Component(); + let parentId = component.parent ? component.parent : null; + + const isParentTabOrCalendar = isChildOfTabsOrCalendar(component, pageComponents, parentId); + + if (isParentTabOrCalendar) { + const childTabId = component.parent.split('-')[component.parent.split('-').length - 1]; + const _parentId = component?.parent?.split('-').slice(0, -1).join('-'); + const mappedParentId = appResourceMappings.componentsMapping[_parentId]; + + parentId = `${mappedParentId}-${childTabId}`; + } else { + parentId = appResourceMappings.componentsMapping[parentId]; + } + newComponent.name = component.name; newComponent.type = component.type; newComponent.properties = component.properties; newComponent.styles = component.styles; newComponent.validation = component.validation; - newComponent.parent = component.parent || null; + newComponent.parent = component.parent ? parentId : null; newComponent.page = pageCreated; @@ -629,14 +717,14 @@ export class AppImportExportService { if (componentEvents.length > 0) { componentEvents.forEach(async (componentEvent) => { - const newEvent = { - name: componentEvent.name, - sourceId: savedComponent.id, - target: componentEvent.target, - event: componentEvent.event, - index: componentEvent.index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }; + 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]; + await manager.save(EventHandler, newEvent); }); } @@ -675,18 +763,11 @@ export class AppImportExportService { if (importingQueryEvents.length > 0) { importingQueryEvents.forEach(async (dataQueryEvent) => { - const updatedEventDefinition = this.updateEventActionsForNewVersionWithNewMappingIds( - dataQueryEvent, - appResourceMappings.dataQueryMapping, - appResourceMappings.componentsMapping, - appResourceMappings.pagesMapping - ); - const newEvent = { name: dataQueryEvent.name, sourceId: mappedNewDataQuery.id, target: dataQueryEvent.target, - event: updatedEventDefinition, + event: dataQueryEvent.event, index: dataQueryEvent.index, appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], }; @@ -698,18 +779,11 @@ export class AppImportExportService { delete mappedNewDataQuery?.options?.events; queryEvents.forEach(async (event, index) => { - const updatedEventDefinition = this.updateEventActionsForNewVersionWithNewMappingIds( - { event: event }, - appResourceMappings.dataQueryMapping, - appResourceMappings.componentsMapping, - appResourceMappings.pagesMapping - ); - const newEvent = { name: event.eventId, sourceId: mappedNewDataQuery.id, target: Target.dataQuery, - event: updatedEventDefinition, + event: event, index: queryEvents.index || index, appVersionId: mappedNewDataQuery.appVersionId, }; @@ -1374,29 +1448,39 @@ export class AppImportExportService { return { ...queryOptions, table_id: tooljetDatabaseMapping[queryOptions.table_id]?.id }; } - updateEventActionsForNewVersionWithNewMappingIds( - queryEvent: EventHandler | { event: any }, + async updateEventActionsForNewVersionWithNewMappingIds( + manager: EntityManager, + versionId: string, oldDataQueryToNewMapping: Record, oldComponentToNewComponentMapping: Record, - oldPageToNewPageMapping: Record + oldPageToNewPageMapping: Record, + isNormalizedAppDefinitionSchema: boolean ) { - const event = JSON.parse(JSON.stringify(queryEvent)); + if (!isNormalizedAppDefinitionSchema) return; - const eventDefinition = event.event; + const allEvents = await manager.find(EventHandler, { + where: { appVersionId: versionId }, + }); - if (eventDefinition?.actionId === 'run-query') { - eventDefinition.queryId = oldDataQueryToNewMapping[eventDefinition.queryId]; + for (const event of allEvents) { + const eventDefinition = event.event; + + if (eventDefinition?.actionId === 'run-query') { + eventDefinition.queryId = oldDataQueryToNewMapping[eventDefinition.queryId]; + } + + if (eventDefinition?.actionId === 'control-component') { + eventDefinition.componentId = oldComponentToNewComponentMapping[eventDefinition.componentId]; + } + + if (eventDefinition?.actionId === 'switch-page') { + eventDefinition.pageId = oldPageToNewPageMapping[eventDefinition.pageId]; + } + + event.event = eventDefinition; + + await manager.save(event); } - - if (eventDefinition?.actionId === 'control-component') { - eventDefinition.componentId = oldComponentToNewComponentMapping[eventDefinition.componentId]; - } - - if (eventDefinition?.actionId === 'switch-page') { - eventDefinition.pageId = oldPageToNewPageMapping[eventDefinition.pageId]; - } - - return eventDefinition; } } diff --git a/server/src/services/apps.service.ts b/server/src/services/apps.service.ts index bca36716e4..92df0026d2 100644 --- a/server/src/services/apps.service.ts +++ b/server/src/services/apps.service.ts @@ -459,12 +459,28 @@ export class AppsService { const oldComponentToNewComponentMapping = {}; const oldPageToNewPageMapping = {}; + const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentId = undefined) => { + if (componentParentId) { + const parentId = component?.parent?.split('-').slice(0, -1).join('-'); + + const parentComponent = allComponents.find((comp) => comp.id === parentId); + + if (parentComponent) { + return parentComponent.type === 'Tabs' || parentComponent.type === 'Calendar'; + } + } + + return false; + }; + for (const page of pages) { const savedPage = await manager.save( manager.create(Page, { name: page.name, handle: page.handle, index: page.index, + disabled: page.disabled, + hidden: page.hidden, appVersionId: appVersion.id, }) ); @@ -497,13 +513,27 @@ export class AppsService { oldComponentToNewComponentMapping[component.id] = newComponent.id; + let parentId = component.parent ? component.parent : null; + + const isParentTabOrCalendar = isChildOfTabsOrCalendar(component, page.components, parentId); + + if (isParentTabOrCalendar) { + const childTabId = component.parent.split('-')[component.parent.split('-').length - 1]; + const _parentId = component?.parent?.split('-').slice(0, -1).join('-'); + const mappedParentId = oldComponentToNewComponentMapping[_parentId]; + + parentId = `${mappedParentId}-${childTabId}`; + } else { + parentId = oldComponentToNewComponentMapping[parentId]; + } + newComponent.name = component.name; newComponent.type = component.type; newComponent.pageId = savedPage.id; newComponent.properties = component.properties; newComponent.styles = component.styles; newComponent.validation = component.validation; - newComponent.parent = component.parent ? oldComponentToNewComponentMapping[component.parent] : null; + newComponent.parent = component.parent ? parentId : null; newComponent.page = savedPage; newComponents.push(newComponent);