feat(sidebar): extend filters to views and routines
This commit is contained in:
1 parent
3dbcc2d0dc
commit
b74d49e982
20 files changed
+268
-46
No files matched your search
@@ -74,6 +74,7 @@ import { resolveDefaultDatabase } from "@/lib/database/defaultDatabase";
|
||||
import { connectionUsesVisibleSchemaFilter } from "@/lib/database/visibleDatabases";
|
||||
import { canTreeNodePin, canTreeNodeShowExpander } from "@/lib/sidebar/sidebarTreeItemLayout";
|
||||
import { sidebarConnectionVisibleFilterMenu } from "@/lib/sidebar/sidebarVisibleFilterMenu";
|
||||
import { supportsSidebarObjectNameFilter } from "@/lib/sidebar/sidebarObjectNameFilter";
|
||||
import { connectionGroupDestinationRows } from "@/lib/sidebar/sidebarLayout";
|
||||
import { objectTypesForGroupNode } from "@/lib/table/tableTree";
|
||||
import { loadSidebarObjectGroup } from "@/lib/sidebar/sidebarObjectGroupRouting";
|
||||
@@ -5658,7 +5659,7 @@ function buildObjectGroupSidebarMenu(context: SidebarMenuFactoryContext): boolea
|
||||
disabled: node.isLoading,
|
||||
});
|
||||
}
|
||||
if (node.type === "group-tables") {
|
||||
if (supportsSidebarObjectNameFilter(node)) {
|
||||
items.push({
|
||||
label: t("contextMenu.tableNameFilters"),
|
||||
action: () => emit("open-table-name-filters", node),
|
||||
|
||||
@@ -69,6 +69,7 @@ import { connectionBearingGroupIdsUnder, connectionIdsUnderGroup } from "@/lib/s
|
||||
import { isSidebarDatabaseOpenForVisual } from "@/lib/sidebar/sidebarDatabaseOpenState";
|
||||
import { sidebarTreeContextKey } from "@/lib/sidebar/sidebarTreeContext";
|
||||
import { connectionCanConfigureSidebarVisibleDatabases } from "@/lib/sidebar/sidebarVisibleFilterMenu";
|
||||
import { supportsSidebarObjectNameFilter } from "@/lib/sidebar/sidebarObjectNameFilter";
|
||||
import { isWindows } from "@/lib/backend/platform";
|
||||
import { flattenTree } from "@/composables/useFlatTree";
|
||||
import { productionContextForDatabase } from "@/lib/database/productionSafety";
|
||||
@@ -407,8 +408,8 @@ function visibleLabel(node: TreeNode): string {
|
||||
return withValidity(displayLabel(node));
|
||||
}
|
||||
|
||||
function hasActiveTableNameFilter(node: TreeNode): boolean {
|
||||
if (node.type !== "group-tables" || !node.connectionId || !node.database) return false;
|
||||
function hasActiveObjectNameFilter(node: TreeNode): boolean {
|
||||
if (!supportsSidebarObjectNameFilter(node) || !node.connectionId || !node.database) return false;
|
||||
const filter = connectionStore.tableNameFilterForScope({
|
||||
connectionId: node.connectionId,
|
||||
database: node.database,
|
||||
@@ -1471,7 +1472,7 @@ function onKeydown(event: KeyboardEvent) {
|
||||
node.objectCount != null
|
||||
"
|
||||
class="text-muted-foreground text-[10px] shrink-0"
|
||||
>{{ node.objectCount }}<span v-if="hasActiveTableNameFilter(node)"> · {{ t("tree.tableNameFilterActive") }}</span></span
|
||||
>{{ node.objectCount }}<span v-if="hasActiveObjectNameFilter(node)"> · {{ t("tree.tableNameFilterActive") }}</span></span
|
||||
>
|
||||
<Badge v-if="isNodeDefaultDatabase" variant="secondary" class="h-4 px-1.5 text-[10px]">
|
||||
{{ t("editor.defaultDatabase") }}
|
||||
|
||||
@@ -2783,9 +2783,9 @@ export default {
|
||||
refreshChildren: "Refresh",
|
||||
expandAll: "Expand All",
|
||||
tableNameFilters: "Filters...",
|
||||
tableNameFiltersDescription: "Show only table names that match the include list, then hide names that match the exclude list. One SQL LIKE pattern per line.",
|
||||
tableNameFilterInclude: "Include table patterns",
|
||||
tableNameFilterExclude: "Exclude table patterns",
|
||||
tableNameFiltersDescription: "Show only object names that match the include list, then hide names that match the exclude list. One SQL LIKE pattern per line.",
|
||||
tableNameFilterInclude: "Include object name patterns",
|
||||
tableNameFilterExclude: "Exclude object name patterns",
|
||||
tableNameFilterIncludePlaceholder: "Example:\nads_%\nuser_%",
|
||||
tableNameFilterExcludePlaceholder: "Example:\n%_bak\ntmp_%",
|
||||
tableNameFilterLikeHint: "Use SQL LIKE syntax: % matches any characters, _ matches one character. Matching is case-insensitive.",
|
||||
|
||||
@@ -2716,9 +2716,9 @@ export default withEnglishFallback({
|
||||
refreshChildren: "Actualizar",
|
||||
expandAll: "Expandir todo",
|
||||
tableNameFilters: "Filtros...",
|
||||
tableNameFiltersDescription: "Muestra solo tablas que coincidan con include y oculta las que coincidan con exclude. Un patrón SQL LIKE por línea.",
|
||||
tableNameFilterInclude: "Patrones de tablas incluidas",
|
||||
tableNameFilterExclude: "Patrones de tablas excluidas",
|
||||
tableNameFiltersDescription: "Muestra solo objetos que coincidan con include y oculta los que coincidan con exclude. Un patrón SQL LIKE por línea.",
|
||||
tableNameFilterInclude: "Patrones de nombres de objetos incluidos",
|
||||
tableNameFilterExclude: "Patrones de nombres de objetos excluidos",
|
||||
tableNameFilterIncludePlaceholder: "Ejemplo:\nads_%\nuser_%",
|
||||
tableNameFilterExcludePlaceholder: "Ejemplo:\n%_bak\ntmp_%",
|
||||
tableNameFilterLikeHint: "Usa sintaxis SQL LIKE: % coincide con cualquier texto, _ con un carácter. No distingue mayúsculas.",
|
||||
|
||||
@@ -2714,9 +2714,9 @@ export default withEnglishFallback({
|
||||
refreshChildren: "Aggiorna",
|
||||
expandAll: "Espandi tutto",
|
||||
tableNameFilters: "Filtri...",
|
||||
tableNameFiltersDescription: "Mostra solo le tabelle che corrispondono alla lista include, poi nasconde quelle che corrispondono alla lista exclude. Un pattern SQL LIKE per riga.",
|
||||
tableNameFilterInclude: "Pattern tabelle incluse",
|
||||
tableNameFilterExclude: "Pattern tabelle escluse",
|
||||
tableNameFiltersDescription: "Mostra solo gli oggetti che corrispondono alla lista include, poi nasconde quelli che corrispondono alla lista exclude. Un pattern SQL LIKE per riga.",
|
||||
tableNameFilterInclude: "Pattern nomi oggetto inclusi",
|
||||
tableNameFilterExclude: "Pattern nomi oggetto esclusi",
|
||||
tableNameFilterIncludePlaceholder: "Esempio:\nads_%\nuser_%",
|
||||
tableNameFilterExcludePlaceholder: "Esempio:\n%_bak\ntmp_%",
|
||||
tableNameFilterLikeHint: "Usa la sintassi SQL LIKE: % corrisponde a qualsiasi testo, _ a un carattere. Match case-insensitive.",
|
||||
|
||||
@@ -2738,9 +2738,9 @@ export default withEnglishFallback({
|
||||
refreshChildren: "更新",
|
||||
expandAll: "すべて展開",
|
||||
tableNameFilters: "フィルター...",
|
||||
tableNameFiltersDescription: "Include リストに一致するテーブル名だけを表示し、Exclude リストに一致する名前を非表示にします。1 行に 1 つの SQL LIKE パターンを入力します。",
|
||||
tableNameFilterInclude: "含めるテーブル名パターン",
|
||||
tableNameFilterExclude: "除外するテーブル名パターン",
|
||||
tableNameFiltersDescription: "Include リストに一致するオブジェクト名だけを表示し、Exclude リストに一致する名前を非表示にします。1 行に 1 つの SQL LIKE パターンを入力します。",
|
||||
tableNameFilterInclude: "含めるオブジェクト名パターン",
|
||||
tableNameFilterExclude: "除外するオブジェクト名パターン",
|
||||
tableNameFilterIncludePlaceholder: "例:\nads_%\nuser_%",
|
||||
tableNameFilterExcludePlaceholder: "例:\n%_bak\ntmp_%",
|
||||
tableNameFilterLikeHint: "SQL LIKE 構文を使用します。% は任意の文字列、_ は 1 文字に一致します。大文字小文字は区別しません。",
|
||||
|
||||
@@ -2625,9 +2625,9 @@ export default withEnglishFallback({
|
||||
refreshChildren: "새로고침",
|
||||
expandAll: "모두 펼치기",
|
||||
tableNameFilters: "필터...",
|
||||
tableNameFiltersDescription: "포함 목록과 일치하는 테이블 이름만 표시한 다음 제외 목록과 일치하는 이름을 숨깁니다. 한 줄에 하나의 SQL LIKE 패턴.",
|
||||
tableNameFilterInclude: "포함 테이블 패턴",
|
||||
tableNameFilterExclude: "제외 테이블 패턴",
|
||||
tableNameFiltersDescription: "포함 목록과 일치하는 객체 이름만 표시한 다음 제외 목록과 일치하는 이름을 숨깁니다. 한 줄에 하나의 SQL LIKE 패턴.",
|
||||
tableNameFilterInclude: "포함 객체 이름 패턴",
|
||||
tableNameFilterExclude: "제외 객체 이름 패턴",
|
||||
tableNameFilterIncludePlaceholder: "예:\nads_%\nuser_%",
|
||||
tableNameFilterExcludePlaceholder: "예:\n%_bak\ntmp_%",
|
||||
tableNameFilterLikeHint: "SQL LIKE 문법을 사용하세요: %는 임의의 문자, _는 한 문자와 일치합니다. 대소문자를 구분하지 않습니다.",
|
||||
|
||||
@@ -2716,9 +2716,9 @@ export default withEnglishFallback({
|
||||
refreshChildren: "Atualizar",
|
||||
expandAll: "Expandir tudo",
|
||||
tableNameFilters: "Filtros...",
|
||||
tableNameFiltersDescription: "Mostra apenas tabelas que correspondem à lista include e oculta as que correspondem à lista exclude. Um padrão SQL LIKE por linha.",
|
||||
tableNameFilterInclude: "Padrões de tabelas incluídas",
|
||||
tableNameFilterExclude: "Padrões de tabelas excluídas",
|
||||
tableNameFiltersDescription: "Mostra apenas objetos que correspondem à lista include e oculta os que correspondem à lista exclude. Um padrão SQL LIKE por linha.",
|
||||
tableNameFilterInclude: "Padrões de nomes de objetos incluídos",
|
||||
tableNameFilterExclude: "Padrões de nomes de objetos excluídos",
|
||||
tableNameFilterIncludePlaceholder: "Exemplo:\nads_%\nuser_%",
|
||||
tableNameFilterExcludePlaceholder: "Exemplo:\n%_bak\ntmp_%",
|
||||
tableNameFilterLikeHint: "Use sintaxe SQL LIKE: % corresponde a qualquer texto, _ a um caractere. A comparação ignora maiúsculas.",
|
||||
|
||||
@@ -2707,9 +2707,9 @@ export default withEnglishFallback({
|
||||
refreshChildren: "刷新",
|
||||
expandAll: "展开全部",
|
||||
tableNameFilters: "过滤器...",
|
||||
tableNameFiltersDescription: "先仅显示命中 include 列表的表名,再隐藏命中 exclude 列表的表名。每行一个 SQL LIKE 模式。",
|
||||
tableNameFilterInclude: "包含表名模式",
|
||||
tableNameFilterExclude: "排除表名模式",
|
||||
tableNameFiltersDescription: "先仅显示命中 include 列表的对象名称,再隐藏命中 exclude 列表的对象名称。每行一个 SQL LIKE 模式。",
|
||||
tableNameFilterInclude: "包含对象名称模式",
|
||||
tableNameFilterExclude: "排除对象名称模式",
|
||||
tableNameFilterIncludePlaceholder: "例如:\nads_%\nuser_%",
|
||||
tableNameFilterExcludePlaceholder: "例如:\n%_bak\ntmp_%",
|
||||
tableNameFilterLikeHint: "使用 SQL LIKE 语法:% 匹配任意字符,_ 匹配单个字符。匹配时不区分大小写。",
|
||||
|
||||
@@ -2714,9 +2714,9 @@ export default withEnglishFallback({
|
||||
refreshChildren: "重新整理",
|
||||
expandAll: "展開全部",
|
||||
tableNameFilters: "篩選器...",
|
||||
tableNameFiltersDescription: "先僅顯示符合 include 清單的資料表名稱,再隱藏符合 exclude 清單的資料表名稱。每行一個 SQL LIKE 模式。",
|
||||
tableNameFilterInclude: "包含資料表名稱模式",
|
||||
tableNameFilterExclude: "排除資料表名稱模式",
|
||||
tableNameFiltersDescription: "先僅顯示符合 include 清單的物件名稱,再隱藏符合 exclude 清單的物件名稱。每行一個 SQL LIKE 模式。",
|
||||
tableNameFilterInclude: "包含物件名稱模式",
|
||||
tableNameFilterExclude: "排除物件名稱模式",
|
||||
tableNameFilterIncludePlaceholder: "範例:\nads_%\nuser_%",
|
||||
tableNameFilterExcludePlaceholder: "範例:\n%_bak\ntmp_%",
|
||||
tableNameFilterLikeHint: "使用 SQL LIKE 語法:% 符合任意字元,_ 符合單一字元。比對不區分大小寫。",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { supportsSidebarObjectNameFilter } from "../../sidebar/sidebarObjectNameFilter";
|
||||
|
||||
describe("supportsSidebarObjectNameFilter", () => {
|
||||
it("allows only supported object groups", () => {
|
||||
const supportedTypes = ["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions"] as const;
|
||||
expect(supportedTypes.every((type) => supportsSidebarObjectNameFilter({ type, parentType: undefined }))).toBe(true);
|
||||
expect(supportsSidebarObjectNameFilter({ type: "group-indexes", parentType: undefined })).toBe(false);
|
||||
expect(supportsSidebarObjectNameFilter({ type: "group-triggers", parentType: undefined })).toBe(false);
|
||||
expect(supportsSidebarObjectNameFilter({ type: "group-events", parentType: undefined })).toBe(false);
|
||||
expect(supportsSidebarObjectNameFilter({ type: "group-functions", parentType: "package" })).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -829,7 +829,7 @@ export async function getTableComment(_connectionId: string, _database: string,
|
||||
throw new Error("Table comment lookup is not available in the web backend");
|
||||
}
|
||||
|
||||
export async function listObjects(connectionId: string, database: string, schema: string, objectTypes?: (SidebarObjectKind | "EVENT")[], filter?: string, limit?: number, offset?: number, catalog?: string): Promise<ObjectInfo[]> {
|
||||
export async function listObjects(connectionId: string, database: string, schema: string, objectTypes?: (SidebarObjectKind | "EVENT")[], filter?: string, limit?: number, offset?: number, catalog?: string, tableNameFilter?: TableNameFilter): Promise<ObjectInfo[]> {
|
||||
return get(
|
||||
`/api/schema/objects?${qs({
|
||||
connection_id: connectionId,
|
||||
@@ -840,6 +840,7 @@ export async function listObjects(connectionId: string, database: string, schema
|
||||
limit,
|
||||
offset,
|
||||
catalog,
|
||||
table_name_filter: tableNameFilter ? JSON.stringify(tableNameFilter) : undefined,
|
||||
})}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1093,7 +1093,7 @@ export async function getTableComment(connectionId: string, database: string, sc
|
||||
});
|
||||
}
|
||||
|
||||
export async function listObjects(connectionId: string, database: string, schema: string, objectTypes?: (SidebarObjectKind | "EVENT")[], filter?: string, limit?: number, offset?: number, catalog?: string): Promise<ObjectInfo[]> {
|
||||
export async function listObjects(connectionId: string, database: string, schema: string, objectTypes?: (SidebarObjectKind | "EVENT")[], filter?: string, limit?: number, offset?: number, catalog?: string, tableNameFilter?: import("@/types/database").TableNameFilter): Promise<ObjectInfo[]> {
|
||||
return invoke("list_objects", {
|
||||
connectionId,
|
||||
database,
|
||||
@@ -1103,6 +1103,7 @@ export async function listObjects(connectionId: string, database: string, schema
|
||||
limit,
|
||||
offset,
|
||||
catalog,
|
||||
tableNameFilter,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { TreeNode, TreeNodeType } from "@/types/database";
|
||||
|
||||
const sidebarObjectNameFilterGroupTypes: ReadonlySet<TreeNodeType> = new Set(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions"]);
|
||||
|
||||
type SidebarObjectNameFilterTarget = {
|
||||
type: TreeNodeType;
|
||||
parentType?: TreeNode["parentType"];
|
||||
};
|
||||
|
||||
export function supportsSidebarObjectNameFilter(node: SidebarObjectNameFilterTarget): boolean {
|
||||
return node.parentType !== "package" && sidebarObjectNameFilterGroupTypes.has(node.type);
|
||||
}
|
||||
@@ -1715,9 +1715,9 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
tableListSourceRevisions.set(connectionId, tableListSourceRevision(connectionId) + 1);
|
||||
}
|
||||
|
||||
function tableNameFilterMetadataExtra(filter: TableNameFilter | undefined, sourceRevision: number): MetadataScopeInput["extra"] {
|
||||
function tableNameFilterMetadataExtra(filter: TableNameFilter | undefined, sourceRevision?: number): MetadataScopeInput["extra"] {
|
||||
return {
|
||||
tableListSourceRevision: sourceRevision,
|
||||
...(sourceRevision === undefined ? {} : { tableListSourceRevision: sourceRevision }),
|
||||
...(filter
|
||||
? {
|
||||
tableNameFilterInclude: filter.includePatterns,
|
||||
@@ -2066,6 +2066,14 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
return { children: [], objectCount: 0, hasMore: false, nextOffset: options.offset };
|
||||
}
|
||||
const searchFilter = options.searchFilter || undefined;
|
||||
const userTableNameFilter = activeTableNameFilterForScope({
|
||||
connectionId: options.node.connectionId,
|
||||
database: options.node.database,
|
||||
schema: options.node.schema,
|
||||
nodeKind: options.node.type,
|
||||
catalog: options.node.catalog,
|
||||
});
|
||||
const tableNameFilter = effectiveTableNameFilterForNode(options.node, userTableNameFilter);
|
||||
const fetchLimit = searchFilter ? undefined : options.pageSize + 1;
|
||||
const fetchOffset = searchFilter ? undefined : options.offset;
|
||||
const objects = await loadCachedMetadataListPage<ObjectInfo[]>(
|
||||
@@ -2080,8 +2088,12 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
limit: fetchLimit,
|
||||
offset: fetchOffset,
|
||||
sidebarDisplayMode: useSettingsStore().editorSettings.sidebarObjectDisplay,
|
||||
extra: tableNameFilterMetadataExtra(tableNameFilter),
|
||||
}),
|
||||
() => api.listObjects(options.node.connectionId!, options.node.database!, options.querySchema, options.objectTypes, searchFilter, fetchLimit, fetchOffset),
|
||||
() =>
|
||||
tableNameFilter || options.node.catalog
|
||||
? api.listObjects(options.node.connectionId!, options.node.database!, options.querySchema, options.objectTypes, searchFilter, fetchLimit, fetchOffset, options.node.catalog, tableNameFilter)
|
||||
: api.listObjects(options.node.connectionId!, options.node.database!, options.querySchema, options.objectTypes, searchFilter, fetchLimit, fetchOffset),
|
||||
{ force: options.force },
|
||||
);
|
||||
const hasMore = searchFilter ? false : objects.length > options.pageSize;
|
||||
|
||||
Reference in new issue
Block a user