Skip to content

Commit e6e3c0e

Browse files
committed
feat(ingestion/grafana) add option to pass grafana user email as dashboard owner
Also remove the unique id of the dashboard as a owner.
1 parent 0a14181 commit e6e3c0e

File tree

7 files changed

+71
-54
lines changed

7 files changed

+71
-54
lines changed

metadata-ingestion/docs/sources/grafana/grafana_pre.md

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
### Concept Mapping
22

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

1313
### Compatibility
1414

@@ -108,3 +108,29 @@ source:
108108
- **SQL parsing**: Supports parsing of SQL queries for detailed lineage extraction
109109
110110
**Performance Note:** Lineage extraction can be disabled (`include_lineage: false`) to improve ingestion performance when lineage information is not needed.
111+
112+
#### Ownership Configuration
113+
114+
The Grafana source extracts dashboard ownership from the dashboard creator and assigns them as a Technical Owner.
115+
116+
```yaml
117+
source:
118+
type: grafana
119+
config:
120+
url: "https://grafana.company.com"
121+
service_account_token: "your_token"
122+
123+
# Ownership extraction (default: true)
124+
ingest_owners: true
125+
126+
# Email suffix removal (default: true)
127+
# When true: converts [email protected] to urn:li:corpuser:creator
128+
# When false: uses full email as urn:li:corpuser:[email protected]
129+
remove_email_suffix: true
130+
```
131+
132+
**Ownership Features:**
133+
134+
- **Technical Owner assignment**: Dashboard creators are automatically assigned as Technical Owners
135+
- **Email suffix control**: Configure how user email addresses are converted to DataHub user URNs via `remove_email_suffix`
136+
- **Disable ownership**: Set `ingest_owners: false` to skip ownership extraction entirely

metadata-ingestion/docs/sources/grafana/grafana_recipe.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ source:
1010
# SSL verification for HTTPS connections
1111
verify_ssl: true # optional, default is true
1212

13+
# Ownership configuration
14+
ingest_owners: true # optional, default is true - extract dashboard ownership
15+
remove_email_suffix: true # optional, default is true - converts [email protected] to urn:li:corpuser:creator
16+
1317
# Source type mapping for lineage
1418
connection_to_platform_map:
1519
postgres:

