Fix: handle error gracefully for failed saving changes (#512)

* handle error gracefully for event handlers attachment do not exist

* handle error on saving changes
- added server side validation checks
- client side error message
- removed autoSave triggers from priotity queue
This commit is contained in:
Arpit 2024-07-17 13:52:56 +05:30 committed by Kavin Venkatachalam
parent 67cb1455fa
commit 8805af9ce1
5 changed files with 108 additions and 33 deletions

View file

@ -82,7 +82,12 @@ import { HotkeysProvider } from 'react-hotkeys-hook';
import { useResolveStore } from '@/_stores/resolverStore';
import { dfs } from '@/_stores/handleReferenceTransactions';
import { decimalToHex, EditorConstants } from './editorConstants';
import { handleLowPriorityWork, updateCanvasBackground, clearAllQueuedTasks } from '@/_helpers/editorHelpers';
import {
handleLowPriorityWork,
updateCanvasBackground,
clearAllQueuedTasks,
checkAndExtractEntityId,
} from '@/_helpers/editorHelpers';
import { TJLoader } from '@/_ui/TJLoader/TJLoader';
import cx from 'classnames';
import { resolveReferences } from './CodeEditor/utils';
@ -286,7 +291,7 @@ const EditorComponent = (props) => {
if (appDiffOptions?.skipAutoSave === true || appDiffOptions?.entityReferenceUpdated === true) return;
handleLowPriorityWork(() => autoSave(), 100);
autoSave();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify({ appDefinition, currentPageId, dataQueries })]);
@ -1038,26 +1043,24 @@ const EditorComponent = (props) => {
}
//Todo: Move this to a separate function or as a middleware of the api to createing a component
handleLowPriorityWork(() => {
if (updateDiff?.type === 'components' && updateDiff?.operation === 'create') {
const componentsFromCurrentState = getCurrentState().components;
const newComponentIds = Object.keys(updateDiff?.updateDiff);
const newComponentsExposedData = {};
const componentEntityArray = [];
Object.values(componentsFromCurrentState).filter((component) => {
if (newComponentIds.includes(component.id)) {
const componentName = updateDiff?.updateDiff[component.id]?.name;
newComponentsExposedData[componentName] = component;
componentEntityArray.push({ id: component.id, name: componentName });
}
});
if (updateDiff?.type === 'components' && updateDiff?.operation === 'create') {
const componentsFromCurrentState = getCurrentState().components;
const newComponentIds = Object.keys(updateDiff?.updateDiff);
const newComponentsExposedData = {};
const componentEntityArray = [];
Object.values(componentsFromCurrentState).filter((component) => {
if (newComponentIds.includes(component.id)) {
const componentName = updateDiff?.updateDiff[component.id]?.name;
newComponentsExposedData[componentName] = component;
componentEntityArray.push({ id: component.id, name: componentName });
}
});
useResolveStore.getState().actions.addEntitiesToMap(componentEntityArray);
useResolveStore.getState().actions.addAppSuggestions({
components: newComponentsExposedData,
});
}
});
useResolveStore.getState().actions.addEntitiesToMap(componentEntityArray);
useResolveStore.getState().actions.addAppSuggestions({
components: newComponentsExposedData,
});
}
if (
updateDiff?.type === 'components' &&
@ -1080,13 +1083,24 @@ const EditorComponent = (props) => {
isUpdatingEditorStateInProcess: false,
});
})
.catch((err) => {
.catch((e) => {
const entityNotSaved =
e?.data?.statusCode === 500 && e?.error
? checkAndExtractEntityId(e.error)
: { entityId: null, message: 'App could not be saved.' };
let errMessage = e?.data?.message || 'App could not be saved.';
if (entityNotSaved.entityId) {
const componentName =
appDefinition.pages[currentPageId].components[entityNotSaved.entityId]?.component?.name;
errMessage = `The component "${componentName}" could not be saved, so the last action is also not saved.`;
}
updateEditorState({
saveError: true,
isUpdatingEditorStateInProcess: false,
});
// toast.error('App could not save.');
toast.error(err?.error ?? 'App could not save.');
toast.error(errMessage);
})
.finally(() => {
if (appDiffOptions?.cloningComponent) {

View file

@ -336,3 +336,18 @@ export const updateCanvasBackground = ({ canvasBackgroundColor, backgroundFxQuer
}
}
};
export function checkAndExtractEntityId(errorString) {
const regex = /"([a-f0-9-]+)"/;
const match = errorString.match(regex);
if (match && match[1]) {
return {
entityId: match[1],
message: 'The last component is not saved, so the last action is also not saved.',
};
}
return {
entityId: null,
message: 'No entity ID found in the error message.',
};
}

View file

@ -7,6 +7,7 @@ import { useDataQueriesStore } from './dataQueriesStore';
import _ from 'lodash';
import { dfs, handleReferenceTransactions } from './handleReferenceTransactions';
import { isValidUUID } from '@/_helpers/utils';
import toast from 'react-hot-toast';
const initialState = {
editingVersion: null,
@ -137,13 +138,22 @@ export const useAppDataStore = create(
const versionId = get().currentVersionId;
const updatedEvents = get().events;
const response = await appVersionService.createAppVersionEventHandler(appId, versionId, event);
get().actions.setIsSaving(false);
set({ eventsCreatedLoader: false });
appVersionService
.createAppVersionEventHandler(appId, versionId, event)
.then((response) => {
get().actions.setIsSaving(false);
set({ eventsCreatedLoader: false });
updatedEvents.push(response);
updatedEvents.push(response);
set(() => ({ events: updatedEvents }));
set(() => ({ events: updatedEvents }));
})
.catch((err) => {
get().actions.setIsSaving(false);
set({ eventsCreatedLoader: false });
toast.error(err?.error || 'An error occurred while creating the event handler');
});
},
deleteAppVersionEventHandler: async (eventId) => {

View file

@ -72,9 +72,9 @@ export class ComponentsService {
for (const componentId in componentDiff) {
const { component } = componentDiff[componentId];
const componentData: Component = await manager.findOne(Component, componentId);
const doesComponentExist = await manager.findOneOrFail(Component, componentId);
if (!componentData) {
if (doesComponentExist[1] === 0) {
return {
error: {
message: `Component with id ${componentId} does not exist`,
@ -82,6 +82,8 @@ export class ComponentsService {
};
}
const componentData: Component = await manager.findOne(Component, componentId);
const isComponentDefinitionChanged = component.definition ? true : false;
if (isComponentDefinitionChanged) {
@ -151,9 +153,9 @@ export class ComponentsService {
) {
return dbTransactionForAppVersionAssociationsUpdate(async (manager: EntityManager) => {
for (const componentId in componenstLayoutDiff) {
const doesComponentExist = await manager.findAndCount(Component, { id: componentId });
const doesComponentExist = await manager.findOneOrFail(Component, componentId);
if (!doesComponentExist[1]) {
if (doesComponentExist[1] === 0) {
return {
error: {
message: `Component with id ${componentId} does not exist`,

View file

@ -51,6 +51,40 @@ export class EventsService {
}
return await dbTransactionForAppVersionAssociationsUpdate(async (manager: EntityManager) => {
if (
eventHandler.eventType === 'component' ||
eventHandler.eventType === 'table_column' ||
eventHandler.eventType === 'table_action'
) {
const componentExists = await manager.findOne('Component', {
id: eventHandler.attachedTo,
});
if (!componentExists) {
throw new BadRequestException('Component does not exist');
}
}
if (eventHandler.eventType === 'data_query') {
const dataQueryExists = await manager.findOne('DataQuery', {
id: eventHandler.attachedTo,
});
if (!dataQueryExists) {
throw new BadRequestException('Data Query does not exist');
}
}
if (eventHandler.eventType === 'page') {
const pageExists = await manager.findOne('Page', {
id: eventHandler.attachedTo,
});
if (!pageExists) {
throw new BadRequestException('Page does not exist');
}
}
const newEvent = new EventHandler();
newEvent.name = eventHandler.event.eventId;
newEvent.sourceId = eventHandler.attachedTo;