Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 33 additions & 9 deletions metadata-ingestion/docs/sources/grafana/grafana_pre.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
### Concept Mapping

| Source Concept | DataHub Concept | Notes |
| --------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------ |
| `"grafana"` | [Data Platform](../../metamodel/entities/dataPlatform.md) | |
| Grafana Folder | [Container](../../metamodel/entities/container.md) | Subtype `Folder` |
| Grafana Dashboard | [Container](../../metamodel/entities/container.md) | Subtype `Dashboard` |
| Grafana Panel/Visualization | [Chart](../../metamodel/entities/chart.md) | Various types mapped based on panel type (e.g., graph → LINE, pie → PIE) |
| Grafana Data Source | [Dataset](../../metamodel/entities/dataset.md) | Created for each panel's data source |
| Dashboard Owner | [Corp User](../../metamodel/entities/corpuser.md) | Derived from dashboard UID and creator |
| Dashboard Tags | [Tag](../../metamodel/entities/tag.md) | Supports both simple tags and key:value tags |
| Source Concept | DataHub Concept | Notes |
| --------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `"grafana"` | [Data Platform](../../metamodel/entities/dataPlatform.md) | |
| Grafana Folder | [Container](../../metamodel/entities/container.md) | Subtype `Folder` |
| Grafana Dashboard | [Container](../../metamodel/entities/container.md) | Subtype `Dashboard` |
| Grafana Panel/Visualization | [Chart](../../metamodel/entities/chart.md) | Various types mapped based on panel type (e.g., graph → LINE, pie → PIE) |
| Grafana Data Source | [Dataset](../../metamodel/entities/dataset.md) | Created for each panel's data source |
| Dashboard Owner | [Corp User](../../metamodel/entities/corpuser.md) | Dashboard creator assigned as TECHNICAL_OWNER; email suffix removal configurable via `remove_email_suffix` |
| Dashboard Tags | [Tag](../../metamodel/entities/tag.md) | Supports both simple tags and key:value tags |

### Compatibility

Expand Down Expand Up @@ -108,3 +108,27 @@ source:
- **SQL parsing**: Supports parsing of SQL queries for detailed lineage extraction

**Performance Note:** Lineage extraction can be disabled (`include_lineage: false`) to improve ingestion performance when lineage information is not needed.

#### Ownership Configuration

The Grafana source extracts dashboard ownership from the dashboard creator and assigns them as a Technical Owner.

```yaml
source:
type: grafana
config:
url: "https://grafana.company.com"
service_account_token: "your_token"

# Ownership extraction (default: true)
ingest_owners: true

# Email suffix removal like @acryl.io (default: true)
remove_email_suffix: true
```

**Ownership Features:**

- **Technical Owner assignment**: Dashboard creators are automatically assigned as Technical Owners
- **Email suffix control**: Configure how user email addresses are converted to DataHub user URNs via `remove_email_suffix`
- **Disable ownership**: Set `ingest_owners: false` to skip ownership extraction entirely
4 changes: 4 additions & 0 deletions metadata-ingestion/docs/sources/grafana/grafana_recipe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ source:
# SSL verification for HTTPS connections
verify_ssl: true # optional, default is true

# Ownership configuration
ingest_owners: true # optional, default is true - extract dashboard ownership
remove_email_suffix: true # optional, default is true - remove email suffix like @acryl.io

