Display OpenMetadata catalog enrichment in table profile overview

- API endpoint /api/catalog/profile/ enriches response with catalog metadata (tier, owners, tags, url)
- renderOverview() template function displays 'Data Catalog' section with tier, owners, tags, and catalog link
- Graceful degradation: section only shown if catalog enrichment available
This commit is contained in:
Petr 2026-03-12 14:28:02 +01:00
parent a7faf70cb3
commit 2d03a9b557
2 changed files with 70 additions and 1 deletions

View file

@ -673,7 +673,7 @@ def register_routes(app: Flask) -> None:
@app.route("/api/catalog/profile/<table_name>")
@login_required
def catalog_profile(table_name):
"""Return profiler data for a single table."""
"""Return profiler data for a single table with OpenMetadata catalog enrichment."""
profiles_path = _resolve_metadata_path("profiles.json")
try:
if not profiles_path.exists():
@ -686,6 +686,54 @@ def register_routes(app: Flask) -> None:
if not table_profile:
return jsonify({"error": f"No profile for table '{table_name}'"}), 404
# Enrich with OpenMetadata catalog data if available
if _catalog_enricher and _catalog_enricher.enabled:
try:
# Find table config from data_description.md
from src.config import TableConfig
from config.loader import load_instance_config
# Load data_description.md to find table config by name
instance_config = load_instance_config()
desc_path = Path(os.path.dirname(__file__)) / ".." / "docs" / "data_description.md"
if desc_path.exists():
with open(desc_path) as f:
content = f.read()
import re
yaml_match = re.search(r'```yaml\s*\n(.*?)```', content, re.DOTALL)
if yaml_match:
import yaml
yaml_data = yaml.safe_load(yaml_match.group(1))
if yaml_data and "tables" in yaml_data:
# Find table by name
for table_def in yaml_data["tables"]:
if table_def.get("name") == table_name:
table_config = TableConfig(
id=table_def.get("id", ""),
name=table_def.get("name", ""),
description=table_def.get("description", ""),
primary_key=table_def.get("primary_key", "id"),
sync_strategy=table_def.get("sync_strategy", "full_refresh"),
catalog_fqn=table_def.get("catalog_fqn"),
)
catalog_data = _catalog_enricher.enrich_table(table_config)
if catalog_data:
# Add catalog enrichment to profile
table_profile["catalog"] = {
"description": catalog_data.description,
"tags": catalog_data.tags,
"tier": catalog_data.tier,
"owners": catalog_data.owners,
"url": catalog_data.catalog_url,
}
# Override description with catalog version
if catalog_data.description:
table_profile["description"] = catalog_data.description
break
except Exception as e:
logger.warning(f"Error enriching profile for {table_name}: {e}")
return jsonify(table_profile)
except Exception as e:
logger.error(f"Error loading profile for {table_name}: {e}")

View file

@ -1723,6 +1723,26 @@ function renderOverview(data) {
const dr = data.date_range;
const dateRow = dr ? `<tr><td>Date Coverage</td><td>${dr.earliest} to ${dr.latest}</td></tr>` : '';
// Catalog enrichment section
let catalogHtml = '';
if (data.catalog) {
const cat = data.catalog;
const tagsHtml = (cat.tags || []).length > 0
? `<div style="margin-top:8px;"><span style="font-size:11px;color:var(--text-secondary);font-weight:500;">Tags:</span> ${cat.tags.map(t => `<span class="metric-badge" style="display:inline-block;margin:2px 4px 2px 0;">${t}</span>`).join('')}</div>`
: '';
const ownersHtml = (cat.owners || []).length > 0
? `<div style="margin-top:8px;"><span style="font-size:11px;color:var(--text-secondary);font-weight:500;">Owners:</span> ${cat.owners.join(', ')}</div>`
: '';
const tierHtml = cat.tier
? `<div style="margin-top:8px;"><span style="font-size:11px;color:var(--text-secondary);font-weight:500;">Tier:</span> <strong>${cat.tier}</strong></div>`
: '';
const urlHtml = cat.url
? `<div style="margin-top:8px;"><a href="${cat.url}" target="_blank" style="font-size:11px;color:var(--primary);text-decoration:none;">View in Data Catalog →</a></div>`
: '';
catalogHtml = `<div style="margin-top:16px;border-top:1px solid var(--border);padding-top:16px;"><div class="overview-title">Data Catalog</div>${tierHtml}${ownersHtml}${tagsHtml}${urlHtml}</div>`;
}
document.getElementById('sectionOverview').innerHTML = `
<div class="overview-grid">
<div>
@ -1743,6 +1763,7 @@ function renderOverview(data) {
<table class="overview-stats-table">${vtHtml}</table>
${data.description ? `<div style="margin-top:16px;"><div class="overview-title">Description</div><p style="font-size:13px;color:var(--text-secondary);">${data.description}</p></div>` : ''}
${(data.used_by_metrics || []).length > 0 ? `<div style="margin-top:16px;"><div class="overview-title">Used by Metrics</div><div>${data.used_by_metrics.map(m => m.file ? `<a class="metric-badge" style="cursor:pointer;text-decoration:none;" onclick="openMetricModal('${m.file}')">${m.name}</a>` : `<span class="metric-badge">${typeof m === 'string' ? m : m.name}</span>`).join('')}</div></div>` : ''}
${catalogHtml}
</div>
</div>`;
}