diff --git a/app/client/packages/dsl/src/migrate/index.ts b/app/client/packages/dsl/src/migrate/index.ts index 3a11b3b9d6..f78a58eab2 100644 --- a/app/client/packages/dsl/src/migrate/index.ts +++ b/app/client/packages/dsl/src/migrate/index.ts @@ -95,7 +95,7 @@ import { migrateTableComputeValueBinding } from "./migrations/090-migrate-table- import { migrateAIChatWidget } from "./migrations/092-update-ai-chat-widget"; import type { DSLWidget } from "./types"; -export const LATEST_DSL_VERSION = 93; +export const LATEST_DSL_VERSION = 94; export const calculateDynamicHeight = () => { const DEFAULT_GRID_ROW_HEIGHT = 10; @@ -636,6 +636,18 @@ const migrateVersionedDSL = async (currentDSL: DSLWidget, newPage = false) => { if (currentDSL.version === 92) { currentDSL = migrateAIChatWidget(currentDSL); + currentDSL.version = 93; + } + + if (currentDSL.version === 93) { + /** + * We are repeating migration(90->91) here. Why? + * Although we updated the computed value logic in the property pane control files, we did not update the default values that the widget assumes upon instantiation. + * While this would not have caused any functional bugs, it results in the DSL saving an older version of the computed value, which the property control does not process or filter correctly for display. + * Consequently, anyone who dropped a table between version 90 and today will see unnecessary computations in their computed value field that should not be visible. + * This migration will update all those values to the latest computed value, ensuring they are displayed correctly by the control. + */ + currentDSL = migrateTableComputeValueBinding(currentDSL); currentDSL.version = LATEST_DSL_VERSION; } diff --git a/app/client/packages/dsl/src/migrate/tests/DSLMigration.test.ts b/app/client/packages/dsl/src/migrate/tests/DSLMigration.test.ts index a8e1a1b0b2..2e4d54a112 100644 --- a/app/client/packages/dsl/src/migrate/tests/DSLMigration.test.ts +++ b/app/client/packages/dsl/src/migrate/tests/DSLMigration.test.ts @@ -955,6 +955,15 @@ const migrations: Migration[] = [ ], version: 92, }, + { + functionLookup: [ + { + moduleObj: m90, + functionName: "migrateTableComputeValueBinding", + }, + ], + version: 93, + }, ]; const mockFnObj: Record = {}; diff --git a/app/client/src/components/propertyControls/TableComputeValue.test.tsx b/app/client/src/components/propertyControls/TableComputeValue.test.tsx index 9f51bda99d..c433600817 100644 --- a/app/client/src/components/propertyControls/TableComputeValue.test.tsx +++ b/app/client/src/components/propertyControls/TableComputeValue.test.tsx @@ -4,7 +4,6 @@ describe("ComputeTablePropertyControlV2.getInputComputedValue", () => { const tableName = "Table1"; const inputVariations = [ "currentRow.price", - `JSObject1.somefunction(currentRow["id"] || 0)`, ` [ { @@ -35,6 +34,8 @@ describe("ComputeTablePropertyControlV2.getInputComputedValue", () => { "currentRow.price * 2", "currentRow.isValid && true", "!currentRow.isDeleted", + "JSObject1.myFun1(currentRow['id'])", + `JSObject1.somefunction(currentRow["id"] || 0)`, ]; it("1. should return the correct computed value", () => { @@ -85,7 +86,7 @@ describe("ComputeTablePropertyControlV2.getInputComputedValue", () => { ComputeTablePropertyControlV2.getInputComputedValue( malformedComputedValue, ), - ).toBe(malformedComputedValue); + ).toBe("{{currentRow.function(unbalanced}}"); }); it("5. should correctly parse complex but valid expressions with multiple nested parentheses", () => { diff --git a/app/client/src/components/propertyControls/TableComputeValue.tsx b/app/client/src/components/propertyControls/TableComputeValue.tsx index 90c9957ef1..0d9cb5de34 100644 --- a/app/client/src/components/propertyControls/TableComputeValue.tsx +++ b/app/client/src/components/propertyControls/TableComputeValue.tsx @@ -161,86 +161,25 @@ class ComputeTablePropertyControlV2 extends BaseControl { const tableData = Table1.processedTableData || []; return tableData.length > 0 ? tableData.map((currentRow, currentIndex) => (currentRow.price * 2)) : currentRow.price * 2 })()}}" const mapSignatureIndex = propertyValue.indexOf(MAP_FUNCTION_SIGNATURE); // Find the actual computation expression between the map parentheses const computationStart = mapSignatureIndex + MAP_FUNCTION_SIGNATURE.length; - - const { endIndex, isValid } = this.findMatchingClosingParenthesis( - propertyValue, - computationStart, - ); - - // Handle case where no matching closing parenthesis is found - if (!isValid) { - // If we can't find the proper closing parenthesis, fall back to returning the original value - // This prevents errors when the expression is malformed - return propertyValue; - } + const computationEnd = propertyValue.lastIndexOf(")) : "); // Extract the computation expression between the map parentheses + // Note: At this point, we're just extracting the raw expression like "currentRow.price * 2" + // The actual removal of "currentRow." prefix happens later in JSToString() const computationExpression = propertyValue.substring( computationStart, - endIndex, + computationEnd, ); return JSToString(computationExpression); }; - /** - * Check if the computed value string looks structurally valid - * This helps catch obviously malformed expressions early - */ - private static isLikelyValidComputedValue(value: string): boolean { - // Check for basic structural elements that should be present - const hasOpeningStructure = value.includes("(() => {"); - const hasTableDataAssignment = value.includes("const tableData ="); - const hasReturnStatement = value.includes("return tableData.length > 0 ?"); - const hasClosingStructure = value.includes("})()}}"); - - return ( - hasOpeningStructure && - hasTableDataAssignment && - hasReturnStatement && - hasClosingStructure - ); - } - - /** - * Utility function to find the matching closing parenthesis - * @param text - The text to search in - * @param startIndex - The index after the opening parenthesis - * @returns Object containing the index of the matching closing parenthesis and whether it was found - */ - private static findMatchingClosingParenthesis( - text: string, - startIndex: number, - ) { - let openParenCount = 1; // Start with 1 for the opening parenthesis - - for (let i = startIndex; i < text.length; i++) { - if (text[i] === "(") { - openParenCount++; - } else if (text[i] === ")") { - openParenCount--; - - if (openParenCount === 0) { - return { endIndex: i, isValid: true }; - } - } - } - - // No matching closing parenthesis found - return { endIndex: text.length, isValid: false }; - } - getComputedValue = (value: string, tableName: string) => { // Return raw value if: // 1. The value is not a dynamic binding (not wrapped in {{...}}) diff --git a/app/client/src/widgets/TableWidgetV2/widget/utilities.ts b/app/client/src/widgets/TableWidgetV2/widget/utilities.ts index 502c852777..c1c17d8160 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/utilities.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/utilities.ts @@ -210,9 +210,9 @@ export function getDefaultColumnProperties( isDiscardVisible: true, computedValue: isDerived ? "" - : `{{${widgetName}.processedTableData.map((currentRow, currentIndex) => ( currentRow["${escapeString( + : `{{(() => { const tableData = ${widgetName}.processedTableData || []; return tableData.length > 0 ? tableData.map((currentRow, currentIndex) => (currentRow["${escapeString( id, - )}"]))}}`, + )}"])) : ${escapeString(id)} })()}}`, sticky: StickyType.NONE, validation: {}, currencyCode: "USD",