UNPKG

@finos/legend-application-studio

Version:
151 lines 11.7 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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 { Handle, Position } from '@xyflow/react'; import { EyeIcon, KeyIcon, PURE_DatabaseIcon, PURE_DatabaseTableIcon, clsx, } from '@finos/legend-art'; import { observer } from 'mobx-react-lite'; import { getColumnTypeLabel, getTableColumns, isPrimaryKey, resolveViewColumnFormula, resolveViewGroupByFormula, summarizeMilestoning, } from './DatabaseDiagramHelper.js'; import { DatabaseAnnotationDisplay } from './DatabaseAnnotationDisplay.js'; /** * View-only React Flow node representing a single relation (Table or View). * * Layout: header (icon + relation name + schema badge + optional VIEW tag) + * a list of column rows. Each row is a fixed-height grid: * - Tables: [PK key icon | column name | column type] * - Views: [bullet | column name | formula placeholder] * * Two invisible Handles (left/right) let React Flow route edges into either * side of the box without committing to a specific column anchor. */ export const DatabaseTableNode = observer((props) => { const { relation, kind, schemaName, isSelected, isJoinEndpoint, fkColumns, selectedColumn, selectedViewColumnName, viewColumnFormulas, viewGroupByFormulas, } = props.data; const isViewKind = kind === 'view'; return (_jsxs("div", { className: clsx('database-diagram__table-node', { 'database-diagram__table-node--selected': isSelected, 'database-diagram__table-node--join-endpoint': isJoinEndpoint, 'database-diagram__table-node--view': isViewKind, }), children: [_jsx(Handle, { type: "target", position: Position.Left, className: "database-diagram__table-node__handle", isConnectable: false }), _jsx(Handle, { type: "source", position: Position.Right, className: "database-diagram__table-node__handle", isConnectable: false }), _jsxs("div", { className: "database-diagram__table-node__header", children: [_jsx("div", { className: "database-diagram__table-node__header__icon", children: isViewKind ? _jsx(EyeIcon, {}) : _jsx(PURE_DatabaseTableIcon, {}) }), _jsx("div", { className: "database-diagram__table-node__header__name", children: relation.name }), isViewKind && (_jsx("div", { className: "database-diagram__table-node__header__kind-tag", children: "VIEW" })), isViewKind && renderViewMetadataTags(relation), !isViewKind && renderMilestoningTags(relation), _jsx("div", { className: "database-diagram__table-node__header__schema", children: schemaName }), _jsx(DatabaseAnnotationDisplay, { stereotypes: relation.stereotypes, taggedValues: relation.taggedValues, layout: "compact" })] }), _jsx("div", { className: "database-diagram__table-node__columns", children: isViewKind ? renderViewColumns(relation, viewColumnFormulas, selectedViewColumnName) : renderTableColumns(relation, fkColumns, selectedColumn) }), isViewKind && renderViewGroupBySection(relation, viewGroupByFormulas)] })); }); // Function declarations (not const arrow) so they're hoisted and the React // component above can call them without triggering `no-use-before-define`. function renderTableColumns(table, fkColumns, selectedColumn) { return getTableColumns(table).map((column) => { const isPk = isPrimaryKey(table, column.name); const isFk = fkColumns.has(column); const isFocused = selectedColumn === column; return (_jsxs("div", { className: clsx('database-diagram__table-node__column', { 'database-diagram__table-node__column--pk': isPk, 'database-diagram__table-node__column--fk': isFk, 'database-diagram__table-node__column--nullable': column.nullable === true, 'database-diagram__table-node__column--focused': isFocused, }), title: column.nullable ? `${column.name} (nullable)` : column.name, children: [_jsx("div", { className: "database-diagram__table-node__column__key", children: isPk ? _jsx(KeyIcon, {}) : null }), _jsx("div", { className: "database-diagram__table-node__column__name", children: column.name }), _jsx("div", { className: "database-diagram__table-node__column__type", children: getColumnTypeLabel(column) })] }, column.name)); }); } /** * View columns are defined by `view.columnMappings` rather than `Column[]`. * Each mapping carries the column name and a relational operation that * computes the value. We render the Pure code returned by the engine (cached * in `DatabaseEditorState.viewColumnFormulas`); while it's still loading we * fall back to a placeholder so the layout stays stable. * * The `title` attribute also surfaces the formula for tooltip-on-hover, since * complex formulas may not fit in the 1-line display. */ function renderViewColumns(view, formulas, selectedViewColumnName) { return view.columnMappings.map((mapping) => { const isPk = isPrimaryKey(view, mapping.columnName); const isFocused = selectedViewColumnName === mapping.columnName; const formula = resolveViewColumnFormula(formulas, view.schema.name, view.name, mapping.columnName); return (_jsxs("div", { className: clsx('database-diagram__table-node__column', { 'database-diagram__table-node__column--pk': isPk, 'database-diagram__table-node__column--view': true, 'database-diagram__table-node__column--focused': isFocused, }), title: `${mapping.columnName}: ${formula}`, children: [_jsx("div", { className: "database-diagram__table-node__column__key", children: isPk ? _jsx(KeyIcon, {}) : null }), _jsx("div", { className: "database-diagram__table-node__column__name", children: mapping.columnName }), _jsx("div", { className: "database-diagram__table-node__column__type", children: formula })] }, mapping.columnName)); }); } /** * Render the secondary view-metadata tags shown next to the primary "VIEW" * tag in the canvas node header. These mirror the tags rendered in the * schema tree's view row (see `DatabaseTreeViewRow`) so the two surfaces * stay in sync. * * Returns `null` when the view has none of these features set, so the * header stays compact for "plain" views. */ function renderViewMetadataTags(view) { const groupByCount = view.groupBy?.columns.length ?? 0; const hasDistinct = view.distinct === true; const hasFilter = Boolean(view.filter); const hasGroupBy = groupByCount > 0; if (!hasDistinct && !hasFilter && !hasGroupBy) { return null; } return (_jsxs(_Fragment, { children: [hasDistinct && (_jsx("div", { className: "database-diagram__table-node__header__kind-tag database-diagram__table-node__header__kind-tag--distinct", title: "View applies DISTINCT", children: "DISTINCT" })), hasFilter && view.filter && (_jsx("div", { className: "database-diagram__table-node__header__kind-tag database-diagram__table-node__header__kind-tag--filtered", title: `Filtered by ${view.filter.filter.ownerReference.valueForSerialization ?? ''}.${view.filter.filterName}`, children: "FILTERED" })), hasGroupBy && (_jsx("div", { className: "database-diagram__table-node__header__kind-tag database-diagram__table-node__header__kind-tag--grouped", title: `GROUP BY ${groupByCount} expression${groupByCount === 1 ? '' : 's'}`, children: `GROUP BY (${groupByCount})` }))] })); } /** * Compact header tags surfacing a table's `milestoning` configuration. * One tag per Milestoning entry (a table can declare both business and * processing milestoning). The tag color follows the kind classifier from * `summarizeMilestoning` so business and processing read distinctly. * * Returns `null` for non-milestoned tables to keep the header compact \u2014 * the vast majority of tables are not milestoned. */ function renderMilestoningTags(table) { if (table.milestoning.length === 0) { return null; } return (_jsx(_Fragment, { children: table.milestoning.map((milestoning) => { const summary = summarizeMilestoning(milestoning); return (_jsx("div", { className: clsx('database-diagram__table-node__header__kind-tag', `database-diagram__table-node__header__kind-tag--milestoned-${summary.kind}`), title: summary.description, children: summary.label }, summary.label)); }) })); } /** * Footer rendered under the column rows when a view declares `groupBy`. * Lists each grouping expression as one row of Pure code (lazy-resolved * from `viewGroupByFormulas` — falls back to a placeholder while the * batched engine call is in flight). * * Returns `null` for views with no groupBy and for table-kind nodes, so the * footer doesn't add visual weight to nodes that don't need it. */ function renderViewGroupBySection(view, formulas) { const columns = view.groupBy?.columns ?? []; if (columns.length === 0) { return null; } return (_jsxs("div", { className: "database-diagram__table-node__group-by", title: `GROUP BY (${columns.length})`, children: [_jsx("div", { className: "database-diagram__table-node__group-by__header", children: "GROUP BY" }), columns.map((_, index) => { const formula = resolveViewGroupByFormula(formulas, view.schema.name, view.name, index); return (_jsxs("div", { className: "database-diagram__table-node__group-by__row", title: formula, children: [_jsx("span", { className: "database-diagram__table-node__group-by__index", children: `[${index}]` }), _jsx("span", { className: "database-diagram__table-node__group-by__formula", children: formula })] }, formula)); })] })); } /** * Compact stub rendered when a join references a relation in another * database. Shows the schema/name plus the owning database path, with a * dashed border to distinguish it from real in-database table nodes. Two * invisible Handles let React Flow attach edges to either side. Not * selectable — the canvas swallows clicks on stubs. */ export const DatabaseForeignRelationStubNode = observer((props) => { const { schemaName, relationName, ownerPath, isJoinEndpoint } = props.data; return (_jsxs("div", { className: clsx('database-diagram__table-node', 'database-diagram__table-node--foreign-stub', { 'database-diagram__table-node--join-endpoint': isJoinEndpoint, }), title: `${schemaName}.${relationName}\n(in database: ${ownerPath})`, children: [_jsx(Handle, { type: "target", position: Position.Left, className: "database-diagram__table-node__handle", isConnectable: false }), _jsx(Handle, { type: "source", position: Position.Right, className: "database-diagram__table-node__handle", isConnectable: false }), _jsxs("div", { className: "database-diagram__table-node__header", children: [_jsx("div", { className: "database-diagram__table-node__header__icon", children: _jsx(PURE_DatabaseIcon, {}) }), _jsx("div", { className: "database-diagram__table-node__header__name", children: `${schemaName}.${relationName}` })] }), _jsx("div", { className: "database-diagram__table-node__foreign-stub__owner", children: ownerPath })] })); }); //# sourceMappingURL=DatabaseTableNode.js.map