diff --git a/app/client/package.json b/app/client/package.json index 1a4652a73863..8ae1f9249643 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -140,7 +140,6 @@ "fusioncharts": "^3.18.0", "graphql": "^16.8.1", "history": "^4.10.1", - "immer": "^9.0.6", "interweave": "^12.7.2", "interweave-autolink": "^4.4.2", "js-regex-pl": "^1.0.1", @@ -161,6 +160,7 @@ "mixpanel-browser": "^2.55.1", "moment": "2.29.4", "moment-timezone": "^0.5.35", + "mutative": "^1.1.0", "nanoid": "^2.0.4", "node-forge": "^1.3.0", "object-hash": "^3.0.0", @@ -331,6 +331,7 @@ "eslint-plugin-testing-library": "^6.2.0", "factory.ts": "^0.5.1", "husky": "^8.0.0", + "immer": "^9.0.6", "jest": "^29.6.1", "jest-canvas-mock": "^2.3.1", "jest-environment-jsdom": "^29.6.1", diff --git a/app/client/src/WidgetProvider/factory/index.tsx b/app/client/src/WidgetProvider/factory/index.tsx index 26e55f2a3f9f..f863b6530d88 100644 --- a/app/client/src/WidgetProvider/factory/index.tsx +++ b/app/client/src/WidgetProvider/factory/index.tsx @@ -35,7 +35,7 @@ import { import type { RegisteredWidgetFeatures } from "../../utils/WidgetFeatures"; import type { SetterConfig } from "entities/AppTheming"; import { freeze, memoize } from "./decorators"; -import produce from "immer"; +import { create } from "mutative"; import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer"; import type { CopiedWidgetData, @@ -418,7 +418,7 @@ class WidgetFactory { if (dynamicProperties && dynamicProperties.length) { addPropertyConfigIds(dynamicProperties, false); - section = produce(section, (draft) => { + section = create(section, (draft) => { draft.children = [...dynamicProperties, ...section.children]; }); } diff --git a/app/client/src/ce/pages/Editor/Explorer/hooks.tsx b/app/client/src/ce/pages/Editor/Explorer/hooks.tsx index c9174bf94b70..f2d30d8779a1 100644 --- a/app/client/src/ce/pages/Editor/Explorer/hooks.tsx +++ b/app/client/src/ce/pages/Editor/Explorer/hooks.tsx @@ -6,7 +6,7 @@ import type { Datasource } from "entities/Datasource"; import { isStoredDatasource } from "entities/Action"; import type { WidgetProps } from "widgets/BaseWidget"; import log from "loglevel"; -import produce from "immer"; +import { create } from "mutative"; import type { CanvasStructure } from "reducers/uiReducers/pageCanvasStructureReducer"; import { getActions, getDatasources } from "ee/selectors/entitiesSelector"; import type { ActionData } from "ee/reducers/entityReducers/actionsReducer"; @@ -184,7 +184,7 @@ export const useActions = (searchKeyword?: string) => { return useMemo(() => { if (searchKeyword) { const start = performance.now(); - const filteredActions = produce(actions, (draft) => { + const filteredActions = create(actions, (draft) => { for (const [key, value] of Object.entries(draft)) { if (pageIds.includes(key)) { draft[key] = value; @@ -225,7 +225,7 @@ export const useWidgets = (searchKeyword?: string) => { return useMemo(() => { if (searchKeyword && pageCanvasStructures) { const start = performance.now(); - const filteredDSLs = produce(pageCanvasStructures, (draft) => { + const filteredDSLs = create(pageCanvasStructures, (draft) => { for (const [key, value] of Object.entries(draft)) { if (pageIds.includes(key)) { draft[key] = value; @@ -256,7 +256,7 @@ export const usePageIds = (searchKeyword?: string) => { return useMemo(() => { if (searchKeyword) { - const filteredPages = produce(pages, (draft) => { + const filteredPages = create(pages, (draft) => { draft.forEach((page, index) => { const searchMatches = page.pageName.toLowerCase().indexOf(searchKeyword.toLowerCase()) > diff --git a/app/client/src/ce/reducers/entityReducers/jsActionsReducer.tsx b/app/client/src/ce/reducers/entityReducers/jsActionsReducer.tsx index 60aad9fde696..4d267d75d041 100644 --- a/app/client/src/ce/reducers/entityReducers/jsActionsReducer.tsx +++ b/app/client/src/ce/reducers/entityReducers/jsActionsReducer.tsx @@ -6,7 +6,7 @@ import { ReduxActionErrorTypes, } from "ee/constants/ReduxActionConstants"; import { set, keyBy, findIndex, unset } from "lodash"; -import produce from "immer"; +import { create } from "mutative"; import { klona } from "klona"; export const initialState: JSCollectionDataState = []; @@ -400,7 +400,7 @@ export const handlers = { }> >, ) => { - return produce(state, (draft) => { + return create(state, (draft) => { const CollectionUpdateSearch = keyBy(action.payload, "collectionId"); const actionUpdateSearch = keyBy(action.payload, "id"); diff --git a/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx b/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx index 55c6327476ed..eaf285c6fe9e 100644 --- a/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx +++ b/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx @@ -22,7 +22,7 @@ import { defaultNavigationSetting, defaultThemeSetting, } from "constants/AppConstants"; -import produce from "immer"; +import { create } from "mutative"; import { isEmpty } from "lodash"; import type { ApplicationPayload } from "entities/Application"; import { gitConnectSuccess, type GitConnectSuccessPayload } from "git"; @@ -530,7 +530,7 @@ export const handlers = { state: ApplicationsReduxState, action: ReduxAction, ) => { - return produce(state, (draftState: ApplicationsReduxState) => { + return create(state, (draftState: ApplicationsReduxState) => { draftState.isUploadingNavigationLogo = false; if ( diff --git a/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/index.tsx b/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/index.tsx index aba8a7770462..db17e2747976 100644 --- a/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/index.tsx +++ b/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/index.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; -import produce from "immer"; +import { create } from "mutative"; import { noop, set } from "lodash"; import { CommonControls } from "./CommonControls"; @@ -179,7 +179,7 @@ function WidgetQueryGeneratorForm(props: Props) { setPristine(false); setConfig( - produce(config, (draftConfig) => { + create(config, (draftConfig) => { if ( property === "datasource" || (typeof property === "object" && diff --git a/app/client/src/components/propertyControls/ButtonTabControl.tsx b/app/client/src/components/propertyControls/ButtonTabControl.tsx index 7206d8b20ea4..db37f6645307 100644 --- a/app/client/src/components/propertyControls/ButtonTabControl.tsx +++ b/app/client/src/components/propertyControls/ButtonTabControl.tsx @@ -3,7 +3,7 @@ import type { ControlData, ControlProps } from "./BaseControl"; import BaseControl from "./BaseControl"; import type { ToggleGroupOption } from "@appsmith/ads"; import { ToggleButtonGroup } from "@appsmith/ads"; -import produce from "immer"; +import { create } from "mutative"; import type { DSEventDetail } from "utils/AppsmithUtils"; import { DSEventTypes, @@ -62,7 +62,7 @@ class ButtonTabControl extends BaseControl { isUpdatedViaKeyboard, ); } else { - const updatedValues: string[] = produce(values, (draft: string[]) => { + const updatedValues: string[] = create(values, (draft: string[]) => { draft.push(value); }); diff --git a/app/client/src/index.tsx b/app/client/src/index.tsx index 4f14d5f33a5a..2ebad23bccfa 100755 --- a/app/client/src/index.tsx +++ b/app/client/src/index.tsx @@ -23,15 +23,12 @@ import "./assets/styles/index.css"; import "./polyfills"; import GlobalStyles from "globalStyles"; // enable autofreeze only in development -import { setAutoFreeze } from "immer"; + import AppErrorBoundary from "./AppErrorBoundry"; import log from "loglevel"; import { FaroErrorBoundary } from "@grafana/faro-react"; import { isTracingEnabled } from "instrumentation/utils"; -const shouldAutoFreeze = process.env.NODE_ENV === "development"; - -setAutoFreeze(shouldAutoFreeze); runSagaMiddleware(); appInitializer(); diff --git a/app/client/src/reducers/entityReducers/datasourceReducer.ts b/app/client/src/reducers/entityReducers/datasourceReducer.ts index 1d20f11e5b94..f0c3d8f88153 100644 --- a/app/client/src/reducers/entityReducers/datasourceReducer.ts +++ b/app/client/src/reducers/entityReducers/datasourceReducer.ts @@ -13,7 +13,7 @@ import type { import { ToastMessageType } from "entities/Datasource"; import { TEMP_DATASOURCE_ID } from "constants/Datasource"; import type { DropdownOption } from "@appsmith/ads-old"; -import produce from "immer"; +import { create } from "mutative"; import { assign } from "lodash"; export interface DatasourceDataState { @@ -466,7 +466,7 @@ const datasourceReducer = createReducer(initialState, { state: DatasourceDataState, action: ReduxAction, ): DatasourceDataState => { - return produce(state, (draftState) => { + return create(state, (draftState) => { draftState.loading = false; draftState.list.forEach((datasource) => { if (datasource.id === action.payload.id) { @@ -656,7 +656,7 @@ const datasourceReducer = createReducer(initialState, { [ReduxActionTypes.FETCH_GSHEET_SPREADSHEETS]: ( state: DatasourceDataState, ) => { - return produce(state, (draftState) => { + return create(state, (draftState) => { draftState.gsheetStructure.isFetchingSpreadsheets = true; }); }, @@ -664,7 +664,7 @@ const datasourceReducer = createReducer(initialState, { state: DatasourceDataState, action: ReduxAction<{ id: string; data: DropdownOption[] }>, ) => { - return produce(state, (draftState) => { + return create(state, (draftState) => { draftState.gsheetStructure.spreadsheets[action.payload.id] = { value: action.payload.data, }; @@ -676,7 +676,7 @@ const datasourceReducer = createReducer(initialState, { state: DatasourceDataState, action: ReduxAction<{ id: string; error: string }>, ) => { - return produce(state, (draftState) => { + return create(state, (draftState) => { draftState.gsheetStructure.spreadsheets[action.payload.id] = { error: action.payload.error, }; @@ -685,7 +685,7 @@ const datasourceReducer = createReducer(initialState, { }); }, [ReduxActionTypes.FETCH_GSHEET_SHEETS]: (state: DatasourceDataState) => { - return produce(state, (draftState) => { + return create(state, (draftState) => { draftState.gsheetStructure.isFetchingSheets = true; }); }, @@ -693,7 +693,7 @@ const datasourceReducer = createReducer(initialState, { state: DatasourceDataState, action: ReduxAction<{ id: string; data: DropdownOption[] }>, ) => { - return produce(state, (draftState) => { + return create(state, (draftState) => { draftState.gsheetStructure.sheets[action.payload.id] = { value: action.payload.data, }; @@ -704,7 +704,7 @@ const datasourceReducer = createReducer(initialState, { state: DatasourceDataState, action: ReduxAction<{ id: string; error: string }>, ) => { - return produce(state, (draftState) => { + return create(state, (draftState) => { draftState.gsheetStructure.sheets[action.payload.id] = { error: action.payload.error, }; @@ -712,7 +712,7 @@ const datasourceReducer = createReducer(initialState, { }); }, [ReduxActionTypes.FETCH_GSHEET_COLUMNS]: (state: DatasourceDataState) => { - return produce(state, (draftState) => { + return create(state, (draftState) => { draftState.gsheetStructure.isFetchingColumns = true; }); }, @@ -720,7 +720,7 @@ const datasourceReducer = createReducer(initialState, { state: DatasourceDataState, action: ReduxAction<{ id: string; data: DropdownOption[] }>, ) => { - return produce(state, (draftState) => { + return create(state, (draftState) => { draftState.gsheetStructure.columns[action.payload.id] = { value: action.payload.data, }; @@ -731,7 +731,7 @@ const datasourceReducer = createReducer(initialState, { state: DatasourceDataState, action: ReduxAction<{ id: string; error: string }>, ) => { - return produce(state, (draftState) => { + return create(state, (draftState) => { draftState.gsheetStructure.columns[action.payload.id] = { error: action.payload.error, }; diff --git a/app/client/src/reducers/entityReducers/metaReducer/index.ts b/app/client/src/reducers/entityReducers/metaReducer/index.ts index 26f0db37810b..810201b449ef 100644 --- a/app/client/src/reducers/entityReducers/metaReducer/index.ts +++ b/app/client/src/reducers/entityReducers/metaReducer/index.ts @@ -11,7 +11,7 @@ import { ReduxActionTypes, WidgetReduxActionTypes, } from "ee/constants/ReduxActionConstants"; -import produce from "immer"; +import { create } from "mutative"; import type { EvalMetaUpdates } from "ee/workers/common/DataTreeEvaluator/types"; import { getMetaWidgetResetObj, @@ -38,7 +38,7 @@ export const metaReducer = createReducer(initialState, { state: MetaState, action: ReduxAction, ) => { - const nextState = produce(state, (draftMetaState) => { + const nextState = create(state, (draftMetaState) => { set( draftMetaState, `${action.payload.widgetId}.${action.payload.propertyName}`, @@ -54,7 +54,7 @@ export const metaReducer = createReducer(initialState, { state: MetaState, action: ReduxAction, ) => { - const nextState = produce(state, (draftMetaState) => { + const nextState = create(state, (draftMetaState) => { const { batchMetaUpdates } = action.payload; batchMetaUpdates.forEach(({ propertyName, propertyValue, widgetId }) => { @@ -70,7 +70,7 @@ export const metaReducer = createReducer(initialState, { state: MetaState, action: ReduxAction, ) => { - const nextState = produce(state, (draftMetaState) => { + const nextState = create(state, (draftMetaState) => { set( draftMetaState, `${action.payload.widgetId}.${action.payload.propertyName}`, diff --git a/app/client/src/reducers/entityReducers/metaReducer/metaReducerUtils.ts b/app/client/src/reducers/entityReducers/metaReducer/metaReducerUtils.ts index 43ca882f5101..6bf8aeac0c3a 100644 --- a/app/client/src/reducers/entityReducers/metaReducer/metaReducerUtils.ts +++ b/app/client/src/reducers/entityReducers/metaReducer/metaReducerUtils.ts @@ -6,7 +6,7 @@ import type { import type { MetaState, WidgetMetaState } from "."; import type { ReduxAction } from "actions/ReduxActionTypes"; import type { EvalMetaUpdates } from "ee/workers/common/DataTreeEvaluator/types"; -import produce from "immer"; +import { create } from "mutative"; import { set, unset } from "lodash"; import { klonaRegularWithTelemetry } from "utils/helpers"; @@ -82,7 +82,7 @@ export function getNextMetaStateWithUpdates( if (!evalMetaUpdates.length) return state; // if metaObject is updated in dataTree we also update meta values, to keep meta state in sync. - const newMetaState = produce(state, (draftMetaState) => { + const newMetaState = create(state, (draftMetaState) => { evalMetaUpdates.forEach(({ metaPropertyPath, value, widgetId }) => { set(draftMetaState, [widgetId, ...metaPropertyPath], value); }); diff --git a/app/client/src/reducers/evaluationReducers/treeReducer.ts b/app/client/src/reducers/evaluationReducers/treeReducer.ts index 53f6c5034dbe..a8be85d9f1dd 100644 --- a/app/client/src/reducers/evaluationReducers/treeReducer.ts +++ b/app/client/src/reducers/evaluationReducers/treeReducer.ts @@ -1,10 +1,9 @@ import type { ReduxAction } from "actions/ReduxActionTypes"; import { ReduxActionTypes } from "ee/constants/ReduxActionConstants"; -import { applyChange } from "deep-diff"; +import { applyChange, type Diff } from "deep-diff"; import type { DataTree } from "entities/DataTree/dataTreeTypes"; import { createImmerReducer } from "utils/ReducerUtils"; import * as Sentry from "@sentry/react"; -import type { DiffWithNewTreeState } from "workers/Evaluation/helpers"; export type EvaluatedTreeState = DataTree; @@ -15,7 +14,7 @@ const evaluatedTreeReducer = createImmerReducer(initialState, { state: EvaluatedTreeState, action: ReduxAction<{ dataTree: DataTree; - updates: DiffWithNewTreeState[]; + updates: Diff[]; removedPaths: [string]; }>, ) => { @@ -27,15 +26,11 @@ const evaluatedTreeReducer = createImmerReducer(initialState, { for (const update of updates) { try { - if (update.kind === "newTree") { - return update.rhs; - } else { - if (!update.path || update.path.length === 0) { - continue; - } - - applyChange(state, undefined, update); + if (!update.path || update.path.length === 0) { + continue; } + + applyChange(state, undefined, update); } catch (e) { Sentry.captureException(e, { extra: { diff --git a/app/client/src/sagas/WidgetAdditionSagas.ts b/app/client/src/sagas/WidgetAdditionSagas.ts index 7fd5792b583d..9f36040d190b 100644 --- a/app/client/src/sagas/WidgetAdditionSagas.ts +++ b/app/client/src/sagas/WidgetAdditionSagas.ts @@ -20,7 +20,7 @@ import { } from "constants/WidgetConstants"; import { toast } from "@appsmith/ads"; import type { DataTree } from "entities/DataTree/dataTreeTypes"; -import produce from "immer"; +import { create } from "mutative"; import { klona as clone } from "klona/full"; import { getWidgetMinMaxDimensionsInPixel } from "layoutSystems/autolayout/utils/flexWidgetUtils"; import { ResponsiveBehavior } from "layoutSystems/common/utils/constants"; @@ -112,7 +112,7 @@ function* getChildWidgetProps( // if (props) props.children = []; if (props) { - props = produce(props, (draft: WidgetProps) => { + props = create(props, (draft) => { if (!draft.children || !Array.isArray(draft.children)) { draft.children = []; } diff --git a/app/client/src/utils/ReducerUtils.ts b/app/client/src/utils/ReducerUtils.ts index 9a25f1dcfd0e..9a492352b847 100644 --- a/app/client/src/utils/ReducerUtils.ts +++ b/app/client/src/utils/ReducerUtils.ts @@ -1,5 +1,5 @@ import type { ReduxAction } from "actions/ReduxActionTypes"; -import produce from "immer"; +import { create } from "mutative"; export const createReducer = ( // TODO: Fix this the next time the file is edited @@ -32,7 +32,19 @@ export const createImmerReducer = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any return function reducer(state = initialState, action: ReduxAction) { if (handlers.hasOwnProperty(action.type)) { - return produce(handlers[action.type])(state, action); + if (action?.payload?.updates) { + const updates = action?.payload?.updates; + + for (const update of updates) { + if (update.kind === "newTree") { + return update.rhs; + } + } + } + + const fn = handlers[action.type]; + + return create(state, (draft) => fn(draft, action)); } else { return state; } diff --git a/app/client/src/widgets/TableWidgetV2/widget/index.tsx b/app/client/src/widgets/TableWidgetV2/widget/index.tsx index 586fcd9bc2d3..b81f652b779f 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/widget/index.tsx @@ -3,7 +3,6 @@ import log from "loglevel"; import memoizeOne from "memoize-one"; import _, { - cloneDeep, filter, isArray, isEmpty, @@ -142,6 +141,7 @@ import IconSVG from "../icon.svg"; import ThumbnailSVG from "../thumbnail.svg"; import { klonaRegularWithTelemetry } from "utils/helpers"; import HTMLCell from "../component/cellComponents/HTMLCell"; +import { objectKeys } from "@appsmith/utils"; const ReactTableComponent = lazy(async () => retryPromise(async () => import("../component")), @@ -928,16 +928,23 @@ class TableWidgetV2 extends BaseWidget { * @rahulbarwal Remove this once we remove the feature flag */ if (!TableWidgetV2.getFeatureFlag(HTML_COLUMN_TYPE_ENABLED)) { - const updatedPrimaryColumns = cloneDeep(this.props.primaryColumns); let hasHTMLColumns = false; + const { primaryColumns } = this.props; + + const updatedPrimaryColumns = objectKeys(primaryColumns).reduce( + (acc, widgetId) => { + const column = primaryColumns[widgetId]; - Object.values(updatedPrimaryColumns).forEach( - (column: ColumnProperties) => { if (column.columnType === ColumnTypes.HTML) { - column.columnType = ColumnTypes.TEXT; + acc[widgetId] = { ...column, columnType: ColumnTypes.TEXT }; hasHTMLColumns = true; + } else { + acc[widgetId] = column; } + + return acc; }, + {} as Record, ); if (hasHTMLColumns) { diff --git a/app/client/src/workers/Evaluation/__tests__/generateOpimisedUpdates.test.ts b/app/client/src/workers/Evaluation/__tests__/generateOpimisedUpdates.test.ts index 5e8b07703770..cea35fb62d9a 100644 --- a/app/client/src/workers/Evaluation/__tests__/generateOpimisedUpdates.test.ts +++ b/app/client/src/workers/Evaluation/__tests__/generateOpimisedUpdates.test.ts @@ -1,6 +1,6 @@ import type { WidgetEntity } from "ee/entities/DataTree/types"; import { applyChange } from "deep-diff"; -import produce from "immer"; +import { create } from "mutative"; import { klona } from "klona/full"; import { range } from "lodash"; import moment from "moment"; @@ -110,7 +110,7 @@ const oldState: dataTreeWithWidget = { describe("generateOptimisedUpdates", () => { describe("regular diff", () => { test("should not generate any diff when the constrainedDiffPaths is empty", () => { - const newState = produce(oldState, (draft) => { + const newState = create(oldState, (draft) => { draft.Table1.pageSize = 17; }); const updates = generateOptimisedUpdates(oldState, newState, []); @@ -119,7 +119,7 @@ describe("generateOptimisedUpdates", () => { expect(updates).toEqual([]); }); test("should not generate any diff when the constrainedDiffPaths nodes are the same ", () => { - const newState = produce(oldState, (draft) => { + const newState = create(oldState, (draft) => { //making an unrelated change draft.Table1.triggerRowSelection = true; }); @@ -131,7 +131,7 @@ describe("generateOptimisedUpdates", () => { expect(updates).toEqual([]); }); test("should generate regular diff updates when a simple property changes in the widget property segment", () => { - const newState = produce(oldState, (draft) => { + const newState = create(oldState, (draft) => { draft.Table1.pageSize = 17; }); const updates = generateOptimisedUpdates(oldState, newState, [ @@ -145,7 +145,7 @@ describe("generateOptimisedUpdates", () => { test("should generate regular diff updates when a simple property changes in the __evaluation__ segment ", () => { const validationError = "Some validation error" as unknown as EvaluationError[]; - const newState = produce(oldState, (draft) => { + const newState = create(oldState, (draft) => { draft.Table1.__evaluation__.errors.tableData = validationError; }); const updates = generateOptimisedUpdates(oldState, newState, [ @@ -162,7 +162,7 @@ describe("generateOptimisedUpdates", () => { ]); }); test("should generate a replace collection patch when the size of the collection exceeds 100 instead of generating granular updates", () => { - const newState = produce(oldState, (draft) => { + const newState = create(oldState, (draft) => { draft.Table1.tableData = largeDataSet; }); const updates = generateOptimisedUpdates(oldState, newState, [ @@ -179,10 +179,10 @@ describe("generateOptimisedUpdates", () => { }); describe("undefined value updates in a collection", () => { test("should generate replace patch when a single node is set to undefined in a collection", () => { - const statWithLargeCollection = produce(oldState, (draft) => { + const statWithLargeCollection = create(oldState, (draft) => { draft.Table1.tableData = ["a", "b"]; }); - const newStateWithAnElementDeleted = produce( + const newStateWithAnElementDeleted = create( statWithLargeCollection, (draft) => { draft.Table1.tableData = ["a", undefined]; @@ -204,10 +204,10 @@ describe("generateOptimisedUpdates", () => { ]); }); test("should generate generate regular diff updates for non undefined updates in a collection", () => { - const statWithLargeCollection = produce(oldState, (draft) => { + const statWithLargeCollection = create(oldState, (draft) => { draft.Table1.tableData = ["a", "b"]; }); - const newStateWithAnElementDeleted = produce( + const newStateWithAnElementDeleted = create( statWithLargeCollection, (draft) => { draft.Table1.tableData = ["a", "e"]; @@ -244,7 +244,7 @@ describe("generateOptimisedUpdates", () => { expect(serialisedUpdates).toEqual(JSON.stringify(additionalUpdates)); }); it("should ignore undefined updates", () => { - const oldStateWithUndefinedValues = produce(oldState, (draft) => { + const oldStateWithUndefinedValues = create(oldState, (draft) => { draft.Table1.pageSize = undefined; }); @@ -260,7 +260,7 @@ describe("generateOptimisedUpdates", () => { expect(serialisedUpdates).toEqual(JSON.stringify(additionalUpdates)); }); it("should generate a delete patch when a property is transformed to undefined", () => { - const oldStateWithUndefinedValues = produce(oldState, (draft) => { + const oldStateWithUndefinedValues = create(oldState, (draft) => { draft.Table1.pageSize = undefined; }); @@ -281,7 +281,7 @@ describe("generateOptimisedUpdates", () => { ]); }); it("should generate an error when there is a serialisation error", () => { - const oldStateWithUndefinedValues = produce(oldState, (draft) => { + const oldStateWithUndefinedValues = create(oldState, (draft) => { //generate a cyclical object draft.Table1.filteredTableData = draft.Table1; }); @@ -302,7 +302,7 @@ describe("generateOptimisedUpdates", () => { const someEvalFn = (() => {}) as unknown as EvaluationError[]; it("should clean out new function properties added to the generated state", () => { - const newStateWithSomeFnProperty = produce(oldState, (draft) => { + const newStateWithSomeFnProperty = create(oldState, (draft) => { draft.Table1.someFn = () => {}; draft.Table1.__evaluation__.errors.someEvalFn = someEvalFn; }); @@ -320,7 +320,7 @@ describe("generateOptimisedUpdates", () => { //should delete all function updates expect(parsedUpdates).toEqual([]); - const parseAndApplyUpdatesToOldState = produce(oldState, (draft) => { + const parseAndApplyUpdatesToOldState = create(oldState, (draft) => { // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any parsedUpdates.forEach((v: any) => { @@ -333,7 +333,7 @@ describe("generateOptimisedUpdates", () => { }); it("should delete properties which get updated to a function", () => { - const newStateWithSomeFnProperty = produce(oldState, (draft) => { + const newStateWithSomeFnProperty = create(oldState, (draft) => { draft.Table1.pageSize = () => {}; draft.Table1.__evaluation__.errors.transientTableData = someEvalFn; }); @@ -362,14 +362,14 @@ describe("generateOptimisedUpdates", () => { }, ]); - const parseAndApplyUpdatesToOldState = produce(oldState, (draft) => { + const parseAndApplyUpdatesToOldState = create(oldState, (draft) => { // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any parsedUpdates.forEach((v: any) => { applyChange(draft, undefined, v); }); }); - const expectedState = produce(oldState, (draft) => { + const expectedState = create(oldState, (draft) => { delete draft.Table1.pageSize; delete draft.Table1.__evaluation__.errors.transientTableData; }); @@ -377,14 +377,14 @@ describe("generateOptimisedUpdates", () => { expect(parseAndApplyUpdatesToOldState).toEqual(expectedState); }); it("should delete function properties which get updated to undefined", () => { - const oldStateWithSomeFnProperty = produce(oldState, (draft) => { + const oldStateWithSomeFnProperty = create(oldState, (draft) => { // eslint-disable-next-line @typescript-eslint/no-empty-function draft.Table1.pageSize = () => {}; draft.Table1.__evaluation__.errors.transientTableData = // eslint-disable-next-line @typescript-eslint/no-empty-function someEvalFn; }); - const newStateWithFnsTransformedToUndefined = produce( + const newStateWithFnsTransformedToUndefined = create( oldState, (draft) => { draft.Table1.pageSize = undefined; @@ -417,14 +417,14 @@ describe("generateOptimisedUpdates", () => { }, ]); - const parseAndApplyUpdatesToOldState = produce(oldState, (draft) => { + const parseAndApplyUpdatesToOldState = create(oldState, (draft) => { // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any parsedUpdates.forEach((v: any) => { applyChange(draft, undefined, v); }); }); - const expectedState = produce(oldState, (draft) => { + const expectedState = create(oldState, (draft) => { delete draft.Table1.pageSize; delete draft.Table1.__evaluation__.errors.transientTableData; }); @@ -443,7 +443,7 @@ describe("generateOptimisedUpdates", () => { rhs: { someOtherKey: BigInt(3323232) }, }, ]; - const newStateWithBigInt = produce(oldState, (draft) => { + const newStateWithBigInt = create(oldState, (draft) => { draft.Table1.pageSize = someBigInt; }); const { serialisedUpdates } = generateSerialisedUpdates( @@ -472,14 +472,14 @@ describe("generateOptimisedUpdates", () => { }, ]); - const parseAndApplyUpdatesToOldState = produce(oldState, (draft) => { + const parseAndApplyUpdatesToOldState = create(oldState, (draft) => { // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any parsedUpdates.forEach((v: any) => { applyChange(draft, undefined, v); }); }); - const expectedState = produce(oldState, (draft) => { + const expectedState = create(oldState, (draft) => { draft.Table1.pageSize = "121221"; draft.Table1.someNewProp = { someOtherKey: "3323232" }; }); @@ -488,7 +488,7 @@ describe("generateOptimisedUpdates", () => { }); describe("serialise momement updates directly", () => { test("should generate a null update when it sees an invalid moment object", () => { - const newState = produce(oldState, (draft) => { + const newState = create(oldState, (draft) => { draft.Table1.pageSize = moment("invalid value"); }); const { serialisedUpdates } = generateSerialisedUpdates( @@ -504,7 +504,7 @@ describe("generateOptimisedUpdates", () => { }); test("should generate a regular update when it sees a valid moment object", () => { const validMoment = moment(); - const newState = produce(oldState, (draft) => { + const newState = create(oldState, (draft) => { draft.Table1.pageSize = validMoment; }); const { serialisedUpdates } = generateSerialisedUpdates( @@ -529,7 +529,7 @@ describe("generateOptimisedUpdates", () => { const parsedUpdates = parseUpdatesAndDeleteUndefinedUpdates(serialisedUpdates); - return produce(prevState, (draft) => { + return create(prevState, (draft) => { // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any parsedUpdates.forEach((v: any) => { @@ -552,7 +552,7 @@ describe("generateOptimisedUpdates", () => { } //attaching a collection to some property in the workerState - workerStateWithCollection = produce(oldState, (draft) => { + workerStateWithCollection = create(oldState, (draft) => { draft.Table1.pageSize = largeCollection; }); //generate serialised diff updates @@ -569,7 +569,7 @@ describe("generateOptimisedUpdates", () => { oldState, ); - const expectedMainThreadState = produce(oldState, (draft) => { + const expectedMainThreadState = create(oldState, (draft) => { draft.Table1.pageSize = JSON.parse(JSON.stringify(largeCollection)); }); @@ -583,7 +583,7 @@ describe("generateOptimisedUpdates", () => { test("update in a single moment value in a collection should always be serialised ", () => { const someNewDate = "2023-12-07T19:05:11.930Z"; // updating a single value in the prev worker state - const updatedWorkerStateWithASingleValue = produce( + const updatedWorkerStateWithASingleValue = create( klona(workerStateWithCollection), (draft) => { draft.Table1.pageSize[0].c = moment(someNewDate); @@ -613,7 +613,7 @@ describe("generateOptimisedUpdates", () => { someNewDate, ); - const expectedMainThreadState = produce( + const expectedMainThreadState = create( mainThreadStateWithCollection, (draft) => { draft.Table1.pageSize[0].c = JSON.parse( @@ -628,7 +628,7 @@ describe("generateOptimisedUpdates", () => { //some garbage value const someNewDate = "fdfdfd"; // updating a single value in the prev worker state - const updatedWorkerStateWithASingleValue = produce( + const updatedWorkerStateWithASingleValue = create( klona(workerStateWithCollection), (draft) => { // TODO: Fix this the next time the file is edited @@ -658,7 +658,7 @@ describe("generateOptimisedUpdates", () => { // check if the main thread state has the updated invalid value which should be null expect(updatedMainThreadState.Table1.pageSize[0].c).toEqual(null); - const expectedMainThreadState = produce( + const expectedMainThreadState = create( mainThreadStateWithCollection, (draft) => { draft.Table1.pageSize[0].c = JSON.parse( diff --git a/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts b/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts index e4dc617bca57..a6da206ec989 100644 --- a/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts +++ b/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts @@ -4,7 +4,7 @@ import { RenderModes } from "constants/WidgetConstants"; import { ENTITY_TYPE } from "ee/entities/DataTree/types"; import type { ConfigTree } from "entities/DataTree/dataTreeTypes"; import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget"; -import produce from "immer"; +import { create } from "mutative"; import type { WidgetEntity } from "plugins/Linting/lib/entity/WidgetEntity"; import type { UpdateDataTreeMessageData } from "sagas/EvalWorkerActionSagas"; import DataTreeEvaluator from "workers/common/DataTreeEvaluator"; @@ -259,7 +259,7 @@ describe("evaluateAndGenerateResponse", () => { test("should generate updates based on the unEvalUpdates", () => { // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any - const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + const updatedLabelUnevalTree = create(unEvalTree, (draft: any) => { draft.Text1.text = UPDATED_LABEL; draft.Text1.label = UPDATED_LABEL; }); @@ -311,7 +311,7 @@ describe("evaluateAndGenerateResponse", () => { test("should generate updates based on the evalOrder", () => { // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any - const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + const updatedLabelUnevalTree = create(unEvalTree, (draft: any) => { draft.Text1.text = UPDATED_LABEL; }); const updateTreeResponse = evaluator.setupUpdateTree( @@ -346,7 +346,7 @@ describe("evaluateAndGenerateResponse", () => { test("should generate the correct updates to be sent to the main thread's state when the value tied to a binding changes ", () => { // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any - const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + const updatedLabelUnevalTree = create(unEvalTree, (draft: any) => { if (draft.Text1?.text) { draft.Text1.text = UPDATED_LABEL; } @@ -386,7 +386,7 @@ describe("evaluateAndGenerateResponse", () => { test("should merge additional updates to the dataTree as well as push the updates back to the main thread's state when unEvalUpdates is ignored", () => { // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any - const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + const updatedLabelUnevalTree = create(unEvalTree, (draft: any) => { if (draft.Text1?.text) { draft.Text1.text = UPDATED_LABEL; } @@ -426,7 +426,7 @@ describe("evaluateAndGenerateResponse", () => { test("should add metaUpdates in the webworker's response", () => { // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any - const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + const updatedLabelUnevalTree = create(unEvalTree, (draft: any) => { if (draft.Text1?.text) { draft.Text1.text = UPDATED_LABEL; } @@ -456,7 +456,7 @@ describe("evaluateAndGenerateResponse", () => { test("should sanitise metaUpdates in the webworker's response and strip out non serialisable properties", () => { // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any - const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + const updatedLabelUnevalTree = create(unEvalTree, (draft: any) => { if (draft.Text1?.text) { draft.Text1.text = UPDATED_LABEL; } @@ -495,7 +495,7 @@ describe("evaluateAndGenerateResponse", () => { test("should add unEvalUpdates to the web worker response", () => { // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any - const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + const updatedLabelUnevalTree = create(unEvalTree, (draft: any) => { if (draft.Text1?.text) { draft.Text1.text = UPDATED_LABEL; } @@ -533,7 +533,7 @@ describe("evaluateAndGenerateResponse", () => { test("should ignore generating updates when unEvalUpdates is empty", () => { // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any - const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + const updatedLabelUnevalTree = create(unEvalTree, (draft: any) => { if (draft.Text1?.text) { draft.Text1.text = UPDATED_LABEL; } diff --git a/app/client/src/workers/common/DataTreeEvaluator/utils.test.ts b/app/client/src/workers/common/DataTreeEvaluator/utils.test.ts index 2c0597760a23..fff436dd8982 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/utils.test.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/utils.test.ts @@ -6,7 +6,7 @@ import { getAllPathsBasedOnDiffPaths, type DataTreeDiff, } from "ee/workers/Evaluation/evaluationUtils"; -import produce from "immer"; +import { create } from "mutative"; describe("getOnlyAffectedJSObjects", () => { const dataTree = { @@ -170,7 +170,7 @@ describe("getAllPathsBasedOnDiffPaths", () => { expect(initialAllKeys).toEqual(updatedAllKeys); }); test("should delete the correct paths within allKeys when a node within a widget is deleted", () => { - const deletedWidgetName = produce(initialTree, (draft) => { + const deletedWidgetName = create(initialTree, (draft) => { // a property within the widget is deleted delete draft.WidgetName.name; }); @@ -187,7 +187,7 @@ describe("getAllPathsBasedOnDiffPaths", () => { // we have to make a copy since allKeys is mutable { ...initialAllKeys }, ); - const deletedWidgetNameInAllKeys = produce(initialAllKeys, (draft) => { + const deletedWidgetNameInAllKeys = create(initialAllKeys, (draft) => { delete draft["WidgetName.name"]; }); @@ -195,7 +195,7 @@ describe("getAllPathsBasedOnDiffPaths", () => { }); test("should add the correct paths to the allKeys when a node within a widget is added", () => { - const addedNewWidgetProperty = produce(initialTree, (draft) => { + const addedNewWidgetProperty = create(initialTree, (draft) => { // new property is added to the widget draft.WidgetName.widgetNewProperty = "newValue"; }); @@ -213,7 +213,7 @@ describe("getAllPathsBasedOnDiffPaths", () => { // we have to make a copy since allKeys is mutable { ...initialAllKeys }, ); - const addedNewWidgetPropertyInAllKeys = produce(initialAllKeys, (draft) => { + const addedNewWidgetPropertyInAllKeys = create(initialAllKeys, (draft) => { draft["WidgetName.widgetNewProperty"] = true; }); @@ -221,7 +221,7 @@ describe("getAllPathsBasedOnDiffPaths", () => { }); test("should generate the correct paths when the value changes form a simple primitive to a collection, this is for EDIT diffs", () => { - const addedNewWidgetProperty = produce(initialTree, (draft) => { + const addedNewWidgetProperty = create(initialTree, (draft) => { //existing property within the widget is edited draft.WidgetName.name = [{ a: 1 }]; }); @@ -239,7 +239,7 @@ describe("getAllPathsBasedOnDiffPaths", () => { // we have to make a copy since allKeys is mutable { ...initialAllKeys }, ); - const addedACollectionInAllKeys = produce(initialAllKeys, (draft) => { + const addedACollectionInAllKeys = create(initialAllKeys, (draft) => { draft["WidgetName.name[0]"] = true; draft["WidgetName.name[0].a"] = true; }); diff --git a/app/client/yarn.lock b/app/client/yarn.lock index 6d3cf69e752f..6934b1bef202 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -13331,6 +13331,7 @@ __metadata: moment: 2.29.4 moment-timezone: ^0.5.35 msw: ^0.28.0 + mutative: ^1.1.0 nanoid: ^2.0.4 node-forge: ^1.3.0 object-hash: ^3.0.0 @@ -25740,6 +25741,13 @@ __metadata: languageName: node linkType: hard +"mutative@npm:^1.1.0": + version: 1.1.0 + resolution: "mutative@npm:1.1.0" + checksum: 5eaf505a97c713ecb45bdadfe905045692414ff6f08e35d9cad0e6e27d9005a06e9696350c23bff70d39488973841384ed01d800d67f8b2957c7a1daf3d24d3c + languageName: node + linkType: hard + "mute-stream@npm:0.0.8": version: 0.0.8 resolution: "mute-stream@npm:0.0.8"