feat: reset meta widgets (#18809)

* Initial commit

* reset meta widgets

* Annotate code

* Rehydrate meta values for meta widgets when dirty

* Undo rehydration logic
This commit is contained in:
Favour Ohanekwu 2022-12-15 09:23:09 +01:00 committed by GitHub
parent a2c878e8c7
commit 8ed9d20547
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 132 additions and 58 deletions

View File

@ -29,12 +29,12 @@ export const updateWidgetMetaPropAndEval = (
export type ResetWidgetMetaPayload = {
widgetId: string;
evaluatedWidget: DataTreeWidget;
evaluatedWidget: DataTreeWidget | undefined;
};
export const resetWidgetMetaProperty = (
widgetId: string,
evaluatedWidget: DataTreeWidget,
evaluatedWidget: DataTreeWidget | undefined,
): BatchAction<ResetWidgetMetaPayload> => {
return batchAction({
type: ReduxActionTypes.RESET_WIDGET_META,

View File

@ -12,7 +12,7 @@ import {
} from "@appsmith/constants/ReduxActionConstants";
import produce from "immer";
import { EvalMetaUpdates } from "workers/common/DataTreeEvaluator/types";
import { klona } from "klona";
import { getMetaWidgetResetObj } from "./metaReducerUtils";
export type WidgetMetaState = Record<string, unknown>;
export type MetaState = Record<string, WidgetMetaState>;
@ -104,25 +104,7 @@ export const metaReducer = createReducer(initialState, {
if (widgetId in state) {
// only reset widgets whose meta properties were changed.
// reset widget: sets the meta values to current default values of widget
const resetMetaObj: WidgetMetaState = {};
// evaluatedWidget is widget data inside dataTree, this will have latest default values of widget
if (evaluatedWidget) {
const { propertyOverrideDependency } = evaluatedWidget;
// propertyOverrideDependency has defaultProperty name for each meta property of widget
Object.entries(propertyOverrideDependency).map(
([propertyName, dependency]) => {
const defaultPropertyValue =
dependency.DEFAULT && evaluatedWidget[dependency.DEFAULT];
if (defaultPropertyValue !== undefined) {
// cloning data to avoid mutation
resetMetaObj[propertyName] = klona(defaultPropertyValue);
}
},
);
}
return { ...state, [widgetId]: resetMetaObj };
state = { ...state, [widgetId]: getMetaWidgetResetObj(evaluatedWidget) };
}
return state;
},

View File

@ -0,0 +1,27 @@
import { DataTreeWidget } from "entities/DataTree/dataTreeFactory";
import { klona } from "klona";
import { WidgetMetaState } from ".";
export function getMetaWidgetResetObj(
evaluatedWidget: DataTreeWidget | undefined,
) {
// reset widget: sets the meta values to current default values of widget
const resetMetaObj: WidgetMetaState = {};
// evaluatedWidget is widget data inside dataTree, this will have latest default values of widget
if (evaluatedWidget) {
const { propertyOverrideDependency } = evaluatedWidget;
// propertyOverrideDependency has defaultProperty name for each meta property of widget
Object.entries(propertyOverrideDependency).map(
([propertyName, dependency]) => {
const defaultPropertyValue =
dependency.DEFAULT && evaluatedWidget[dependency.DEFAULT];
if (defaultPropertyValue !== undefined) {
// cloning data to avoid mutation
resetMetaObj[propertyName] = klona(defaultPropertyValue);
}
},
);
}
return resetMetaObj;
}

View File

@ -10,7 +10,7 @@ import {
CanvasWidgetsReduxState,
FlattenedWidgetProps,
} from "reducers/entityReducers/canvasWidgetsReducer";
import { getWidget, getWidgets } from "./selectors";
import { getWidget, getWidgets, getWidgetsMeta } from "./selectors";
import {
actionChannel,
all,
@ -85,7 +85,7 @@ import {
doesTriggerPathsContainPropertyPath,
getParentBottomRowAfterAddingWidget,
getParentWidgetIdForPasting,
getWidgetChildren,
getWidgetDescendantToReset,
groupWidgetsIntoContainer,
handleSpecificCasesWhilePasting,
getSelectedWidgetWhenPasting,
@ -144,6 +144,7 @@ import { builderURL } from "RouteBuilder";
import history from "utils/history";
import { updateMultipleWidgetProperties } from "actions/widgetActions";
import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions";
import { MetaState } from "reducers/entityReducers/metaReducer";
export function* updateAllChildCanvasHeights(
currentContainerLikeWidgetId: string,
@ -814,10 +815,12 @@ function* resetChildrenMetaSaga(action: ReduxAction<{ widgetId: string }>) {
const { widgetId: parentWidgetId } = action.payload;
const canvasWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
const evaluatedDataTree: DataTree = yield select(getDataTree);
const childrenList = getWidgetChildren(
const widgetsMeta: MetaState = yield select(getWidgetsMeta);
const childrenList = getWidgetDescendantToReset(
canvasWidgets,
parentWidgetId,
evaluatedDataTree,
widgetsMeta,
);
for (const childIndex in childrenList) {

View File

@ -4,7 +4,7 @@ import {
getWidgetMetaProps,
getWidgets,
} from "./selectors";
import _, { isString, remove } from "lodash";
import _, { find, isString, reduce, remove } from "lodash";
import {
CONTAINER_GRID_PADDING,
GridDefaults,
@ -54,6 +54,7 @@ import { getBottomRowAfterReflow } from "utils/reflowHookUtils";
import { DataTreeWidget } from "entities/DataTree/dataTreeFactory";
import { isWidget } from "workers/Evaluation/evaluationUtils";
import { CANVAS_DEFAULT_MIN_HEIGHT_PX } from "constants/AppConstants";
import { MetaState } from "reducers/entityReducers/metaReducer";
export interface CopiedWidgetGroup {
widgetId: string;
@ -301,53 +302,114 @@ export function getWidgetChildrenIds(
}
return childrenIds;
}
function sortWidgetsMetaByParent(widgetsMeta: MetaState, parentId: string) {
return reduce(
widgetsMeta,
function(
result: {
childrenWidgetsMeta: MetaState;
otherWidgetsMeta: MetaState;
},
currentWidgetMeta,
key,
) {
return key.startsWith(parentId + "_")
? {
...result,
childrenWidgetsMeta: {
...result.childrenWidgetsMeta,
[key]: currentWidgetMeta,
},
}
: {
...result,
otherWidgetsMeta: {
...result.otherWidgetsMeta,
[key]: currentWidgetMeta,
},
};
},
{
childrenWidgetsMeta: {},
otherWidgetsMeta: {},
},
);
}
export type DescendantWidgetMap = {
id: string;
// To accomodate metaWidgets which might not be present on the evalTree, evaluatedWidget might be undefined
evaluatedWidget: DataTreeWidget | undefined;
};
export type ChildrenWidgetMap = { id: string; evaluatedWidget: DataTreeWidget };
/**
* getWidgetChildren: It gets all the child widgets of given widget's id with evaluated values
*
* As part of widget's descendant, we add both children and metaWidgets.
* children are assessed from "widget.children"
* metaWidgets are assessed from the metaState, since we care about only metawidgets whose values have been changed.
* NB: metaWidgets id start with parentId + "_"
*/
export function getWidgetChildren(
export function getWidgetDescendantToReset(
canvasWidgets: CanvasWidgetsReduxState,
widgetId: string,
evaluatedDataTree: DataTree,
): ChildrenWidgetMap[] {
const childrenList: ChildrenWidgetMap[] = [];
widgetsMeta: MetaState,
): DescendantWidgetMap[] {
const descendantList: DescendantWidgetMap[] = [];
const widget = _.get(canvasWidgets, widgetId);
// When a form widget tries to resetChildrenMetaProperties
// But one or more of its container like children
// have just been deleted, widget can be undefined
if (widget === undefined) {
return [];
const sortedWidgetsMeta = sortWidgetsMetaByParent(widgetsMeta, widgetId);
for (const childMetaWidgetId of Object.keys(
sortedWidgetsMeta.childrenWidgetsMeta,
)) {
const evaluatedChildWidget = find(evaluatedDataTree, function(entity) {
return isWidget(entity) && entity.widgetId === childMetaWidgetId;
}) as DataTreeWidget | undefined;
descendantList.push({
id: childMetaWidgetId,
evaluatedWidget: evaluatedChildWidget,
});
const grandChildren = getWidgetDescendantToReset(
canvasWidgets,
childMetaWidgetId,
evaluatedDataTree,
sortedWidgetsMeta.otherWidgetsMeta,
);
if (grandChildren.length) {
descendantList.push(...grandChildren);
}
}
const { children = [] } = widget;
if (children && children.length) {
for (const childIndex in children) {
if (children.hasOwnProperty(childIndex)) {
const childWidgetId = children[childIndex];
if (widget) {
const { children = [] } = widget;
if (children && children.length) {
for (const childIndex in children) {
if (children.hasOwnProperty(childIndex)) {
const childWidgetId = children[childIndex];
const childCanvasWidget = _.get(canvasWidgets, childWidgetId);
const childWidgetName = childCanvasWidget.widgetName;
const childWidget = evaluatedDataTree[childWidgetName];
if (isWidget(childWidget)) {
childrenList.push({
id: childWidgetId,
evaluatedWidget: childWidget,
});
const grandChildren = getWidgetChildren(
canvasWidgets,
childWidgetId,
evaluatedDataTree,
);
if (grandChildren.length) {
childrenList.push(...grandChildren);
const childCanvasWidget = _.get(canvasWidgets, childWidgetId);
const childWidgetName = childCanvasWidget.widgetName;
const childWidget = evaluatedDataTree[childWidgetName];
if (isWidget(childWidget)) {
descendantList.push({
id: childWidgetId,
evaluatedWidget: childWidget,
});
const grandChildren = getWidgetDescendantToReset(
canvasWidgets,
childWidgetId,
evaluatedDataTree,
sortedWidgetsMeta.otherWidgetsMeta,
);
if (grandChildren.length) {
descendantList.push(...grandChildren);
}
}
}
}
}
}
return childrenList;
return descendantList;
}
export const getParentWidgetIdForPasting = function*(