# Source type mapping for lineage
connection_to_platform_map:
postgres:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ def build_dashboard_mcps(
chart_urns: List[str],
base_url: str,
ingest_owners: bool,
remove_email_suffix: bool,
ingest_tags: bool,
) -> Tuple[str, List[MetadataChangeProposalWrapper]]:
"""Build dashboard metadata change proposals"""
Expand Down Expand Up @@ -206,7 +207,7 @@ def build_dashboard_mcps(

# Ownership aspect
if dashboard.uid and ingest_owners:
owner = _build_ownership(dashboard)
owner = _build_ownership(dashboard, remove_email_suffix)
if owner:
mcps.append(
MetadataChangeProposalWrapper(
Expand Down Expand Up @@ -378,24 +379,22 @@ def _build_dashboard_properties(dashboard: Dashboard) -> Dict[str, str]:
return props


def _build_ownership(dashboard: Dashboard) -> Optional[OwnershipClass]:
def _build_ownership(
dashboard: Dashboard, remove_email_suffix: bool
) -> Optional[OwnershipClass]:
"""Build ownership information"""
owners = []

if dashboard.uid:
owners.append(
OwnerClass(
owner=make_user_urn(dashboard.uid),
type=OwnershipTypeClass.TECHNICAL_OWNER,
)
)

if dashboard.created_by:
owner_id = dashboard.created_by.split("@")[0]
if remove_email_suffix:
owner_id = dashboard.created_by.split("@")[0]
else:
owner_id = dashboard.created_by

owners.append(
OwnerClass(
owner=make_user_urn(owner_id),
type=OwnershipTypeClass.DATAOWNER,
type=OwnershipTypeClass.TECHNICAL_OWNER,
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ class GrafanaSourceConfig(
ingest_owners: bool = Field(
default=True, description="Whether to ingest dashboard ownership information"
)
remove_email_suffix: bool = Field(
True,
description="Remove Grafana user email suffix for example, @acryl.io, "
"when assigning ownership.",
)
skip_text_panels: bool = Field(
default=False,
description="Whether to skip text panels during ingestion. "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,7 @@ class GrafanaSource(StatefulIngestionSourceBase):
- Tags and Ownership:
- Dashboard and chart tags
- Ownership information derived from:
- Dashboard creators
- Technical owners based on dashboard UIDs
- Custom ownership assignments
- Dashboard creators (Technical owner)

The source supports the following capabilities:
- Platform instance support for multi-Grafana deployments
Expand Down Expand Up @@ -392,6 +390,7 @@ def _process_dashboard(self, dashboard: Dashboard) -> Iterable[MetadataWorkUnit]
chart_urns=chart_urns,
base_url=self.config.url,
ingest_owners=self.config.ingest_owners,
remove_email_suffix=self.config.remove_email_suffix,
ingest_tags=self.config.ingest_tags,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2218,32 +2218,6 @@
"lastRunId": "no-run-id-provided"
}
},
{
"entityType": "dashboard",
"entityUrn": "urn:li:dashboard:(grafana,local-grafana.default)",
"changeType": "UPSERT",
"aspectName": "ownership",
"aspect": {
"json": {
"owners": [
{
"owner": "urn:li:corpuser:default",
"type": "TECHNICAL_OWNER"
}
],
"ownerTypes": {},
"lastModified": {
"time": 0,
"actor": "urn:li:corpuser:unknown"
}
}
},
"systemMetadata": {
"lastObserved": 1720785600000,
"runId": "grafana-test",
"lastRunId": "no-run-id-provided"
}
},
{
"entityType": "dashboard",
"entityUrn": "urn:li:dashboard:(grafana,local-grafana.default)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,23 @@ def test_build_dashboard_properties(mock_dashboard):


def test_build_ownership(mock_dashboard):
ownership = _build_ownership(mock_dashboard)
ownership = _build_ownership(mock_dashboard, True)
assert isinstance(ownership, OwnershipClass)
assert len(ownership.owners) == 2
assert len(ownership.owners) == 1
assert {owner.owner for owner in ownership.owners} == {
"urn:li:corpuser:dash1",
"urn:li:corpuser:test",
}


def test_build_ownership_full_email(mock_dashboard):
ownership = _build_ownership(mock_dashboard, False)
assert isinstance(ownership, OwnershipClass)
assert len(ownership.owners) == 1
assert {owner.owner for owner in ownership.owners} == {
"urn:li:corpuser:[email protected]",
}


def test_build_chart_mcps(mock_panel, mock_dashboard):
dataset_urn, chart_urn, chart_mcps = build_chart_mcps(
panel=mock_panel,
Expand Down Expand Up @@ -147,6 +155,7 @@ def test_build_dashboard_mcps(mock_dashboard):
chart_urns=chart_urns,
base_url="http://grafana.test",
ingest_owners=True,
remove_email_suffix=True,
ingest_tags=True,
)

Expand Down Expand Up @@ -193,7 +202,7 @@ def test_build_dashboard_mcps(mock_dashboard):
)
assert ownership_mcp is not None
assert isinstance(ownership_mcp.aspect, OwnershipClass) # type safety
assert len(ownership_mcp.aspect.owners) == 2
assert len(ownership_mcp.aspect.owners) == 1


def test_build_chart_mcps_no_tags(mock_panel, mock_dashboard):
Expand Down Expand Up @@ -222,6 +231,7 @@ def test_build_dashboard_mcps_no_owners(mock_dashboard):
chart_urns=[],
base_url="http://grafana.test",
ingest_owners=True,
remove_email_suffix=True,
ingest_tags=True,
)

Expand Down
Loading