fix(mongodb): null cell display and inline editing

This commit is contained in:
zipg
2026-09-13 13:26:26 +08:00
committed by GitHub
parent 17f184cd07
commit 28206f2963
5 changed files with 41 additions and 12 deletions
+14 -5
View File
@@ -99,7 +99,7 @@ import type { BuildSingleColumnAlterSqlOptions } from "@/lib/table/tableStructur
import { buildTableSelectSql, qualifyTableReferencesInSql, quoteTableDataIdentifier } from "@/lib/table/tableSelectSql";
import { uuid } from "@/lib/common/utils";
import { generateCellValues, type CellValueGenerationKind } from "@/lib/dataGrid/cellValueGeneration";
import { MONGO_DOCUMENT_GRID_NULL, mongoDocumentGridDisplayText, mongoDocumentGridEditorText, mongoDocumentGridExternalValue, mongoDocumentGridInputValue } from "@/lib/mongo/mongoDocumentValues";
import { MONGO_DOCUMENT_GRID_NULL, mongoDocumentGridClipboardText, mongoDocumentGridDisplayText, mongoDocumentGridEditorText, mongoDocumentGridExternalValue, mongoDocumentGridInputValue } from "@/lib/mongo/mongoDocumentValues";
import { compactHeaderColumnType, formatMetadataColumnTypeLabel, isNumericColumnType, resolveDataGridTypeVisualKind, resolveHeaderColumnType, resolveResultColumnType } from "@/lib/dataGrid/dataGridColumnType";
import { dataGridCellTextClass, dataGridTypeVisualClass } from "@/lib/dataGrid/dataGridCellTextVisual";
import { DATA_GRID_TYPE_COLOR_KEYS, resolveActiveDataGridTypeColors } from "@/lib/dataGrid/dataGridTypeColorScheme";
@@ -2017,8 +2017,10 @@ const columnAligns = computed<("left" | "right")[]>(() => {
});
function gridCellTextColorClass(item: RowItem, actualColIdx: number, visibleColIdx: number): string {
if (!colorizeDataGridCellTypes.value) return "text-foreground";
const value = item.data[actualColIdx];
const isMongoDocumentNull = props.mongoCollectionGrid === true && value === MONGO_DOCUMENT_GRID_NULL;
if (isMongoDocumentNull) return "text-muted-foreground italic";
if (!colorizeDataGridCellTypes.value) return "text-foreground";
const checkbox = booleanCellsUseCheckbox.value && isBooleanGridCell(item, actualColIdx) && value !== null;
return dataGridCellTextClass({
colorizeTypes: colorizeDataGridCellTypes.value,
@@ -2036,10 +2038,12 @@ function gridCellTextColorClass(item: RowItem, actualColIdx: number, visibleColI
}
function transposeCellTextColorClass(recordIndex: number, actualColIdx: number): string {
if (!colorizeDataGridCellTypes.value) return "text-foreground";
const item = displayItems.value[recordIndex];
if (!item) return "text-foreground";
const value = item.data[actualColIdx];
const isMongoDocumentNull = props.mongoCollectionGrid === true && value === MONGO_DOCUMENT_GRID_NULL;
if (isMongoDocumentNull) return "text-muted-foreground italic";
if (!colorizeDataGridCellTypes.value) return "text-foreground";
return dataGridCellTextClass({
colorizeTypes: colorizeDataGridCellTypes.value,
typeKind: allColumnTypeVisualKinds.value[actualColIdx] ?? "unknown",
@@ -3947,6 +3951,10 @@ function isIoTDBTimestampColumn(columnIndex: number): boolean {
}
function inlineCellEditorText(value: CellValue, columnIndex: number): string {
if (props.mongoCollectionGrid) {
const documentGridText = mongoDocumentGridEditorText(value);
if (documentGridText !== undefined) return documentGridText;
}
const columnInfo = tableColumnForGridColumn(columnIndex) ?? resultColumnInfoForGridColumn(columnIndex);
const columnType = props.result.column_types?.[columnIndex] ?? columnInfo?.data_type;
return (
@@ -7073,6 +7081,7 @@ function drawCanvasGrid() {
searchMatchKeys: searchMatchSet.value,
currentSearchMatch: currentSearchMatch.value,
formatCell: (value, columnIndex, row) => formatCellCached(visibleLargeValuePreviewValue(row, columnIndex, value), columnIndex, largeValueOriginalBytes(row, columnIndex)),
isNullValue: (value) => value === null || (props.mongoCollectionGrid === true && value === MONGO_DOCUMENT_GRID_NULL),
newRowCellPlaceholder,
isRowActive,
rowCellsUseSelectionVisual,
@@ -7395,7 +7404,7 @@ const {
columnComments: visibleColumnComments,
allColumnComments,
displayValue: formatCellCached,
cellClipboardText: (value) => (props.mongoCollectionGrid ? mongoDocumentGridEditorText(value) : undefined),
cellClipboardText: (value) => (props.mongoCollectionGrid ? mongoDocumentGridClipboardText(value) : undefined),
externalCellValue: (value) => (props.mongoCollectionGrid ? mongoDocumentGridExternalValue(value) : value),
mongoDocuments: computed(() => props.result.mongo_copy_documents ?? props.result.mongo_documents),
spatialColumns: computed(() => props.result.spatial_columns),
@@ -8933,7 +8942,7 @@ async function onGridKeydown(event: KeyboardEvent) {
function detailClipboardText(detail: DataGridCellDetail): string {
if (props.mongoCollectionGrid) {
const documentGridText = mongoDocumentGridEditorText(detail.value);
const documentGridText = mongoDocumentGridClipboardText(detail.value);
if (documentGridText !== undefined) return documentGridText;
}
if (detail.value === null) return "";
@@ -103,9 +103,10 @@ describe("data grid cell text visual priority", () => {
expect(dataGridSource).toContain("'cursor-text hover:bg-gray-200 hover:text-foreground dark:hover:bg-gray-800':");
});
it("returns before reading cell state when type colors are disabled", () => {
expect(dataGridSource).toContain('function gridCellTextColorClass(item: RowItem, actualColIdx: number, visibleColIdx: number): string {\n if (!colorizeDataGridCellTypes.value) return "text-foreground";\n const value = item.data[actualColIdx];');
expect(dataGridSource).toContain('function transposeCellTextColorClass(recordIndex: number, actualColIdx: number): string {\n if (!colorizeDataGridCellTypes.value) return "text-foreground";\n const item = displayItems.value[recordIndex];');
it("keeps Mongo BSON null muted when type colors are disabled", () => {
const mongoNullPriority = 'const isMongoDocumentNull = props.mongoCollectionGrid === true && value === MONGO_DOCUMENT_GRID_NULL;\n if (isMongoDocumentNull) return "text-muted-foreground italic";\n if (!colorizeDataGridCellTypes.value) return "text-foreground";';
expect(dataGridSource.split(mongoNullPriority)).toHaveLength(3);
});
it("places data-grid type selectors in the components layer", () => {
@@ -80,6 +80,7 @@ export interface DrawCanvasDataGridOptions {
searchMatchKeys: ReadonlySet<number>;
currentSearchMatch: CanvasSearchMatch | null;
formatCell: (value: CellValue, columnIndex: number, row: CanvasDataGridRow) => string;
isNullValue?: (value: CellValue) => boolean;
columnIsBoolean?: (columnIndex: number) => boolean;
newRowCellPlaceholder?: (row: CanvasDataGridRow, columnIndex: number) => string | null;
isRowActive: (rowIndex: number) => boolean;
@@ -345,6 +346,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
searchMatchKeys,
currentSearchMatch,
formatCell,
isNullValue,
newRowCellPlaceholder,
isRowActive,
rowCellsUseSelectionVisual,
@@ -558,6 +560,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
ctx.rect(clippedX, y, Math.min(cellPaintWidth, width - clippedX), CANVAS_DATA_GRID_ROW_HEIGHT);
ctx.clip();
const value = item.data[actualColIdx];
const isNullCell = isNullValue?.(value) ?? value === null;
const isBooleanCell = columnIsBoolean?.(actualColIdx) === true && isBooleanCellValue(value);
const isRightAlign = columnAligns?.[visibleColIdx] === "right";
const isEditingThisCell = editingCell?.rowId === item.id && editingCell.col === actualColIdx;
@@ -567,7 +570,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
const textRole = resolveDataGridCellTextRole({
colorizeTypes: colorizeDataTypes,
typeKind,
isNull: value === null,
isNull: isNullCell,
isDraft: item.isDraft && value === null,
isEditing: isEditingThisCell,
isControl: shouldRenderBooleanCheckbox,
@@ -580,8 +583,8 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
const cellTextColor = textRole === "muted" ? theme.mutedForeground : textRole === "type" ? dataGridTypeForeground(theme, typeKind) : theme.foreground;
ctx.textAlign = isBooleanNullCell ? "center" : isRightAlign ? "right" : "left";
ctx.fillStyle = cellTextColor;
ctx.font = value === null ? italicFont : tabularFont;
setCanvasNumericVariant(ctx, value === null ? "normal" : "tabular-nums");
ctx.font = isNullCell ? italicFont : tabularFont;
setCanvasNumericVariant(ctx, isNullCell ? "normal" : "tabular-nums");
const reservedWidth = rightAlignedActionCell?.rowIndex === item.displayIndex && rightAlignedActionCell.visibleColIdx === visibleColIdx ? rightAlignedActionCell.reservedWidth : 0;
const { textAnchorX, maxWidth: cellMaxWidth } = resolveCanvasCellTextLayout({ drawX, colWidth, dpr: scaleX, isRightAlign, reservedWidth });
if (shouldRenderBooleanCheckbox) {
@@ -145,10 +145,18 @@ function mongoDocumentGridEscapedString(value: unknown): string | undefined {
/** Returns the text presented in a collection-grid editor, when customized. */
export function mongoDocumentGridEditorText(value: unknown): string | undefined {
if (value === MONGO_DOCUMENT_GRID_NULL) return "NULL";
// An existing BSON null is represented as NULL in the grid, but editing it
// starts with an empty input. The private marker must never be user-facing.
if (value === MONGO_DOCUMENT_GRID_NULL) return "";
return mongoDocumentGridEscapedString(value);
}
/** Returns the text used when copying a collection-grid cell. */
export function mongoDocumentGridClipboardText(value: unknown): string | undefined {
if (value === MONGO_DOCUMENT_GRID_NULL) return "NULL";
return mongoDocumentGridEditorText(value);
}
/** Returns the custom display text required by collection-grid BSON values. */
export function mongoDocumentGridDisplayText(value: unknown): string | undefined {
if (value === MONGO_DOCUMENT_GRID_NULL) return "NULL";
+8
View File
@@ -11,6 +11,7 @@ import {
MONGO_DOCUMENT_GRID_NULL,
mongoDocumentDisplayValue,
mongoDocumentGridDisplayText,
mongoDocumentGridClipboardText,
mongoDocumentGridEditorText,
mongoDocumentGridExternalValue,
mongoDocumentGridInputValue,
@@ -403,6 +404,13 @@ test("escapes Mongo collection-grid values reserved for BSON null state", () =>
});
});
test("keeps BSON null empty in editors while preserving a copy marker", () => {
assert.equal(mongoDocumentGridEditorText(MONGO_DOCUMENT_GRID_NULL), "");
assert.equal(mongoDocumentGridClipboardText(MONGO_DOCUMENT_GRID_NULL), "NULL");
assert.equal(mongoDocumentGridEditorText("NULL"), undefined);
assert.equal(mongoDocumentGridClipboardText("NULL"), undefined);
});
test("restores internal Mongo collection-grid values for external output", () => {
assert.equal(mongoDocumentGridExternalValue(MONGO_DOCUMENT_GRID_NULL), null);
assert.equal(mongoDocumentGridExternalValue("NULL"), "NULL");