Hotfix [LTS] - Bugfixes (#10146)

* fixes: child components on listView are not updating correctly

* bumped version

* Special handling for 'window' keyword in multiline code editors

- Updated validation logic to allow 'window.x' while treating standalone 'window' as a reserved keyword.
- Refined keyword matching to ensure keywords are not part of a string or a comment and are exact matches.
- Applied a hotfix to both CE-LTS and the latest non-LTS CE versions, effective by end of day.
- Note: This update is specifically for multiline code editors; single line editors will continue to support 'window.x' or any usage of 'window' as it breaks the app otherwise.

* fixes: update the query refs for failed query

* fixes: on form submit the children's data should be accessable from queries events

* fixes: event execution

* adds a debounce for form reset on submit

* for onPageLoad events, we need to execute the actions after the page is loaded
This commit is contained in:
Arpit 2024-06-24 20:22:23 +05:30 committed by GitHub
parent 5666442f8f
commit a1b38f4a2f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 82 additions and 37 deletions

View file

@ -1 +1 @@
2.50.6
2.50.7

View file

@ -1 +1 @@
2.50.6
2.50.7

View file

@ -3,7 +3,7 @@ import { SubCustomDragLayer } from '@/Editor/SubCustomDragLayer';
import { SubContainer } from '@/Editor/SubContainer';
// eslint-disable-next-line import/no-unresolved
import { diff } from 'deep-object-diff';
import _, { omit } from 'lodash';
import _, { debounce, omit } from 'lodash';
import { Box } from '@/Editor/Box';
import { generateUIComponents } from './FormUtils';
import { useMounted } from '@/_hooks/use-mount';
@ -189,7 +189,9 @@ export const Form = function Form(props) {
};
const fireSubmissionEvent = () => {
if (isValid) {
onEvent('onSubmit', formEvents).then(() => resetComponent());
onEvent('onSubmit', formEvents).then(() => {
debounce(() => resetComponent(), 100)();
});
} else {
fireEvent('onInvalid');
}

View file

@ -258,16 +258,18 @@ export const Container = ({
return;
}
if (!appDefinition.pages[currentPageId]?.components) return;
const definition = useEditorStore.getState().appDefinition;
if (!definition.pages[currentPageId]?.components) return;
const newDefinition = {
...appDefinition,
...definition,
pages: {
...appDefinition.pages,
...definition.pages,
[currentPageId]: {
...appDefinition.pages[currentPageId],
...definition.pages[currentPageId],
components: {
...appDefinition.pages[currentPageId]?.components,
...definition.pages[currentPageId]?.components,
...boxes,
},
},
@ -276,7 +278,7 @@ export const Container = ({
//need to check if a new component is added or deleted
const oldComponents = appDefinition.pages[currentPageId]?.components ?? {};
const oldComponents = definition.pages[currentPageId]?.components ?? {};
const newComponents = boxes;
const componendAdded = Object.keys(newComponents).length > Object.keys(oldComponents).length;
@ -289,7 +291,8 @@ export const Container = ({
opts.componentAdded = true;
}
const shouldUpdate = !_.isEmpty(diff(appDefinition, newDefinition));
const shouldUpdate = !_.isEmpty(diff(definition, newDefinition));
if (shouldUpdate) {
appDefinitionChanged(newDefinition, opts);
}
@ -1045,15 +1048,16 @@ const WidgetWrapper = ({
const isWidgetActive = (isSelected || isDragging) && mode !== 'view';
const { label = { value: null } } = propertiesDefinition ?? {};
const visibility = propertiesDefinition?.visibility?.value ?? stylesDefinition?.visibility?.value ?? null;
const resolvedVisibility = resolveWidgetFieldValue(visibility);
const styles = {
width: width + 'px',
height: calculateMoveableBoxHeight() + 'px',
height: resolvedVisibility ? calculateMoveableBoxHeight() + 'px' : '10px',
transform: `translate(${layoutData.left * gridWidth}px, ${layoutData.top}px)`,
...(isGhostComponent ? { opacity: 0.5 } : {}),
...(isWidgetActive ? { zIndex: 3 } : {}),
};
return (
<>
<div

View file

@ -31,6 +31,7 @@ export const shouldUpdate = (prevProps, nextProps) => {
return (
deepEqualityCheckusingLoDash(prevProps?.id, nextProps?.id) &&
deepEqualityCheckusingLoDash(prevProps?.component?.definition, nextProps?.component?.definition) &&
deepEqualityCheckusingLoDash(prevProps?.customResolvables, nextProps?.customResolvables) &&
prevProps?.width === nextProps?.width &&
prevProps?.height === nextProps?.height &&
prevProps?.darkMode === nextProps?.darkMode &&

View file

@ -190,22 +190,24 @@ export const SubContainer = ({
}, [containerWidth]);
useEffect(() => {
if (appDefinitionChanged) {
const definition = useEditorStore.getState().appDefinition;
if (definition) {
const newDefinition = {
...appDefinition,
...definition,
pages: {
...appDefinition.pages,
...definition.pages,
[currentPageId]: {
...appDefinition.pages[currentPageId],
...definition.pages[currentPageId],
components: {
...appDefinition.pages[currentPageId].components,
...definition.pages[currentPageId].components,
...childWidgets,
},
},
},
};
const oldComponents = appDefinition.pages[currentPageId]?.components ?? {};
const oldComponents = definition.pages[currentPageId]?.components ?? {};
const newComponents = newDefinition.pages[currentPageId]?.components ?? {};
const componendAdded = Object.keys(newComponents).length > Object.keys(oldComponents).length;
@ -216,7 +218,7 @@ export const SubContainer = ({
opts.componentAdded = true;
}
const shouldUpdate = !_.isEmpty(diff(appDefinition, newDefinition));
const shouldUpdate = !_.isEmpty(diff(definition, newDefinition));
if (shouldUpdate) {
appDefinitionChanged(newDefinition, opts);
@ -716,11 +718,19 @@ const SubWidgetWrapper = ({
const isDragging = useGridStore((state) => state?.draggingComponentId === id);
const isComponentVisible = () => {
const visibility =
widget.component.definition?.properties?.visibility?.value ??
widget.component.definition?.styles?.visibility?.value ??
null;
return resolveWidgetFieldValue(visibility);
};
let width = (canvasWidth * layoutData.width) / 43;
width = width > canvasWidth ? canvasWidth : width; //this handles scenarios where the width is set more than canvas for older components
const styles = {
width: width + 'px',
height: layoutData.height + 'px',
height: isComponentVisible() ? layoutData.height + 'px' : '10px',
transform: `translate(${layoutData.left * gridWidth}px, ${layoutData.top}px)`,
...(isGhostComponent ? { opacity: 0.5 } : {}),
};

View file

@ -105,15 +105,22 @@ export function onComponentOptionsChanged(component, options, id) {
componentData = deepClone(componentData) || {};
const shouldUpdateResolvedRefsOfHints = [];
const isListviewOrKanbaComponent = component.component === 'Listview' || component.component === 'Kanban';
const isFromComponent = component.component === 'Form';
for (const option of options) {
componentData[option[0]] = option[1];
const isListviewOrKanbaComponent = component.component === 'Listview' || component.component === 'Kanban';
let path = null;
if (isListviewOrKanbaComponent) {
path = `components.${componentName}`;
} else if (isFromComponent) {
const basePath = `components.${componentName}.${option[0]}`;
useResolveStore.getState().actions.addAppSuggestions({
[basePath]: option[1],
});
shouldUpdateResolvedRefsOfHints.push({ hint: basePath, newRef: componentData[option[1]] });
} else {
path = `components.${componentName}.${option[0]}`;
}
@ -405,7 +412,7 @@ export async function executeActionsForEventId(_ref, eventId, events = [], mode,
const filteredEvents = events?.filter((event) => event?.event.eventId === eventId)?.sort((a, b) => a.index - b.index);
for (const event of filteredEvents) {
await executeAction(_ref, event.event, mode, customVariables); // skipcq: JS-0032
await executeActionWithDebounce(_ref, event.event, mode, customVariables);
}
}
@ -745,9 +752,8 @@ export async function onEvent(_ref, eventName, events, options = {}, mode = 'edi
const { customVariables } = options;
if (eventName === 'onPageLoad') {
return _.debounce(async () => {
await executeActionsForEventId(_ref, 'onPageLoad', events, mode, customVariables);
}, 10);
// for onPageLoad events, we need to execute the actions after the page is loaded
handleLowPriorityWork(() => executeActionsForEventId(_ref, 'onPageLoad', events, mode, customVariables));
}
if (eventName === 'onTrigger') {
@ -1221,6 +1227,16 @@ export function runQuery(
});
resolve(data);
onEvent(_self, 'onDataQueryFailure', queryEvents);
const toUpdateRefs = [
{ hint: `queries.${queryName}.isLoading`, newRef: false },
{
hint: `queries.${queryName}.data`,
newRef: [],
},
];
useResolveStore.getState().actions.updateResolvedRefsOfHints(toUpdateRefs);
if (mode !== 'view') {
const err = query.kind == 'tooljetdb' ? data?.error || data : data;
toast.error(err?.message ? err?.message : 'Something went wrong');
@ -1652,7 +1668,7 @@ export const cloneComponents = (
});
};
const getAllChildComponents = (allComponents, parentId) => {
export const getAllChildComponents = (allComponents, parentId) => {
const childComponents = [];
Object.keys(allComponents).forEach((componentId) => {
@ -1660,7 +1676,8 @@ const getAllChildComponents = (allComponents, parentId) => {
const isParentTabORCalendar =
allComponents[parentId]?.component?.component === 'Tabs' ||
allComponents[parentId]?.component?.component === 'Calendar';
allComponents[parentId]?.component?.component === 'Calendar' ||
allComponents[parentId]?.component?.component === 'Kanban';
if (componentParentId && isParentTabORCalendar) {
const childComponent = allComponents[componentId];

View file

@ -2,7 +2,7 @@ import { useResolveStore } from '@/_stores/resolverStore';
import _ from 'lodash';
export function validateMultilineCode(code) {
const reservedKeyword = ['app', 'window', 'this']; // Case-sensitive reserved keywords
const reservedKeyword = ['app', 'this']; // Case-sensitive reserved keywords except 'window'
const keywordRegex = new RegExp(`\\b(${reservedKeyword.join('|')})\\b`, 'i');
let inString = false;
let inComment = false;
@ -33,6 +33,18 @@ export function validateMultilineCode(code) {
// If we are not within a string or a comment, check for keywords
if (!inString && !inComment) {
// Special handling for 'window'
if (code.substring(i, i + 6) === 'window' && (code[i + 6] === undefined || code[i + 6] !== '.')) {
return {
status: 'failed',
data: {
message: `Code contains reserved keyword 'window'`,
description:
'Cannot resolve code with reserved keyword "window" in it unless it is followed by a dot. Please remove it and try again.',
},
};
}
const restOfCode = code.substring(i);
const match = restOfCode.match(keywordRegex);

View file

@ -1 +1 @@
2.50.6
2.50.7

View file

@ -108,13 +108,12 @@ export class ComponentsService {
return acc;
}, {});
// Update the component with merged data
await manager.update(Component, componentId, newComponentsData);
return;
} else {
// Update the component directly if definition is not changed
await manager.update(Component, componentId, component);
}
await manager.update(Component, componentId, component);
return;
}
}, appVersionId);
}