feat: optimize HTML text extraction in TableWidgetV2 (#38153)

This commit is contained in:
Rahul Barwal 2024-12-23 15:49:46 +05:30 committed by GitHub
parent d52490cabb
commit dcb27d2c87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 117 additions and 21 deletions

View File

@ -183,6 +183,46 @@ describe("HTML columns", () => {
delete input.searchText;
});
it("validate search works when a javascript object is sent in HTMLcolumn", () => {
const jsObjectInput = _.cloneDeep(input);
jsObjectInput.processedTableData[0].status = {
color: "yellow",
text: "Adventure",
};
jsObjectInput.searchText = "Adventure";
const expected = [
{
id: 1,
name: "Jim Doe",
status: {
color: "yellow",
text: "Adventure",
},
__originalIndex__: 0,
},
];
let result = getFilteredTableData(jsObjectInput, moment, _);
expect(result).toStrictEqual(expected);
});
it("validate search does not filter based on html attributes", () => {
input.searchText = "span";
const expected = [];
let result = getFilteredTableData(input, moment, _);
expect(result).toStrictEqual(expected);
input.searchText = "color";
result = getFilteredTableData(input, moment, _);
expect(result).toStrictEqual(expected);
delete input.searchText;
});
it("validates filters on table for HTML columns", () => {
input.filters = [
{

View File

@ -284,13 +284,46 @@ export default {
const getTextFromHTML = (html) => {
if (!html) return "";
const tempDiv = document.createElement("div");
if (typeof html === "object") {
html = JSON.stringify(html);
}
tempDiv.innerHTML = html;
try {
const tempDiv = document.createElement("div");
return tempDiv.textContent || tempDiv.innerText || "";
tempDiv.innerHTML = html;
return tempDiv.textContent || tempDiv.innerText || "";
} catch (e) {
return "";
}
};
/**
* Since getTextFromHTML is an expensive operation, we need to avoid calling it unnecessarily
* This optimization ensures that getTextFromHTML is only called when required
*/
const columnsWithHTML = Object.values(props.primaryColumns).filter(
(column) => column.columnType === "html",
);
const htmlColumnAliases = new Set(
columnsWithHTML.map((column) => column.alias),
);
const isFilteringByColumnThatHasHTML = props.filters?.some((filter) =>
htmlColumnAliases.has(filter.column),
);
const isSortingByColumnThatHasHTML =
props.sortOrder?.column && htmlColumnAliases.has(props.sortOrder.column);
const shouldExtractHTMLText = !!(
props.searchText ||
isFilteringByColumnThatHasHTML ||
isSortingByColumnThatHasHTML
);
const getKeyForExtractedTextFromHTML = (columnAlias) =>
`__htmlExtractedText_${columnAlias}__`;
/* extend processedTableData with values from
* - computedValues, in case of normal column
* - empty values, in case of derived column
@ -325,6 +358,12 @@ export default {
...processedTableData[index],
[column.alias]: computedValue,
};
if (shouldExtractHTMLText && column.columnType === "html") {
processedTableData[index][
getKeyForExtractedTextFromHTML(column.alias)
] = getTextFromHTML(computedValue);
}
});
});
}
@ -514,11 +553,23 @@ export default {
);
}
}
case "html":
case "html": {
const htmlExtractedTextA =
processedA[
getKeyForExtractedTextFromHTML(sortByColumnOriginalId)
];
const htmlExtractedTextB =
processedB[
getKeyForExtractedTextFromHTML(sortByColumnOriginalId)
];
return sortByOrder(
getTextFromHTML(processedA[sortByColumnOriginalId]) >
getTextFromHTML(processedB[sortByColumnOriginalId]),
(htmlExtractedTextA ??
getTextFromHTML(processedA[sortByColumnOriginalId])) >
(htmlExtractedTextB ??
getTextFromHTML(processedB[sortByColumnOriginalId])),
);
}
default:
return sortByOrder(
processedA[sortByColumnOriginalId].toString().toLowerCase() >
@ -715,10 +766,6 @@ export default {
(column) => column.columnType === "url" && column.displayText,
);
const columnsWithHTML = Object.values(props.primaryColumns).filter(
(column) => column.columnType === "html",
);
/*
* For select columns with label and values, we need to include the label value
* in the search and filter data
@ -814,17 +861,23 @@ export default {
return acc;
}, {});
let htmlValues = {};
/*
* We don't want html tags and inline styles to match in search
*/
const htmlValues = columnsWithHTML.reduce((acc, column) => {
const value = row[column.alias];
if (shouldExtractHTMLText) {
htmlValues = columnsWithHTML.reduce((acc, column) => {
const value = row[column.alias];
acc[column.alias] =
value === null || value === undefined ? "" : getTextFromHTML(value);
acc[column.alias] = _.isNil(value)
? ""
: row[getKeyForExtractedTextFromHTML(column.alias)] ??
getTextFromHTML(value);
return acc;
}, {});
return acc;
}, {});
}
const displayedRow = {
...row,
@ -832,13 +885,12 @@ export default {
...displayTextValues,
...htmlValues,
};
const htmlColumns = columnsWithHTML.map((column) => column.alias);
if (searchKey) {
const combinedRowContent = [
...Object.values(_.omit(displayedRow, hiddenColumns)),
...Object.values(
_.omit(originalRow, [...hiddenColumns, ...htmlColumns]),
_.omit(originalRow, [...hiddenColumns, ...htmlColumnAliases]),
),
]
.join(", ")
@ -875,12 +927,16 @@ export default {
/*
* We don't want html tags and inline styles to match in filter conditions
*/
const isHTMLColumn = htmlColumns.includes(props.filters[i].column);
const isHTMLColumn = htmlColumnAliases.has(props.filters[i].column);
const originalColValue = isHTMLColumn
? getTextFromHTML(originalRow[props.filters[i].column])
? originalRow[
getKeyForExtractedTextFromHTML(props.filters[i].column)
] ?? getTextFromHTML(originalRow[props.filters[i].column])
: originalRow[props.filters[i].column];
const displayedColValue = isHTMLColumn
? getTextFromHTML(displayedRow[props.filters[i].column])
? displayedRow[
getKeyForExtractedTextFromHTML(props.filters[i].column)
] ?? getTextFromHTML(displayedRow[props.filters[i].column])
: displayedRow[props.filters[i].column];
filterResult =