fix(grid): replace matches in truncated large values
This commit is contained in:
6 files changed
+157
-24
No files matched your search
@@ -311,7 +311,7 @@ import { createDataGridRuntimeScope } from "@/lib/dataGrid/dataGridRuntime";
|
||||
import { useDataGridEditor } from "@/composables/useDataGridEditor";
|
||||
import { useDataGridSort } from "@/composables/useDataGridSort";
|
||||
import { useDataGridSearch, type DataGridSearchMatch } from "@/composables/useDataGridSearch";
|
||||
import { findDataGridReplacementMatches, replaceDataGridText, type DataGridReplaceScope } from "@/lib/dataGrid/dataGridReplace";
|
||||
import { findDataGridReplacementMatches, prepareDataGridCellReplacements, type DataGridReplaceScope } from "@/lib/dataGrid/dataGridReplace";
|
||||
import { useDataGridResultLifecycle } from "@/composables/useDataGridResultLifecycle";
|
||||
import { useDataGridAutoRefresh } from "@/composables/useDataGridAutoRefresh";
|
||||
import { useDataGridAsyncSurface } from "@/composables/useDataGridAsyncSurface";
|
||||
@@ -8160,29 +8160,19 @@ function selectedRangeTargetsOnlyDraftRow(): boolean {
|
||||
}
|
||||
|
||||
const replaceAvailable = computed(() => !!props.editable && hasDataGridSaveTarget.value && canEditExistingRows.value && !resolvedConnectionConfig.value?.read_only && !isConditionalUpdateActive.value);
|
||||
const replaceBusy = computed(() => isSaving.value || gridSurfaceBusy.value || props.loading === true);
|
||||
const replaceResolving = ref(false);
|
||||
const replaceBusy = computed(() => replaceResolving.value || isSaving.value || gridSurfaceBusy.value || props.loading === true);
|
||||
|
||||
function replacementRowItem(rowId: number): RowItem | undefined {
|
||||
const row = props.result.rows[rowId];
|
||||
if (!row || rowId < 0) return undefined;
|
||||
return { id: rowId, displayIndex: displayRowIndexById(rowId), sourceIndex: rowId, data: rowDataWithChanges(row, rowId), isNew: false, isDeleted: deletedRows.value.has(rowId), isDirtyCol: [], status: dirtyRows.value.has(rowId) ? "edited" : "clean" };
|
||||
const dirty = dirtyRows.value.get(rowId);
|
||||
return { id: rowId, displayIndex: displayRowIndexById(rowId), sourceIndex: rowId, data: rowDataWithChanges(row, rowId), isNew: false, isDeleted: deletedRows.value.has(rowId), isDirtyCol: dirtyColumnsForRow(dirty, props.result.columns.length), status: dirty?.size ? "edited" : "clean" };
|
||||
}
|
||||
|
||||
function canReplaceGridCell(item: RowItem | undefined, col: number): boolean {
|
||||
const type = allColumnTypes.value[col];
|
||||
return (
|
||||
replaceAvailable.value &&
|
||||
!!item &&
|
||||
item.sourceIndex !== undefined &&
|
||||
!item.isNew &&
|
||||
!item.isDraft &&
|
||||
typeof item.data[col] === "string" &&
|
||||
canEditCellItem(item, col) &&
|
||||
!isLargeValuePreview(item, col) &&
|
||||
!isBinaryCellColumnType(type) &&
|
||||
!isNumericColumnType(type) &&
|
||||
!isBooleanGridCell(item, col)
|
||||
);
|
||||
return replaceAvailable.value && !!item && item.sourceIndex !== undefined && !item.isNew && !item.isDraft && typeof item.data[col] === "string" && canEditCellItem(item, col) && !isBinaryCellColumnType(type) && !isNumericColumnType(type) && !isBooleanGridCell(item, col);
|
||||
}
|
||||
|
||||
function replacementCellInScope(rowId: number, col: number): boolean {
|
||||
@@ -8227,14 +8217,35 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
function replaceGridMatches(currentOnly = false) {
|
||||
async function replaceGridMatches(currentOnly = false) {
|
||||
if (!replaceAvailable.value || replaceBusy.value) return;
|
||||
if (currentOnly && !canReplaceCurrent.value) return;
|
||||
const current = currentSearchMatch.value;
|
||||
const currentRowId = current?.kind === "cell" ? displayItemAt(current.displayRow)?.id : undefined;
|
||||
const matches = replacementMatches.value.filter((match) => !currentOnly || (match.rowId === currentRowId && match.col === current?.col));
|
||||
const count = stageCellReplacements(matches.map((match) => ({ ...match, previousValue: match.value, value: replaceDataGridText(match.value, deferredClientSearchText.value, replacementText.value, replaceCaseSensitive.value) })));
|
||||
if (count > 0) toast(t("grid.replaceStagedCells", { count }), 5000);
|
||||
if (!matches.length) return;
|
||||
const sourceResult = props.result;
|
||||
const search = deferredClientSearchText.value;
|
||||
const replacement = replacementText.value;
|
||||
const caseSensitive = replaceCaseSensitive.value;
|
||||
replaceResolving.value = true;
|
||||
try {
|
||||
const changes = await prepareDataGridCellReplacements({
|
||||
matches,
|
||||
search,
|
||||
replacement,
|
||||
caseSensitive,
|
||||
needsResolution: (rowId, col) => isLargeValuePreview(replacementRowItem(rowId), col),
|
||||
resolveValues: resolveLargeValueCells,
|
||||
});
|
||||
if (props.result !== sourceResult) return;
|
||||
const count = stageCellReplacements(changes);
|
||||
if (count > 0) toast(t("grid.replaceStagedCells", { count }), 5000);
|
||||
} catch (error) {
|
||||
if (props.result === sourceResult) reportLargeValueLoadError(error);
|
||||
} finally {
|
||||
replaceResolving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function fillSelectionWithValue(value: string | null, options: { preserveEmptyString?: boolean; emptyStringAsNull?: boolean } = {}): boolean {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dataGridSource = readFileSync(new URL("../DataGrid.vue", import.meta.url), "utf8");
|
||||
|
||||
function functionSource(name: string, nextName: string): string {
|
||||
const start = dataGridSource.indexOf(`function ${name}`);
|
||||
const relativeEnd = dataGridSource.slice(start).search(new RegExp(`\\n(?:async\\s+)?function\\s+${nextName}\\b`));
|
||||
expect(start).toBeGreaterThanOrEqual(0);
|
||||
expect(relativeEnd).toBeGreaterThan(0);
|
||||
return dataGridSource.slice(start, start + relativeEnd);
|
||||
}
|
||||
|
||||
describe("DataGrid large-value replacement", () => {
|
||||
it("counts preview matches and resolves full values before staging replacements", () => {
|
||||
const eligibilitySource = functionSource("canReplaceGridCell", "replacementCellInScope");
|
||||
const replaceSource = functionSource("replaceGridMatches", "fillSelectionWithValue");
|
||||
|
||||
expect(eligibilitySource).not.toContain("!isLargeValuePreview");
|
||||
expect(replaceSource).toContain("await prepareDataGridCellReplacements");
|
||||
expect(replaceSource).toContain("resolveLargeValueCells");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user