UNPKG

@finos/legend-application-studio

Version:
331 lines 31.3 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; /** * Copyright (c) 2020-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { observer } from 'mobx-react-lite'; import { useState } from 'react'; import { ChevronDownIcon, ChevronRightIcon, CompressIcon, CopyIcon, ExpandAllIcon, ExternalLinkIcon, EyeIcon, FilterIcon, KeyIcon, PURE_DatabaseIcon, PURE_DatabaseSchemaIcon, PURE_DatabaseTableIcon, PURE_DatabaseTableJoinIcon, PURE_DataProductIcon, SearchIcon, TimesIcon, clsx, } from '@finos/legend-art'; import {} from '@finos/legend-graph'; import { DatabaseAnnotationDisplay } from './DatabaseAnnotationDisplay.js'; import { getRelationNodeId, getSchemaNodeId, } from '../../../../stores/editor/editor-state/element-editor-state/DatabaseEditorState.js'; import { getColumnTypeLabel, getTableColumns, isCrossDatabaseJoin, isPrimaryKey, isSelfJoin, matchesSearch, resolveFilterFormula, resolveJoinFormula, resolveViewColumnFormula, resolveViewGroupByFormula, summarizeMilestoning, } from './DatabaseDiagramHelper.js'; /** * Small inline copy-to-clipboard button rendered next to rendered Pure code * (filter/join/view-column/groupBy formulas). Two-state visual: shows the * default copy icon, briefly flips to a "Copied!" label after a successful * write, then resets. Click stops propagation so the surrounding row's * click handler (which usually selects/focuses the row) doesn't fire. */ const CopyFormulaButton = ({ value, label = 'Copy', }) => { const [copied, setCopied] = useState(false); return (_jsx("button", { type: "button", className: clsx('database-diagram__copy-btn', { 'database-diagram__copy-btn--copied': copied, }), title: copied ? 'Copied!' : label, onClick: (event) => { event.stopPropagation(); // `navigator.clipboard` may be unavailable in test/insecure contexts; // fall through silently in that case rather than throwing. navigator.clipboard .writeText(value) .then(() => { setCopied(true); window.setTimeout(() => setCopied(false), 1200); }) .catch(() => { /* no-op \u2014 copy failures are non-fatal */ }); }, children: _jsx(CopyIcon, {}) })); }; /** * Pure-CSS shimmer placeholder shown in place of formula text while the * batched engine call is still in flight. We swap from skeleton to text * once the corresponding `is...Loading` flag flips false on the editor * state. Compact one-line bar so layout doesn't shift on resolve. */ const FormulaSkeleton = () => (_jsx("span", { className: "database-diagram__skeleton", "aria-label": "Loading formula", "aria-busy": true })); /** * Empty-state row shown when a side-panel section has zero rows of its * primary kind (e.g. a database with no joins). Lighter than rendering * nothing because users sometimes wonder if the panel is broken when a * section is silently absent. */ const EmptySectionRow = ({ message }) => (_jsx("div", { className: "database-diagram__side-panel__empty", children: message })); /** * Predicates that determine whether a tree node should be rendered under * the current search filter. A node is shown when it OR any descendant * matches the (already lowercased) query. Empty query short-circuits so * everything is shown, which keeps the no-filter render path cheap. */ const relationMatchesSearch = (rel, query) => { if (!query) { return true; } if (matchesSearch(rel.name, query)) { return true; } // For tables walk Column.name; for views walk ColumnMapping.columnName. // Either way the comparison is a flat substring on visible identifiers. if ('columnMappings' in rel) { return rel.columnMappings.some((m) => matchesSearch(m.columnName, query)); } return getTableColumns(rel).some((c) => matchesSearch(c.name, query)); }; const schemaMatchesSearch = (schema, query) => { if (!query) { return true; } if (matchesSearch(schema.name, query)) { return true; } return (schema.tables.some((t) => relationMatchesSearch(t, query)) || schema.views.some((v) => relationMatchesSearch(v, query))); }; /** * Renders one column under an expanded Table. Clicking focuses the column, * which (per `DatabaseEditorState.focusOnColumn`) selects the parent table, * marks the specific column, and requests a canvas pan. * * Columns aren't expandable — they're leaves of the tree. */ const DatabaseTreeTableColumnRow = observer((props) => { const { editorState, table, column } = props; const isSelected = editorState.selectedColumn === column; const isPK = isPrimaryKey(table, column.name); return (_jsxs("button", { type: "button", className: clsx('database-diagram__tree__column', { 'database-diagram__tree__column--selected': isSelected, 'database-diagram__tree__column--pk': isPK, }), onClick: () => editorState.focusOnColumn(table, column), title: `${table.schema.name}.${table.name}.${column.name}: ${getColumnTypeLabel(column)}`, children: [_jsx("span", { className: "database-diagram__tree__column__icon", children: isPK ? (_jsx(KeyIcon, {})) : (_jsx("span", { className: "database-diagram__tree__column__bullet", children: "\u2022" })) }), _jsxs("span", { className: "database-diagram__tree__column__name", children: [column.name, column.nullable === true && (_jsx("span", { className: "database-diagram__tree__column__nullable", title: "Nullable", children: "?" }))] }), _jsx("span", { className: "database-diagram__tree__column__type", children: getColumnTypeLabel(column) }), _jsx(DatabaseAnnotationDisplay, { stereotypes: column.stereotypes, taggedValues: column.taggedValues, layout: "compact" })] })); }); /** * Renders one column-mapping under an expanded View. Visually similar to a * table-column row but the secondary text is the formula placeholder rather * than a SQL type. Not selectable individually in the MVP — clicking just * focuses the parent view. */ const DatabaseTreeViewColumnRow = observer((props) => { const { editorState, view, columnMapping } = props; const isPK = isPrimaryKey(view, columnMapping.columnName); const isSelected = editorState.selectedRelation === view && editorState.selectedViewColumnName === columnMapping.columnName; // Pull the live formula from the editor state. Re-renders when the // batched engine call resolves and updates `viewColumnFormulas`. const formula = resolveViewColumnFormula(editorState.viewColumnFormulas, view.schema.name, view.name, columnMapping.columnName); const isLoading = editorState.isLoadingViewColumnFormulas && !editorState.viewColumnFormulas.has(`${view.schema.name}.${view.name}.${columnMapping.columnName}`); return (_jsxs("button", { type: "button", className: clsx('database-diagram__tree__column', 'database-diagram__tree__column--view', { 'database-diagram__tree__column--pk': isPK, 'database-diagram__tree__column--selected': isSelected, }), onClick: () => editorState.focusOnViewColumn(view, columnMapping.columnName), title: `${view.schema.name}.${view.name}.${columnMapping.columnName}: ${formula}`, children: [_jsx("span", { className: "database-diagram__tree__column__icon", children: isPK ? (_jsx(KeyIcon, {})) : (_jsx("span", { className: "database-diagram__tree__column__bullet", children: "\u2022" })) }), _jsx("span", { className: "database-diagram__tree__column__name", children: columnMapping.columnName }), _jsx("span", { className: "database-diagram__tree__column__type", children: isLoading ? _jsx(FormulaSkeleton, {}) : formula }), !isLoading && (_jsx(CopyFormulaButton, { value: formula, label: "Copy formula" }))] })); }); /** * Renders one Table row plus (when expanded) its Column children. Clicking * the row both *selects the table on the canvas* (with a pan-to) AND toggles * expansion. Bundling these is intentional — when a user navigates to a * relation, they almost always want to see its columns too. */ const DatabaseTreeTableRow = observer((props) => { const { editorState, table } = props; const query = editorState.searchText.trim().toLowerCase(); if (!relationMatchesSearch(table, query)) { return null; } const id = getRelationNodeId(table.schema.name, table.name); // When a filter is active force-expand the row so matches deeper in // the tree are immediately visible without an extra click. const isExpanded = query !== '' || editorState.expandedRelationIds.has(id); const isSelected = editorState.selectedRelation === table; const columns = getTableColumns(table); // When filtering, only show columns that match the query (or all // columns if the table itself matched by name). const tableNameMatches = matchesSearch(table.name, query); const visibleColumns = query === '' || tableNameMatches ? columns : columns.filter((c) => matchesSearch(c.name, query)); return (_jsxs("div", { className: "database-diagram__tree__table", children: [_jsxs("button", { type: "button", className: clsx('database-diagram__tree__table__row', { 'database-diagram__tree__table__row--selected': isSelected, }), onClick: () => { editorState.focusOnRelation(table); editorState.toggleRelationExpanded(id); }, title: `${table.schema.name}.${table.name}`, children: [_jsx("span", { className: "database-diagram__tree__caret", children: isExpanded ? _jsx(ChevronDownIcon, {}) : _jsx(ChevronRightIcon, {}) }), _jsx(PURE_DatabaseTableIcon, {}), _jsx("span", { className: "database-diagram__tree__table__name", children: table.name }), _jsx("span", { className: "database-diagram__tree__table__count", children: columns.length }), _jsx(DatabaseAnnotationDisplay, { stereotypes: table.stereotypes, taggedValues: table.taggedValues, layout: "compact" })] }), isExpanded && (_jsxs("div", { className: "database-diagram__tree__table__children", children: [table.milestoning.map((milestoning) => { const summary = summarizeMilestoning(milestoning); return (_jsxs("div", { className: clsx('database-diagram__tree__table__meta-row', `database-diagram__tree__table__meta-row--milestoning-${summary.kind}`), title: summary.description, children: [_jsx("span", { className: "database-diagram__tree__table__meta-row__label", children: "milestoning" }), _jsx("span", { className: "database-diagram__tree__table__meta-row__value", children: summary.label })] }, summary.label)); }), visibleColumns.map((column) => (_jsx(DatabaseTreeTableColumnRow, { editorState: editorState, table: table, column: column }, column.name)))] }))] })); }); /** * Renders the View → Filter mapping reference under an expanded view. Shows * `<owning-database>.<filterName>`. When the referenced filter belongs to * the database currently being edited, clicking navigates to it via * `focusOnFilter`; cross-database references are non-interactive (filters * from other databases aren't reachable from this editor). */ const DatabaseTreeViewFilterRow = observer((props) => { const { editorState, view, filterMapping } = props; const ownerPath = filterMapping.filter.ownerReference.valueForSerialization ?? ''; const filterValue = filterMapping.filter.value; // Filters live on a Database; if the owning DB matches the one we're // editing we can navigate. Otherwise the row is informational. const isLocal = filterValue.owner === editorState.database; const display = `${ownerPath}.${filterMapping.filterName}`; const formula = isLocal ? resolveFilterFormula(editorState.filterFormulas, filterValue.name) : undefined; return (_jsxs("button", { type: "button", className: clsx('database-diagram__tree__column', 'database-diagram__tree__column--view', 'database-diagram__tree__column--view-meta'), onClick: () => { if (isLocal) { editorState.focusOnFilter(filterValue); } else { editorState.focusOnRelation(view); } }, disabled: !isLocal, title: formula ? `${display}: ${formula}` : `${display}${isLocal ? '' : ' (external)'}`, children: [_jsx("span", { className: "database-diagram__tree__column__icon", children: _jsx(FilterIcon, {}) }), _jsx("span", { className: "database-diagram__tree__column__name", children: "filter" }), _jsx("span", { className: "database-diagram__tree__column__type", children: display })] })); }); /** * Renders one row per `View.groupBy.columns[i]` Pure expression. Each row * lazily reads its rendered formula from `viewGroupByFormulas` — until the * batched engine call resolves it falls back to a static placeholder. * * Rows aren't clickable: the underlying RelationalOperationElement isn't a * navigable graph node and the parent view is already focused via the * surrounding tree row. */ const DatabaseTreeViewGroupByRow = observer((props) => { const { editorState, view, index } = props; const formula = resolveViewGroupByFormula(editorState.viewGroupByFormulas, view.schema.name, view.name, index); const isLoading = editorState.isLoadingViewGroupByFormulas && !editorState.viewGroupByFormulas.has(`${view.schema.name}.${view.name}.groupBy[${index}]`); return (_jsxs("div", { className: clsx('database-diagram__tree__column', 'database-diagram__tree__column--view', 'database-diagram__tree__column--view-meta', 'database-diagram__tree__column--readonly'), title: `group by [${index}]: ${formula}`, children: [_jsx("span", { className: "database-diagram__tree__column__icon", children: _jsx("span", { className: "database-diagram__tree__column__bullet", children: "\u00B7" }) }), _jsx("span", { className: "database-diagram__tree__column__name", children: `[${index}]` }), _jsx("span", { className: "database-diagram__tree__column__type", children: isLoading ? _jsx(FormulaSkeleton, {}) : formula }), !isLoading && (_jsx(CopyFormulaButton, { value: formula, label: "Copy expression" }))] })); }); /** * Renders one View row plus (when expanded) its column-mapping children. * Visually distinct from the table row via the eye icon. Same click semantics * as tables (select + expand). */ const DatabaseTreeViewRow = observer((props) => { const { editorState, view } = props; const query = editorState.searchText.trim().toLowerCase(); if (!relationMatchesSearch(view, query)) { return null; } const id = getRelationNodeId(view.schema.name, view.name); // Same auto-expand-on-filter behavior as tables. const isExpanded = query !== '' || editorState.expandedRelationIds.has(id); const isSelected = editorState.selectedRelation === view; const groupBy = view.groupBy; const groupByCount = groupBy?.columns.length ?? 0; const viewNameMatches = matchesSearch(view.name, query); const visibleMappings = query === '' || viewNameMatches ? view.columnMappings : view.columnMappings.filter((m) => matchesSearch(m.columnName, query)); return (_jsxs("div", { className: "database-diagram__tree__table database-diagram__tree__table--view", children: [_jsxs("button", { type: "button", className: clsx('database-diagram__tree__table__row', { 'database-diagram__tree__table__row--selected': isSelected, }), onClick: () => { editorState.focusOnRelation(view); editorState.toggleRelationExpanded(id); }, title: `${view.schema.name}.${view.name} (view)`, children: [_jsx("span", { className: "database-diagram__tree__caret", children: isExpanded ? _jsx(ChevronDownIcon, {}) : _jsx(ChevronRightIcon, {}) }), _jsx(EyeIcon, {}), _jsx("span", { className: "database-diagram__tree__table__name", children: view.name }), _jsx("span", { className: "database-diagram__tree__table__count", children: view.columnMappings.length }), view.distinct === true && (_jsx("span", { className: "database-diagram__tree__table__view-tag database-diagram__tree__table__view-tag--distinct", title: "View applies DISTINCT", children: "DISTINCT" })), view.filter && (_jsx("span", { className: "database-diagram__tree__table__view-tag database-diagram__tree__table__view-tag--filtered", title: `Filtered by ${view.filter.filter.ownerReference.valueForSerialization ?? ''}.${view.filter.filterName}`, children: "FILTERED" })), groupByCount > 0 && (_jsx("span", { className: "database-diagram__tree__table__view-tag database-diagram__tree__table__view-tag--grouped", title: `GROUP BY ${groupByCount} expression${groupByCount === 1 ? '' : 's'}`, children: `GROUP BY (${groupByCount})` })), _jsx(DatabaseAnnotationDisplay, { stereotypes: view.stereotypes, taggedValues: view.taggedValues, layout: "compact" })] }), isExpanded && (_jsxs("div", { className: "database-diagram__tree__table__children", children: [visibleMappings.map((mapping) => (_jsx(DatabaseTreeViewColumnRow, { editorState: editorState, view: view, columnMapping: mapping }, mapping.columnName))), query === '' && view.filter && (_jsx(DatabaseTreeViewFilterRow, { editorState: editorState, view: view, filterMapping: view.filter })), query === '' && groupBy?.columns.map((_, index) => (_jsx(DatabaseTreeViewGroupByRow // Ordering is the only identity for groupBy columns. // eslint-disable-next-line react/no-array-index-key , { editorState: editorState, view: view, index: index }, `groupBy:${index}`)))] }))] })); }); /** * Renders one Schema header plus (when expanded) its Table and View children. * Tables come before views within each schema — both kinds are siblings in * the tree, distinguished only by icon and the column-row content. * * Schemas don't have a "selected" state — they only toggle. */ const DatabaseTreeSchemaRow = observer((props) => { const { editorState, schema } = props; const query = editorState.searchText.trim().toLowerCase(); if (!schemaMatchesSearch(schema, query)) { return null; } const schemaId = getSchemaNodeId(schema.name); // Force-expand under search so matches are immediately visible. const isExpanded = query !== '' || editorState.expandedSchemaIds.has(schemaId); const totalChildren = schema.tables.length + schema.views.length; return (_jsxs("div", { className: "database-diagram__tree__schema", children: [_jsxs("button", { type: "button", className: "database-diagram__tree__schema__row", onClick: () => editorState.toggleSchemaExpanded(schemaId), children: [_jsx("span", { className: "database-diagram__tree__caret", children: isExpanded ? _jsx(ChevronDownIcon, {}) : _jsx(ChevronRightIcon, {}) }), _jsx(PURE_DatabaseSchemaIcon, {}), _jsx("span", { className: "database-diagram__tree__schema__name", children: schema.name }), _jsx("span", { className: "database-diagram__tree__schema__count", children: totalChildren }), _jsx(DatabaseAnnotationDisplay, { stereotypes: schema.stereotypes, taggedValues: schema.taggedValues, layout: "compact" })] }), isExpanded && (_jsxs("div", { className: "database-diagram__tree__schema__children", children: [schema.tables.map((table) => (_jsx(DatabaseTreeTableRow, { editorState: editorState, table: table }, `table:${table.name}`))), schema.views.map((view) => (_jsx(DatabaseTreeViewRow, { editorState: editorState, view: view }, `view:${view.name}`)))] }))] })); }); /** * Renders one Lakehouse `IncludeStore` reference: an `ExternalLinkIcon`- * suffixed button whose body shows the generator element path and a small * `storeType` badge to differentiate the kinds of generator (DataProduct vs * IngestDefinition vs whatever future types appear). Clicking opens the * generator element in a new tab — same affordance as the classic * "Included Stores" rows above. Kept as a named sub-component (not an * inline IIFE) so the JSX in `DatabaseSchemaTree` stays readable. */ const LakehouseStoreRow = observer((props) => { const { editorState, spec } = props; const path = spec.packageableElementPointer.valueForSerialization ?? spec.packageableElementPointer.value.path; return (_jsxs("button", { type: "button", className: "database-diagram__side-panel__included-store", title: `${path}\n(generator: ${spec.storeType})\n\n(click to open)`, onClick: () => editorState.editorStore.graphEditorMode.openElement(spec.packageableElementPointer.value), children: [_jsx(PURE_DataProductIcon, {}), _jsx("span", { className: "database-diagram__side-panel__included-store__path", children: path }), _jsx("span", { className: "database-diagram__side-panel__included-store__type", children: spec.storeType }), _jsx("span", { className: "database-diagram__side-panel__included-store__open", children: _jsx(ExternalLinkIcon, {}) })] })); }); /** * The full side-panel tree. Renders schemas → (tables, views) → columns plus * a flat "Joins" section at the bottom (joins are cross-relation * relationships and don't naturally fit in the tree hierarchy). * * State (selection, expansion) lives entirely in `DatabaseEditorState` so it * survives reprocessing and so other components (the canvas) can react to it. */ export const DatabaseSchemaTree = observer((props) => { const { editorState } = props; const { database } = editorState; const query = editorState.searchText.trim().toLowerCase(); // Pre-compute filtered counts so each section header surfaces "showing // N of M" feedback when the user is filtering. Cheap walks on the // already-typed query \u2014 we don't memoize because the per-render cost // is dominated by row JSX, not these filters. const visibleSchemas = query === '' ? database.schemas : database.schemas.filter((s) => schemaMatchesSearch(s, query)); const visibleJoins = query === '' ? database.joins : database.joins.filter((j) => matchesSearch(j.name, query)); const visibleFilters = query === '' ? database.filters : database.filters.filter((f) => matchesSearch(f.name, query)); return (_jsxs("div", { className: "database-diagram__side-panel", children: [_jsxs("div", { className: "database-diagram__side-panel__toolbar", children: [_jsxs("div", { className: "database-diagram__side-panel__search", children: [_jsx(SearchIcon, {}), _jsx("input", { type: "text", className: "database-diagram__side-panel__search__input", placeholder: "Filter schemas, tables, columns...", value: editorState.searchText, onChange: (e) => editorState.setSearchText(e.target.value), spellCheck: false }), editorState.searchText !== '' && (_jsx("button", { type: "button", className: "database-diagram__side-panel__search__clear", title: "Clear filter", onClick: () => editorState.setSearchText(''), children: _jsx(TimesIcon, {}) }))] }), _jsx("button", { type: "button", className: "database-diagram__side-panel__toolbar__btn", title: "Expand all schemas", onClick: () => editorState.expandAllSchemas(), children: _jsx(ExpandAllIcon, {}) }), _jsx("button", { type: "button", className: "database-diagram__side-panel__toolbar__btn", title: "Collapse all", onClick: () => editorState.collapseAll(), children: _jsx(CompressIcon, {}) })] }), (database.stereotypes.length > 0 || database.taggedValues.length > 0) && (_jsxs("div", { className: "database-diagram__side-panel__section", children: [_jsx("div", { className: "database-diagram__side-panel__section__header", children: "Annotations" }), _jsx("div", { className: "database-diagram__side-panel__annotations", children: _jsx(DatabaseAnnotationDisplay, { stereotypes: database.stereotypes, taggedValues: database.taggedValues, layout: "block" }) })] })), _jsxs("div", { className: "database-diagram__side-panel__section", children: [_jsxs("div", { className: "database-diagram__side-panel__section__header", children: ["Schemas", ' ', _jsx("span", { className: "database-diagram__side-panel__section__count", children: query === '' ? `(${database.schemas.length})` : `(${visibleSchemas.length}/${database.schemas.length})` })] }), database.schemas.length === 0 ? (_jsx(EmptySectionRow, { message: "No schemas defined." })) : visibleSchemas.length === 0 ? (_jsx(EmptySectionRow, { message: "No schemas match the current filter." })) : (database.schemas.map((schema) => (_jsx(DatabaseTreeSchemaRow, { editorState: editorState, schema: schema }, schema.name))))] }), _jsxs("div", { className: "database-diagram__side-panel__section", children: [_jsxs("div", { className: "database-diagram__side-panel__section__header", children: ["Joins", ' ', _jsx("span", { className: "database-diagram__side-panel__section__count", children: query === '' ? `(${database.joins.length})` : `(${visibleJoins.length}/${database.joins.length})` })] }), database.joins.length === 0 ? (_jsx(EmptySectionRow, { message: "No joins defined." })) : visibleJoins.length === 0 ? (_jsx(EmptySectionRow, { message: "No joins match the current filter." })) : (visibleJoins.map((join) => { const isSelected = editorState.selectedJoin === join; const formula = resolveJoinFormula(editorState.joinFormulas, join.name); const isLoading = editorState.isLoadingJoinFormulas && !editorState.joinFormulas.has(join.name); const selfJoin = isSelfJoin(join); const crossDb = !selfJoin && isCrossDatabaseJoin(join, database); return (_jsxs("button", { type: "button", className: clsx('database-diagram__side-panel__join', { 'database-diagram__side-panel__join--selected': isSelected, }), onClick: () => editorState.focusOnJoin(join), title: `${join.name}: ${formula}`, children: [_jsx("span", { className: "database-diagram__side-panel__join__icon", children: _jsx(PURE_DatabaseTableJoinIcon, {}) }), _jsxs("span", { className: "database-diagram__side-panel__join__body", children: [_jsxs("span", { className: "database-diagram__side-panel__join__name", children: [join.name, selfJoin && (_jsx("span", { className: "database-diagram__side-panel__join__marker database-diagram__side-panel__join__marker--self", title: "Self-join (source and target are the same relation)", children: "SELF" })), crossDb && (_jsx("span", { className: "database-diagram__side-panel__join__marker database-diagram__side-panel__join__marker--cross-db", title: "Cross-database join (one or both endpoints live in an included store)", children: "CROSS-DB" }))] }), _jsx("span", { className: "database-diagram__side-panel__join__formula", children: isLoading ? _jsx(FormulaSkeleton, {}) : formula })] }), !isLoading && (_jsx(CopyFormulaButton, { value: formula, label: "Copy join formula" }))] }, join.name)); }))] }), _jsxs("div", { className: "database-diagram__side-panel__section", children: [_jsxs("div", { className: "database-diagram__side-panel__section__header", children: ["Filters", ' ', _jsx("span", { className: "database-diagram__side-panel__section__count", children: query === '' ? `(${database.filters.length})` : `(${visibleFilters.length}/${database.filters.length})` })] }), database.filters.length === 0 ? (_jsx(EmptySectionRow, { message: "No filters defined." })) : visibleFilters.length === 0 ? (_jsx(EmptySectionRow, { message: "No filters match the current filter." })) : (visibleFilters.map((filter) => { const isSelected = editorState.selectedFilter === filter; const formula = resolveFilterFormula(editorState.filterFormulas, filter.name); const isLoading = editorState.isLoadingFilterFormulas && !editorState.filterFormulas.has(filter.name); return (_jsxs("button", { type: "button", className: clsx('database-diagram__side-panel__filter', { 'database-diagram__side-panel__filter--selected': isSelected, }), onClick: () => editorState.focusOnFilter(filter), title: `${filter.name}: ${formula}`, children: [_jsx("span", { className: "database-diagram__side-panel__filter__icon", children: _jsx(FilterIcon, {}) }), _jsxs("span", { className: "database-diagram__side-panel__filter__body", children: [_jsx("span", { className: "database-diagram__side-panel__filter__name", children: filter.name }), _jsx("span", { className: "database-diagram__side-panel__filter__formula", children: isLoading ? _jsx(FormulaSkeleton, {}) : formula })] }), !isLoading && (_jsx(CopyFormulaButton, { value: formula, label: "Copy filter formula" }))] }, filter.name)); }))] }), database.includes.length > 0 && (_jsxs("div", { className: "database-diagram__side-panel__section", children: [_jsxs("div", { className: "database-diagram__side-panel__section__header", children: ["Included Stores", ' ', _jsxs("span", { className: "database-diagram__side-panel__section__count", children: ["(", database.includes.length, ")"] })] }), database.includes.map((ref) => { const path = ref.valueForSerialization ?? ref.value.path; return (_jsxs("button", { type: "button", className: "database-diagram__side-panel__included-store", title: `${path}\n\n(click to open)`, onClick: () => editorState.editorStore.graphEditorMode.openElement(ref.value), children: [_jsx(PURE_DatabaseIcon, {}), _jsx("span", { className: "database-diagram__side-panel__included-store__path", children: path }), _jsx("span", { className: "database-diagram__side-panel__included-store__open", children: _jsx(ExternalLinkIcon, {}) })] }, path)); })] })), database.includedStoreSpecifications.length > 0 && (_jsxs("div", { className: "database-diagram__side-panel__section", children: [_jsxs("div", { className: "database-diagram__side-panel__section__header", children: ["Lakehouse Stores", ' ', _jsxs("span", { className: "database-diagram__side-panel__section__count", children: ["(", database.includedStoreSpecifications.length, ")"] })] }), database.includedStoreSpecifications.map((spec) => (_jsx(LakehouseStoreRow, { editorState: editorState, spec: spec }, `${spec.storeType}:${spec.packageableElementPointer.value.path}`)))] }))] })); }); //# sourceMappingURL=DatabaseSchemaTree.js.map