feat: optimize HTML text extraction in TableWidgetV2 (#38153)
This commit is contained in:
parent
d52490cabb
commit
dcb27d2c87
|
|
@ -183,6 +183,46 @@ describe("HTML columns", () => {
|
||||||
delete input.searchText;
|
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", () => {
|
it("validates filters on table for HTML columns", () => {
|
||||||
input.filters = [
|
input.filters = [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -284,13 +284,46 @@ export default {
|
||||||
const getTextFromHTML = (html) => {
|
const getTextFromHTML = (html) => {
|
||||||
if (!html) return "";
|
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
|
/* extend processedTableData with values from
|
||||||
* - computedValues, in case of normal column
|
* - computedValues, in case of normal column
|
||||||
* - empty values, in case of derived column
|
* - empty values, in case of derived column
|
||||||
|
|
@ -325,6 +358,12 @@ export default {
|
||||||
...processedTableData[index],
|
...processedTableData[index],
|
||||||
[column.alias]: computedValue,
|
[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(
|
return sortByOrder(
|
||||||
getTextFromHTML(processedA[sortByColumnOriginalId]) >
|
(htmlExtractedTextA ??
|
||||||
getTextFromHTML(processedB[sortByColumnOriginalId]),
|
getTextFromHTML(processedA[sortByColumnOriginalId])) >
|
||||||
|
(htmlExtractedTextB ??
|
||||||
|
getTextFromHTML(processedB[sortByColumnOriginalId])),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return sortByOrder(
|
return sortByOrder(
|
||||||
processedA[sortByColumnOriginalId].toString().toLowerCase() >
|
processedA[sortByColumnOriginalId].toString().toLowerCase() >
|
||||||
|
|
@ -715,10 +766,6 @@ export default {
|
||||||
(column) => column.columnType === "url" && column.displayText,
|
(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
|
* For select columns with label and values, we need to include the label value
|
||||||
* in the search and filter data
|
* in the search and filter data
|
||||||
|
|
@ -814,17 +861,23 @@ export default {
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
let htmlValues = {};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We don't want html tags and inline styles to match in search
|
* We don't want html tags and inline styles to match in search
|
||||||
*/
|
*/
|
||||||
const htmlValues = columnsWithHTML.reduce((acc, column) => {
|
if (shouldExtractHTMLText) {
|
||||||
const value = row[column.alias];
|
htmlValues = columnsWithHTML.reduce((acc, column) => {
|
||||||
|
const value = row[column.alias];
|
||||||
|
|
||||||
acc[column.alias] =
|
acc[column.alias] = _.isNil(value)
|
||||||
value === null || value === undefined ? "" : getTextFromHTML(value);
|
? ""
|
||||||
|
: row[getKeyForExtractedTextFromHTML(column.alias)] ??
|
||||||
|
getTextFromHTML(value);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
const displayedRow = {
|
const displayedRow = {
|
||||||
...row,
|
...row,
|
||||||
|
|
@ -832,13 +885,12 @@ export default {
|
||||||
...displayTextValues,
|
...displayTextValues,
|
||||||
...htmlValues,
|
...htmlValues,
|
||||||
};
|
};
|
||||||
const htmlColumns = columnsWithHTML.map((column) => column.alias);
|
|
||||||
|
|
||||||
if (searchKey) {
|
if (searchKey) {
|
||||||
const combinedRowContent = [
|
const combinedRowContent = [
|
||||||
...Object.values(_.omit(displayedRow, hiddenColumns)),
|
...Object.values(_.omit(displayedRow, hiddenColumns)),
|
||||||
...Object.values(
|
...Object.values(
|
||||||
_.omit(originalRow, [...hiddenColumns, ...htmlColumns]),
|
_.omit(originalRow, [...hiddenColumns, ...htmlColumnAliases]),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
.join(", ")
|
.join(", ")
|
||||||
|
|
@ -875,12 +927,16 @@ export default {
|
||||||
/*
|
/*
|
||||||
* We don't want html tags and inline styles to match in filter conditions
|
* 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
|
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];
|
: originalRow[props.filters[i].column];
|
||||||
const displayedColValue = isHTMLColumn
|
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];
|
: displayedRow[props.filters[i].column];
|
||||||
|
|
||||||
filterResult =
|
filterResult =
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user