feat(grid): show nullable indicator in query result headers
This commit is contained in:
5 files changed
+68
-3
No files matched your search
@@ -138,7 +138,7 @@ import { buildDataGridCellDetail, buildDataGridColumnDetail, buildDataGridRowDet
|
||||
import { applyColumnFormatter, buildColumnFormatterKey, getSupportedTimeZoneOptions, normalizeColumnFormatter, resolveColumnFormatter, type ColumnFormatterConfig, type DateTimeFormatterUnit, DateTimePatterns } from "@/lib/dataGrid/columnFormatter";
|
||||
import { temporalCellEditorConfig, type TemporalCellEditorConfig } from "@/lib/dataGrid/dataGridTemporalEditor";
|
||||
import { BOOLEAN_CELL_EDITOR_VALUES, booleanCellEditorValue, isBooleanCellValue, isBooleanColumnType, isPointInBooleanCheckbox, nextBooleanCellValue, normalizeBooleanCellValue, parseBooleanCellEditorValue } from "@/lib/dataGrid/dataGridBooleanColumn";
|
||||
import { resolveDataGridColumnsByResultIndex } from "@/lib/dataGrid/dataGridColumnMetadata";
|
||||
import { resolveDataGridColumnNullability, resolveDataGridColumnsByResultIndex } from "@/lib/dataGrid/dataGridColumnMetadata";
|
||||
import { isCancelSearchShortcut, isCopyCurrentRowShortcut, isDeleteCurrentRowShortcut, isFocusSearchShortcut, isModRShortcut, isSaveShortcut, isToggleTransposeShortcut } from "@/lib/editor/keyboardShortcuts";
|
||||
import { dataGridHeaderContentWidth, scrollbarGutterWidth } from "@/lib/dataGrid/dataGridScrollGutter";
|
||||
import { canFetchNextDataGridSegment, canGoNextDataGridPage, dataGridTotalRowCountLabelKey, dataGridTruncationHintKey, hasCompleteLocalDataGridResult, resolveDataGridPaginationTotal, type DataGridInexactTotalRowCountMode } from "@/lib/dataGrid/dataGridPagination";
|
||||
@@ -538,6 +538,10 @@ function headerColumnType(column: string, actualColIdx: number): string {
|
||||
return resolved ? shortTypeName(compactHeaderColumnType(resolved)) : "";
|
||||
}
|
||||
|
||||
function headerColumnNullability(actualColIdx: number): "nullable" | "required" | undefined {
|
||||
return resolveDataGridColumnNullability(props.context, tableColumnForGridColumn(actualColIdx));
|
||||
}
|
||||
|
||||
const reserveColumnTypeLine = computed(() => reserveDataGridHeaderLine(showColumnTypesInHeader.value, props.result.columns, (column, index) => headerColumnType(column, index)));
|
||||
// Match the rendered header columns so comments from unprojected metadata cannot add an empty row.
|
||||
const reserveColumnCommentLine = computed(() => reserveDataGridHeaderLine(showColumnCommentsInHeader.value, props.result.columns, (column) => headerColumnComment(column)));
|
||||
@@ -9355,6 +9359,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
:show-comment-line="reserveColumnCommentLine"
|
||||
:tooltip-column-type="columnTypeMap.get(col.name)"
|
||||
:tooltip-column-comment="columnCommentMap.get(col.name)"
|
||||
:column-nullability="headerColumnNullability(col.actualColIdx)"
|
||||
:type-class="typeColorClass(headerColumnType(col.name, col.actualColIdx))"
|
||||
:drag-class="columnHeaderDragClass(col.visibleColIdx)"
|
||||
:column-style="columnHeaderStyle(col.visibleColIdx)"
|
||||
@@ -9362,6 +9367,9 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
:column-name-label="t('grid.columnName')"
|
||||
:column-type-label="t('grid.columnType')"
|
||||
:column-comment-label="t('grid.columnComment')"
|
||||
:nullable-label="t('structureEditor.nullable')"
|
||||
:yes-label="t('structureEditor.yes')"
|
||||
:no-label="t('structureEditor.no')"
|
||||
:column-index-label="t('grid.tableInfoIndexes')"
|
||||
:column-primary-index-label="t('grid.columnPrimaryIndex')"
|
||||
:column-unique-index-label="t('grid.columnUniqueIndex')"
|
||||
|
||||
@@ -18,6 +18,7 @@ const props = defineProps<{
|
||||
columnComment?: string;
|
||||
tooltipColumnType?: string;
|
||||
tooltipColumnComment?: string;
|
||||
columnNullability?: "nullable" | "required";
|
||||
showTypeLine?: boolean;
|
||||
showCommentLine?: boolean;
|
||||
typeClass?: HTMLAttributes["class"];
|
||||
@@ -27,6 +28,9 @@ const props = defineProps<{
|
||||
columnNameLabel: string;
|
||||
columnTypeLabel: string;
|
||||
columnCommentLabel: string;
|
||||
nullableLabel?: string;
|
||||
yesLabel?: string;
|
||||
noLabel?: string;
|
||||
columnIndexLabel: string;
|
||||
columnPrimaryIndexLabel: string;
|
||||
columnUniqueIndexLabel: string;
|
||||
@@ -71,7 +75,10 @@ const emit = defineEmits<{
|
||||
<KeyRound v-if="columnIndexKind === 'primary'" class="h-3 w-3 shrink-0" :class="columnIndexColorClass(columnIndexKind)" :title="columnIndexText(columnIndexKind)" />
|
||||
<Hash v-else-if="columnIndexKind && columnIndexKind !== 'none'" class="h-3 w-3 shrink-0" :class="columnIndexColorClass(columnIndexKind)" :title="columnIndexText(columnIndexKind)" />
|
||||
<span class="flex min-w-0 flex-1 flex-col overflow-hidden">
|
||||
<span class="min-w-0 truncate leading-4">{{ name }}</span>
|
||||
<span class="flex min-w-0 items-center gap-1 leading-4">
|
||||
<span class="min-w-0 truncate">{{ name }}</span>
|
||||
<span v-if="columnNullability === 'nullable'" data-grid-header-nullable class="shrink-0 rounded-sm border border-muted-foreground/40 px-0.5 text-[8px] font-semibold leading-3 text-muted-foreground" :title="nullableLabel" :aria-label="nullableLabel"> NULL </span>
|
||||
</span>
|
||||
<span v-if="showTypeLine" data-grid-header-type-line class="h-3 min-w-0 truncate text-[10px] font-normal leading-3" :class="[typeClass, { invisible: !columnType }]" :title="columnType || undefined" :aria-hidden="columnType ? undefined : true">{{ columnType }}</span>
|
||||
<span v-if="showCommentLine" data-grid-header-comment-line class="h-3 min-w-0 truncate text-[10px] font-normal leading-3 text-muted-foreground" :class="{ invisible: !columnComment }" :title="columnComment || undefined" :aria-hidden="columnComment ? undefined : true">{{
|
||||
columnComment
|
||||
@@ -98,6 +105,10 @@ const emit = defineEmits<{
|
||||
<span class="text-background/70">{{ columnCommentLabel }}</span>
|
||||
<span>{{ tooltipColumnComment ?? columnComment }}</span>
|
||||
</template>
|
||||
<template v-if="columnNullability">
|
||||
<span class="text-background/70">{{ nullableLabel }}</span>
|
||||
<span>{{ columnNullability === "nullable" ? yesLabel : noLabel }}</span>
|
||||
</template>
|
||||
<template v-if="columnIndexKind && columnIndexKind !== 'none'">
|
||||
<span class="text-background/70">{{ columnIndexLabel }}</span>
|
||||
<span class="flex items-center gap-1">
|
||||
|
||||
@@ -384,6 +384,34 @@ describe("DataGridColumnHeader", () => {
|
||||
expect(findAll(mounted.root, (node) => node.props["data-grid-header-type-line"] === "")).toHaveLength(0);
|
||||
expect(findAll(mounted.root, (node) => node.props["data-grid-header-comment-line"] === "")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("marks nullable columns without marking required columns", () => {
|
||||
const baseProps = {
|
||||
name: "nickname",
|
||||
actualColumnIndex: 1,
|
||||
visibleColumnIndex: 1,
|
||||
copyColumnNameLabel: "copy",
|
||||
columnNameLabel: "name",
|
||||
columnTypeLabel: "type",
|
||||
columnCommentLabel: "comment",
|
||||
nullableLabel: "nullable",
|
||||
yesLabel: "yes",
|
||||
noLabel: "no",
|
||||
columnIndexLabel: "index",
|
||||
columnPrimaryIndexLabel: "primary",
|
||||
columnUniqueIndexLabel: "unique",
|
||||
columnRegularIndexLabel: "regular",
|
||||
};
|
||||
const nullable = mountComponent(DataGridColumnHeader, { ...baseProps, columnNullability: "nullable" });
|
||||
const required = mountComponent(DataGridColumnHeader, { ...baseProps, columnNullability: "required" });
|
||||
const badge = findOne(nullable.root, (node) => node.props["data-grid-header-nullable"] === "");
|
||||
|
||||
expect(hostText(badge).trim()).toBe("NULL");
|
||||
expect(badge.props.title).toBe("nullable");
|
||||
expect(hostText(nullable.root)).toContain("nullableyes");
|
||||
expect(findAll(required.root, (node) => node.props["data-grid-header-nullable"] === "")).toHaveLength(0);
|
||||
expect(hostText(required.root)).toContain("nullableno");
|
||||
});
|
||||
});
|
||||
|
||||
describe("DataGridFilterBuilder", () => {
|
||||
|
||||
Reference in new issue
Block a user