metadata-ingestion/src/datahub/ingestion/source/grafana/entity_mcp_builder.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ def build_dashboard_mcps(
167167
chart_urns: List[str],
168168
base_url: str,
169169
ingest_owners: bool,
170+
remove_email_suffix: bool,
170171
ingest_tags: bool,
171172
) -> Tuple[str, List[MetadataChangeProposalWrapper]]:
172173
"""Build dashboard metadata change proposals"""
@@ -206,7 +207,7 @@ def build_dashboard_mcps(
206207

207208
# Ownership aspect
208209
if dashboard.uid and ingest_owners:
209-
owner = _build_ownership(dashboard)
210+
owner = _build_ownership(dashboard, remove_email_suffix)
210211
if owner:
211212
mcps.append(
212213
MetadataChangeProposalWrapper(
@@ -378,24 +379,22 @@ def _build_dashboard_properties(dashboard: Dashboard) -> Dict[str, str]:
378379
return props
379380

380381

381-
def _build_ownership(dashboard: Dashboard) -> Optional[OwnershipClass]:
382+
def _build_ownership(
383+
dashboard: Dashboard, remove_email_suffix: bool
384+
) -> Optional[OwnershipClass]:
382385
"""Build ownership information"""
383386
owners = []
384387

385-
if dashboard.uid:
386-
owners.append(
387-
OwnerClass(
388-
owner=make_user_urn(dashboard.uid),
389-
type=OwnershipTypeClass.TECHNICAL_OWNER,
390-
)
391-
)
392-
393388
if dashboard.created_by:
394-
owner_id = dashboard.created_by.split("@")[0]
389+
if remove_email_suffix:
390+
owner_id = dashboard.created_by.split("@")[0]
391+
else:
392+
owner_id = dashboard.created_by
393+
395394
owners.append(
396395
OwnerClass(
397396
owner=make_user_urn(owner_id),
398-
type=OwnershipTypeClass.DATAOWNER,
397+
type=OwnershipTypeClass.TECHNICAL_OWNER,
399398
)
400399
)
401400

metadata-ingestion/src/datahub/ingestion/source/grafana/grafana_config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ class GrafanaSourceConfig(
8383
ingest_owners: bool = Field(
8484
default=True, description="Whether to ingest dashboard ownership information"
8585
)
86+
remove_email_suffix: bool = Field(
87+
True,
88+
description="Remove Grafana user email suffix for example, @acryl.io, "
89+
"when assigning ownership.",
90+
)
8691
skip_text_panels: bool = Field(
8792
default=False,
8893
description="Whether to skip text panels during ingestion. "

metadata-ingestion/src/datahub/ingestion/source/grafana/grafana_source.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,7 @@ class GrafanaSource(StatefulIngestionSourceBase):
109109
- Tags and Ownership:
110110
- Dashboard and chart tags
111111
- Ownership information derived from:
112-
- Dashboard creators
113-
- Technical owners based on dashboard UIDs
114-
- Custom ownership assignments
112+
- Dashboard creators (Technical owner)
115113
116114
The source supports the following capabilities:
117115
- Platform instance support for multi-Grafana deployments
@@ -392,6 +390,7 @@ def _process_dashboard(self, dashboard: Dashboard) -> Iterable[MetadataWorkUnit]
392390
chart_urns=chart_urns,
393391
base_url=self.config.url,
394392
ingest_owners=self.config.ingest_owners,
393+
remove_email_suffix=self.config.remove_email_suffix,
395394
ingest_tags=self.config.ingest_tags,
396395
)
397396

metadata-ingestion/tests/integration/grafana/grafana_mcps_golden.json

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2218,32 +2218,6 @@
22182218
"lastRunId": "no-run-id-provided"
22192219
}
22202220
},
2221-
{
2222-
"entityType": "dashboard",
2223-
"entityUrn": "urn:li:dashboard:(grafana,local-grafana.default)",
2224-
"changeType": "UPSERT",
2225-
"aspectName": "ownership",
2226-
"aspect": {
2227-
"json": {
2228-
"owners": [
2229-
{
2230-
"owner": "urn:li:corpuser:default",
2231-
"type": "TECHNICAL_OWNER"
2232-
}
2233-
],
2234-
"ownerTypes": {},
2235-
"lastModified": {
2236-
"time": 0,
2237-
"actor": "urn:li:corpuser:unknown"
2238-
}
2239-
}
2240-
},
2241-
"systemMetadata": {
2242-
"lastObserved": 1720785600000,
2243-
"runId": "grafana-test",
2244-
"lastRunId": "no-run-id-provided"
2245-
}
2246-
},
22472221
{
22482222
"entityType": "dashboard",
22492223
"entityUrn": "urn:li:dashboard:(grafana,local-grafana.default)",

metadata-ingestion/tests/unit/grafana/test_grafana_entity_mcp_builder.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,23 @@ def test_build_dashboard_properties(mock_dashboard):
7272

7373

7474
def test_build_ownership(mock_dashboard):
75-
ownership = _build_ownership(mock_dashboard)
75+
ownership = _build_ownership(mock_dashboard, True)
7676
assert isinstance(ownership, OwnershipClass)
77-
assert len(ownership.owners) == 2
77+
assert len(ownership.owners) == 1
7878
assert {owner.owner for owner in ownership.owners} == {
79-
"urn:li:corpuser:dash1",
8079
"urn:li:corpuser:test",
8180
}
8281

8382

83+
def test_build_ownership_full_email(mock_dashboard):
84+
ownership = _build_ownership(mock_dashboard, False)
85+
assert isinstance(ownership, OwnershipClass)
86+
assert len(ownership.owners) == 1
87+
assert {owner.owner for owner in ownership.owners} == {
88+
"urn:li:corpuser:[email protected]",
89+
}
90+
91+
8492
def test_build_chart_mcps(mock_panel, mock_dashboard):
8593
dataset_urn, chart_urn, chart_mcps = build_chart_mcps(
8694
panel=mock_panel,
@@ -147,6 +155,7 @@ def test_build_dashboard_mcps(mock_dashboard):
147155
chart_urns=chart_urns,
148156
base_url="http://grafana.test",
149157
ingest_owners=True,
158+
remove_email_suffix=True,
150159
ingest_tags=True,
151160
)
152161

@@ -193,7 +202,7 @@ def test_build_dashboard_mcps(mock_dashboard):
193202
)
194203
assert ownership_mcp is not None
195204
assert isinstance(ownership_mcp.aspect, OwnershipClass) # type safety
196-
assert len(ownership_mcp.aspect.owners) == 2
205+
assert len(ownership_mcp.aspect.owners) == 1
197206

198207

199208
def test_build_chart_mcps_no_tags(mock_panel, mock_dashboard):
@@ -222,6 +231,7 @@ def test_build_dashboard_mcps_no_owners(mock_dashboard):
222231
chart_urns=[],
223232
base_url="http://grafana.test",
224233
ingest_owners=True,
234+
remove_email_suffix=True,
225235
ingest_tags=True,
226236
)
227237

0 commit comments

Comments
 (0)