Merge branch 'appdefinition-architecture-revamp' of https://github.com/ToolJet/ToolJet into feat/react-moveable-integration

This commit is contained in:
Johnson Cherian 2023-10-18 20:42:22 +05:30
commit 64c074aab9
16 changed files with 556 additions and 380 deletions

View file

@ -58,7 +58,7 @@ export const CreateVersion = ({
});
appVersionService
.getOne(appId, data.id)
.getAppVersionData(appId, data.id)
.then((data) => {
setAppDefinitionFromVersion(data);
})

View file

@ -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) => {

View file

@ -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];

View file

@ -69,7 +69,7 @@ export function SwitchPage({ getPages, event, handlerChanged, eventIndex, darkMo
<div key={index} className="row input-group mt-1">
<div className="col">
<CodeHinter
initialValue={event.queryParams[index][0]}
initialValue={event?.queryParams?.[index]?.[0]}
onChange={(value) => 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
</div>
<div className="col">
<CodeHinter
initialValue={event.queryParams[index][1]}
initialValue={event?.queryParams?.[index]?.[1]}
onChange={(value) => queryParamChangeHandler(index, 1, value)}
mode="javascript"
className="form-control codehinter-query-editor-input"

View file

@ -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 = [];

View file

@ -909,7 +909,6 @@ export const EventManager = ({
<div {...droppableProps} ref={innerRef}>
{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 (
<Draggable key={index} draggableId={`${event.eventId}-${index}`} index={index}>

View file

@ -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 = () => {

View file

@ -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

View file

@ -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 });

View file

@ -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) => {

View file

@ -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);

View file

@ -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<void> {
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<void> {
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');
}
}

View file

@ -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<void> {
// 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<void> {
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');
}
}

View file

@ -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);

View file

@ -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<string, unknown>,
oldComponentToNewComponentMapping: Record<string, unknown>,
oldPageToNewPageMapping: Record<string, unknown>
oldPageToNewPageMapping: Record<string, unknown>,
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;
}
}

View file

@ -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);