fix: List Widget shows cyclic dependency error when children widgets with bindings in action are deleted (#8942)

* fix list widget dynamic trigger path issue

* Update app/client/src/sagas/WidgetOperationUtils.ts

Co-authored-by: Hetu Nandu <hetunandu@gmail.com>

Co-authored-by: root <root@DESKTOP-9GENCK0.localdomain>
Co-authored-by: Hetu Nandu <hetunandu@gmail.com>
This commit is contained in:
Pawan Kumar 2021-11-08 17:52:41 +05:30 committed by GitHub
parent 229412e19d
commit 4005f23baa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 175 additions and 42 deletions

View File

@ -14,7 +14,7 @@ import {
import { GridDefaults } from "constants/WidgetConstants";
import { ENTITY_TYPE } from "entities/AppsmithConsole";
import LOG_TYPE from "entities/AppsmithConsole/logtype";
import { flattenDeep, omit, remove } from "lodash";
import { flattenDeep, omit } from "lodash";
import {
CanvasWidgetsReduxState,
FlattenedWidgetProps,
@ -26,8 +26,10 @@ import AppsmithConsole from "utils/AppsmithConsole";
import { WidgetProps } from "widgets/BaseWidget";
import { clearEvalPropertyCacheOfWidget } from "./EvaluationsSaga";
import { getSelectedWidget, getWidget, getWidgets } from "./selectors";
import { getParentWithEnhancementFn } from "./WidgetEnhancementHelpers";
import { getAllWidgetsInTree } from "./WidgetOperationUtils";
import {
getAllWidgetsInTree,
updateListWidgetPropertiesOnChildDelete,
} from "./WidgetOperationUtils";
import { showUndoRedoToast } from "utils/replayHelpers";
import WidgetFactory from "utils/WidgetFactory";
const WidgetTypes = WidgetFactory.widgetTypes;
@ -40,42 +42,6 @@ type WidgetDeleteTabChild = {
widgetId: string;
};
/**
* this saga clears out the enhancementMap, template and dynamicBindingPathList when a child
* is deleted in list widget
*
* @param widgets
* @param widgetId
* @param widgetName
* @param parentId
*/
function* updateListWidgetPropertiesOnChildDelete(
widgets: CanvasWidgetsReduxState,
widgetId: string,
widgetName: string,
) {
const clone = JSON.parse(JSON.stringify(widgets));
const parentWithEnhancementFn = getParentWithEnhancementFn(widgetId, clone);
if (parentWithEnhancementFn?.type === "LIST_WIDGET") {
const listWidget = parentWithEnhancementFn;
// delete widget in template of list
if (listWidget && widgetName in listWidget.template) {
listWidget.template[widgetName] = undefined;
}
// delete dynamic binding path if any
remove(listWidget?.dynamicBindingPathList || [], (path: any) =>
path.key.startsWith(`template.${widgetName}`),
);
return clone;
}
return clone;
}
function* deleteTabChildSaga(
deleteChildTabAction: ReduxAction<WidgetDeleteTabChild>,
) {
@ -177,8 +143,7 @@ function* getUpdatedDslAfterDeletingWidget(widgetId: string, parentId: string) {
yield call(clearEvalPropertyCacheOfWidget, widgetName);
let finalWidgets: CanvasWidgetsReduxState = yield call(
updateListWidgetPropertiesOnChildDelete,
let finalWidgets: CanvasWidgetsReduxState = updateListWidgetPropertiesOnChildDelete(
widgets,
widgetId,
widgetName,

View File

@ -4,6 +4,7 @@ import {
handleSpecificCasesWhilePasting,
doesTriggerPathsContainPropertyPath,
checkIfPastingIntoListWidget,
updateListWidgetPropertiesOnChildDelete,
} from "./WidgetOperationUtils";
describe("WidgetOperationSaga", () => {
@ -456,4 +457,128 @@ describe("WidgetOperationSaga", () => {
expect(result?.type).toStrictEqual("LIST_WIDGET");
});
it("should return widgets after executing updateListWidgetPropertiesOnChildDelete", () => {
const result = updateListWidgetPropertiesOnChildDelete(
{
list1: {
widgetId: "list1",
type: "LIST_WIDGET",
widgetName: "List1",
parentId: "0",
renderMode: "CANVAS",
parentColumnSpace: 2,
parentRowSpace: 3,
leftColumn: 2,
rightColumn: 3,
topRow: 1,
bottomRow: 3,
isLoading: false,
listData: [],
version: 16,
disablePropertyPane: false,
template: {},
enhancements: {},
dynamicBindingPathList: [{ key: "template.ButtonWidget1.text" }],
dynamicTriggerPathList: [
{
key: "template.ButtonWidget1.onClick",
},
],
},
buttonWidget1: {
type: "BUTTON_WIDGET",
widgetId: "buttonWidget1",
widgetName: "buttonWidget1",
version: 16,
parentColumnSpace: 2,
parentRowSpace: 3,
leftColumn: 2,
rightColumn: 3,
topRow: 1,
bottomRow: 3,
renderMode: "CANVAS",
isLoading: false,
parentId: "list1",
},
0: {
type: "CANVAS_WIDGET",
widgetId: "0",
widgetName: "MainContainer",
version: 16,
parentColumnSpace: 2,
parentRowSpace: 3,
leftColumn: 2,
rightColumn: 3,
topRow: 1,
bottomRow: 3,
renderMode: "CANVAS",
isLoading: false,
parentId: "list1",
},
},
"buttonWidget1",
"ButtonWidget1",
);
const expected = updateListWidgetPropertiesOnChildDelete(
{
list1: {
widgetId: "list1",
type: "LIST_WIDGET",
widgetName: "List1",
parentId: "0",
renderMode: "CANVAS",
parentColumnSpace: 2,
parentRowSpace: 3,
leftColumn: 2,
rightColumn: 3,
topRow: 1,
bottomRow: 3,
isLoading: false,
listData: [],
version: 16,
disablePropertyPane: false,
template: {},
enhancements: {},
dynamicBindingPathList: [],
dynamicTriggerPathList: [],
},
buttonWidget1: {
type: "BUTTON_WIDGET",
widgetId: "buttonWidget1",
widgetName: "buttonWidget1",
version: 16,
parentColumnSpace: 2,
parentRowSpace: 3,
leftColumn: 2,
rightColumn: 3,
topRow: 1,
bottomRow: 3,
renderMode: "CANVAS",
isLoading: false,
parentId: "list1",
},
0: {
type: "CANVAS_WIDGET",
widgetId: "0",
widgetName: "MainContainer",
version: 16,
parentColumnSpace: 2,
parentRowSpace: 3,
leftColumn: 2,
rightColumn: 3,
topRow: 1,
bottomRow: 3,
renderMode: "CANVAS",
isLoading: false,
parentId: "list1",
},
},
"buttonWidget1",
"ButtonWidget1",
);
expect(result).toStrictEqual(expected);
});
});

View File

@ -4,7 +4,7 @@ import {
getWidgetMetaProps,
getWidgets,
} from "./selectors";
import _, { isString } from "lodash";
import _, { isString, remove } from "lodash";
import {
GridDefaults,
MAIN_CONTAINER_WIDGET_ID,
@ -29,6 +29,7 @@ import {
} from "utils/DynamicBindingUtils";
import { getNextEntityName } from "utils/AppsmithUtils";
import WidgetFactory from "utils/WidgetFactory";
import { getParentWithEnhancementFn } from "./WidgetEnhancementHelpers";
export interface CopiedWidgetGroup {
widgetId: string;
@ -817,3 +818,45 @@ export function* getParentWidgetIdForGrouping(
return pastingIntoWidgetId;
}
/**
* this saga clears out the enhancementMap, template, dynamicBindingPathList and dynamicTriggerPathList when a child
* is deleted in list widget
*
* @param widgets
* @param widgetId
* @param widgetName
* @param parentId
*/
export function updateListWidgetPropertiesOnChildDelete(
widgets: CanvasWidgetsReduxState,
widgetId: string,
widgetName: string,
) {
const clone = JSON.parse(JSON.stringify(widgets));
const parentWithEnhancementFn = getParentWithEnhancementFn(widgetId, clone);
if (parentWithEnhancementFn?.type === "LIST_WIDGET") {
const listWidget = parentWithEnhancementFn;
// delete widget in template of list
if (listWidget && widgetName in listWidget.template) {
listWidget.template[widgetName] = undefined;
}
// delete dynamic binding path if any
remove(listWidget?.dynamicBindingPathList || [], (path: any) =>
path.key.startsWith(`template.${widgetName}`),
);
// delete dynamic trigger path if any
remove(listWidget?.dynamicTriggerPathList || [], (path: any) =>
path.key.startsWith(`template.${widgetName}`),
);
return clone;
}
return clone;
}