import { FontStyleTypes, TextSizes, GridDefaults, } from "constants/WidgetConstants"; import { getAllTableColumnKeys } from "widgets/TableWidget/component/TableHelpers"; import { ColumnProperties, CellAlignmentTypes, VerticalAlignmentTypes, ColumnTypes, } from "widgets/TableWidget/component/Constants"; import { Colors } from "constants/Colors"; import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl"; import { cloneDeep, isString } from "lodash"; import { WidgetProps } from "widgets/BaseWidget"; import { DSLWidget } from "widgets/constants"; import { getSubstringBetweenTwoWords } from "utils/helpers"; export const tableWidgetPropertyPaneMigrations = (currentDSL: DSLWidget) => { currentDSL.children = currentDSL.children?.map((_child: WidgetProps) => { let child = cloneDeep(_child); // If the current child is a TABLE_WIDGET if (child.type === "TABLE_WIDGET") { const hiddenColumns = child.hiddenColumns || []; const columnNameMap = child.columnNameMap; const columnSizeMap = child.columnSizeMap; const columnTypeMap = child.columnTypeMap; let tableColumns: string[] = []; const dynamicBindingPathList = child.dynamicBindingPathList; if (child.tableData.length) { let tableData = []; // Try parsing the table data, if it parses great // If it does not parse, assign tableData the value as is. try { tableData = JSON.parse(child.tableData); } catch (e) { tableData = child.tableData; } if ( !isString(tableData) && dynamicBindingPathList?.findIndex((item) => item.key !== "tableData") ) { // Get the list of column ids tableColumns = getAllTableColumnKeys(tableData); } else { child.migrated = false; } } // Get primaryColumns to be the list of column keys // Use the old order if it exists, else use the new order const primaryColumns = child.columnOrder?.length ? child.columnOrder : tableColumns; child.primaryColumns = {}; // const hasActions = child.columnActions && child.columnActions.length > 0; // Generate new primarycolumns primaryColumns.forEach((accessor: string, index: number) => { // Get the column type from the columnTypeMap let columnType = columnTypeMap && columnTypeMap[accessor] ? columnTypeMap[accessor].type : ColumnTypes.TEXT; // If the columnType is currency make it a text type // We're deprecating currency types if (columnType === "currency") { columnType = ColumnTypes.TEXT; } // Get a full set of column properties const column: ColumnProperties = { index, // Use to maintain order of columns // The widget of the column width: columnSizeMap && columnSizeMap[accessor] ? columnSizeMap[accessor] : 150, // id of the column id: accessor, // default horizontal alignment horizontalAlignment: CellAlignmentTypes.LEFT, // default vertical alignment verticalAlignment: VerticalAlignmentTypes.CENTER, // columnType columnType, // default text color textColor: Colors.THUNDER, // default text size textSize: TextSizes.PARAGRAPH, // default font size fontStyle: FontStyleTypes.REGULAR, enableFilter: true, enableSort: true, // hide the column if it was hidden earlier using hiddenColumns isVisible: hiddenColumns.includes(accessor) ? false : true, // We did not have a concept of derived columns so far isDerived: false, // Use renamed names from the map // or use the newly generated name label: columnNameMap && columnNameMap[accessor] ? columnNameMap[accessor] : accessor, // Generate computed value computedValue: `{{${child.widgetName}.sanitizedTableData.map((currentRow) => ( currentRow.${accessor})}}`, }; // copy inputForma nd outputFormat for date column types if (columnTypeMap && columnTypeMap[accessor]) { column.outputFormat = columnTypeMap[accessor].format || ""; column.inputFormat = columnTypeMap[accessor].inputFormat || ""; } child.primaryColumns[column.id] = column; }); // Get all column actions const columnActions = child.columnActions || []; // Get dynamicTriggerPathList let dynamicTriggerPathList: Array<{ key: string }> = child.dynamicTriggerPathList || []; const columnPrefix = "customColumn"; const updatedDerivedColumns: Record = {}; // Add derived column for each column action columnActions.forEach((action: ColumnAction, index: number) => { const column = { index: child.primaryColumns.length + index, // Add to the end of the columns list width: 150, // Default width id: `${columnPrefix}${index + 1}`, // A random string which was generated previously label: action.label, // Revert back to "Actions" columnType: "button", // All actions are buttons isVisible: true, isDisabled: false, isDerived: true, buttonLabel: action.label, buttonStyle: "rgb(3, 179, 101)", buttonLabelColor: "#FFFFFF", onClick: action.dynamicTrigger, computedValue: "", }; dynamicTriggerPathList.push({ key: `primaryColumns.${columnPrefix}${index + 1}.onClick`, }); updatedDerivedColumns[column.id] = column; child.primaryColumns[column.id] = column; }); if (Object.keys(updatedDerivedColumns).length) { dynamicTriggerPathList = dynamicTriggerPathList.filter( (triggerPath: Record) => { triggerPath.key !== "columnActions"; }, ); } child.dynamicTriggerPathList = dynamicTriggerPathList; child.textSize = TextSizes.PARAGRAPH; child.horizontalAlignment = CellAlignmentTypes.LEFT; child.verticalAlignment = VerticalAlignmentTypes.CENTER; child.fontStyle = FontStyleTypes.REGULAR; child.derivedColumns = updatedDerivedColumns; } else if (child.children && child.children.length > 0) { child = tableWidgetPropertyPaneMigrations(child); } return child; }); return currentDSL; }; const removeSpecialChars = (value: string, limit?: number) => { const separatorRegex = /\W+/; return value .split(separatorRegex) .join("_") .slice(0, limit || 30); }; export const migrateTablePrimaryColumnsBindings = (currentDSL: DSLWidget) => { currentDSL.children = currentDSL.children?.map((child: WidgetProps) => { if (child.type === "TABLE_WIDGET") { if ( child.primaryColumns && Object.keys(child.primaryColumns).length > 0 ) { const newPrimaryColumns: Record = {}; for (const [key, value] of Object.entries( child.primaryColumns as Record, )) { const sanitizedKey = removeSpecialChars(key, 200); const newComputedValue = value.computedValue ? value.computedValue.replace( `${child.widgetName}.tableData.map`, `${child.widgetName}.sanitizedTableData.map`, ) : ""; newPrimaryColumns[sanitizedKey] = { ...value, computedValue: newComputedValue, }; } child.primaryColumns = newPrimaryColumns; child.dynamicBindingPathList = child.dynamicBindingPathList?.map( (path) => { path.key = path.key.split(" ").join("_"); return path; }, ); } } else if (child.children && child.children.length > 0) { child = migrateTablePrimaryColumnsBindings(child); } return child; }); return currentDSL; }; export const migrateTableWidgetParentRowSpaceProperty = ( currentDSL: DSLWidget, ) => { currentDSL.children = currentDSL.children?.map((child: WidgetProps) => { if (child.type === "TABLE_WIDGET") { if (child.parentRowSpace === 40) { child.parentRowSpace = GridDefaults.DEFAULT_GRID_ROW_HEIGHT; } } else if (child.children && child.children.length > 0) { child = migrateTableWidgetParentRowSpaceProperty(child); } return child; }); return currentDSL; }; export const migrateTableWidgetHeaderVisibilityProperties = ( currentDSL: DSLWidget, ) => { currentDSL.children = currentDSL.children?.map((child: WidgetProps) => { if (child.type === "TABLE_WIDGET") { if (!("isVisibleSearch" in child)) { child.isVisibleSearch = true; child.isVisibleFilters = true; child.isVisibleDownload = true; child.isVisiblePagination = true; } } else if (child.children && child.children.length > 0) { child = migrateTableWidgetHeaderVisibilityProperties(child); } return child; }); return currentDSL; }; export const migrateTableWidgetDelimiterProperties = ( currentDSL: DSLWidget, ) => { currentDSL.children = currentDSL.children?.map((child: WidgetProps) => { if (child.type === "TABLE_WIDGET") { if (!child.delimiter) { child.delimiter = ","; } } else if (child.children && child.children.length > 0) { child = migrateTableWidgetDelimiterProperties(child); } return child; }); return currentDSL; }; export const migrateTablePrimaryColumnsComputedValue = ( currentDSL: DSLWidget, ) => { currentDSL.children = currentDSL.children?.map((child: WidgetProps) => { if (child.type === "TABLE_WIDGET") { if ( child.primaryColumns && Object.keys(child.primaryColumns).length > 0 ) { const newPrimaryColumns: Record = {}; for (const [key, value] of Object.entries( child.primaryColumns as Record, )) { const sanitizedKey = removeSpecialChars(key, 200); let newComputedValue = ""; if (value.computedValue) { newComputedValue = value.computedValue.replace( `${child.widgetName}.sanitizedTableData.map((currentRow) => { return`, `${child.widgetName}.sanitizedTableData.map((currentRow) => (`, ); // change matching "}" bracket with ")" const lastParanthesesInd = newComputedValue.length - 4; newComputedValue = newComputedValue.substring(0, lastParanthesesInd) + ")" + newComputedValue.substring(lastParanthesesInd + 1); } newPrimaryColumns[sanitizedKey] = { ...value, computedValue: newComputedValue, }; } child.primaryColumns = newPrimaryColumns; } } else if (child.children && child.children.length > 0) { child = migrateTablePrimaryColumnsComputedValue(child); } return child; }); return currentDSL; }; /** * This migration sanitizes the following properties - * primaryColumns object key, for the value of each key - id, computedValue are sanitized * columnOrder * dynamicBindingPathList * * This migration solves the following issue - * https://github.com/appsmithorg/appsmith/issues/6897 */ export const migrateTableSanitizeColumnKeys = (currentDSL: DSLWidget) => { currentDSL.children = currentDSL.children?.map((child: WidgetProps) => { if (child.type === "TABLE_WIDGET") { const primaryColumnEntries: [string, ColumnProperties][] = Object.entries( child.primaryColumns || {}, ); const newPrimaryColumns: Record = {}; if (primaryColumnEntries.length) { for (const [key, value] of primaryColumnEntries) { const sanitizedKey = removeSpecialChars(key, 200); const id = removeSpecialChars(value.id, 200); // Sanitizes "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.$$$random_header))}}" // to "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow._random_header))}}" const computedValue = (value?.computedValue || "").replace( key, sanitizedKey, ); newPrimaryColumns[sanitizedKey] = { ...value, computedValue, id, }; } child.primaryColumns = newPrimaryColumns; } // Sanitizes [ "id", "name", $$$random_header ] // to [ "id", "name", _random_header ] child.columnOrder = (child.columnOrder || []).map((co: string) => removeSpecialChars(co, 200), ); // Sanitizes [ {key: primaryColumns.$random.header.computedValue }] // to [ {key: primaryColumns._random_header.computedValue }] child.dynamicBindingPathList = (child.dynamicBindingPathList || []).map( (path) => { const pathChunks = path.key.split("."); // primaryColumns.$random.header.computedValue -> [ "primaryColumns", "$random", "header", "computedValue"] // tableData is a valid dynamicBindingPath and pathChunks would have just one entry if (pathChunks.length < 2) { return path; } const firstPart = pathChunks[0] + "."; // "primaryColumns." const lastPart = "." + pathChunks[pathChunks.length - 1]; // ".computedValue" const key = getSubstringBetweenTwoWords( path.key, firstPart, lastPart, ); // primaryColumns.$random.header.computedValue -> $random.header const sanitizedPrimaryColumnKey = removeSpecialChars(key, 200); return { key: firstPart + sanitizedPrimaryColumnKey + lastPart, }; }, ); } else if (child.children && child.children.length > 0) { child = migrateTableSanitizeColumnKeys(child); } return child; }); return currentDSL; };