PromucFlow_constructor/app/client/src/utils/migrations/TableWidget.ts

438 lines
16 KiB
TypeScript
Raw Normal View History

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<string, ColumnProperties> = {};
// 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<string, string>) => {
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<string, ColumnProperties> = {};
for (const [key, value] of Object.entries(
child.primaryColumns as Record<string, ColumnProperties>,
)) {
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<string, ColumnProperties> = {};
for (const [key, value] of Object.entries(
child.primaryColumns as Record<string, ColumnProperties>,
)) {
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;
};
const getUpdatedColumns = (
widgetName: string,
columns: Record<string, ColumnProperties>,
) => {
const updatedColumns: Record<string, ColumnProperties> = {};
if (columns && Object.keys(columns).length > 0) {
for (const [columnId, columnProps] of Object.entries(columns)) {
const sanitizedColumnId = removeSpecialChars(columnId, 200);
const selectedRowBindingValue = `${widgetName}.selectedRow`;
let newOnClickBindingValue = undefined;
if (
columnProps.onClick &&
columnProps.onClick.includes(selectedRowBindingValue)
) {
newOnClickBindingValue = columnProps.onClick.replace(
selectedRowBindingValue,
"currentRow",
);
}
updatedColumns[sanitizedColumnId] = columnProps;
if (newOnClickBindingValue)
updatedColumns[sanitizedColumnId].onClick = newOnClickBindingValue;
}
}
return updatedColumns;
};
export const migrateTableWidgetSelectedRowBindings = (
currentDSL: DSLWidget,
) => {
currentDSL.children = currentDSL.children?.map((child: WidgetProps) => {
if (child.type === "TABLE_WIDGET") {
child.derivedColumns = getUpdatedColumns(
child.widgetName,
child.derivedColumns as Record<string, ColumnProperties>,
);
child.primaryColumns = getUpdatedColumns(
child.widgetName,
child.primaryColumns as Record<string, ColumnProperties>,
);
} else if (child.children && child.children.length > 0) {
child = migrateTableWidgetSelectedRowBindings(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<string, ColumnProperties> = {};
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;
};