Skip to content

Commit 551c2f6

Browse files
committed
feat: Also fetch domains and data products
1 parent 0e2857a commit 551c2f6

2 files changed

Lines changed: 132 additions & 8 deletions

File tree

rust/resource-info-fetcher/src/backend/data_hub/graphql.rs

Lines changed: 107 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,42 @@ use std::collections::BTreeMap;
88

99
use serde::{Deserialize, Serialize};
1010

11-
use crate::backend::data_hub::{DataHubResourceInfoResponse, Group, Owners, Tag, Urn, User};
11+
use crate::backend::data_hub::{
12+
DataHubResourceInfoResponse, DataProduct, Domain, Group, Owners, Tag, Urn, User,
13+
};
1214

1315
/// A single query covering every entity kind we build URNs for. We use the generic `entity(urn:)`
1416
/// resolver plus per-type inline fragments, because a request can target a dataset (Trino table or
1517
/// Kafka topic), a container (Trino catalog or schema), a chart or a dashboard.
16-
const RESOURCE_INFO_QUERY: &str = r"
18+
const RESOURCE_INFO_QUERY: &str = r#"
1719
query ResourceInfo($urn: String!) {
1820
entity(urn: $urn) {
1921
...ResourceInfo
2022
}
2123
}
2224
fragment ResourceInfo on Entity {
23-
... on Dataset { tags { ...Tags } ownership { ...Owners } }
24-
... on Container { tags { ...Tags } ownership { ...Owners } }
25-
... on Chart { tags { ...Tags } ownership { ...Owners } }
26-
... on Dashboard { tags { ...Tags } ownership { ...Owners } }
25+
# DataHub has no direct "dataProduct" field on assets; membership is a graph edge that points from
26+
# the data product to its assets. From the asset's side it is therefore an INCOMING relationship.
27+
dataProducts: relationships(input: {types: ["DataProductContains"], direction: INCOMING, count: 10}) {
28+
relationships { ...DataProduct }
29+
}
30+
... on Dataset { tags { ...Tags } ownership { ...Owners } domain { ...Domain } }
31+
... on Container { tags { ...Tags } ownership { ...Owners } domain { ...Domain } }
32+
... on Chart { tags { ...Tags } ownership { ...Owners } domain { ...Domain } }
33+
... on Dashboard { tags { ...Tags } ownership { ...Owners } domain { ...Domain } }
2734
}
2835
fragment Tags on GlobalTags {
2936
tags { tag { urn properties { name } } }
3037
}
38+
fragment Domain on DomainAssociation {
39+
domain { urn properties { name description } }
40+
}
41+
fragment DataProduct on EntityRelationship {
42+
entity {
43+
urn
44+
... on DataProduct { properties { name description } }
45+
}
46+
}
3147
fragment Owners on Ownership {
3248
owners {
3349
owner {
@@ -39,7 +55,7 @@ fragment Owners on Ownership {
3955
type
4056
}
4157
}
42-
";
58+
"#;
4359

4460
/// Builds the request body for the [`RESOURCE_INFO_QUERY`], parameterized by the entity's URN.
4561
pub fn request(urn: &Urn) -> GraphQlRequest<'_> {
@@ -86,9 +102,12 @@ pub struct ResponseData {
86102
/// concrete entity type. [`Default`] yields the "no metadata" entity used when DataHub does not
87103
/// return an entity for a URN.
88104
#[derive(Debug, Default, Deserialize)]
105+
#[serde(rename_all = "camelCase")]
89106
pub struct Entity {
90107
tags: Option<GlobalTags>,
91108
ownership: Option<Ownership>,
109+
domain: Option<DomainAssociation>,
110+
data_products: Option<EntityRelationships>,
92111
}
93112

94113
#[derive(Debug, Deserialize)]
@@ -112,6 +131,47 @@ struct TagProperties {
112131
name: String,
113132
}
114133

134+
#[derive(Debug, Deserialize)]
135+
struct DomainAssociation {
136+
domain: DomainNode,
137+
}
138+
139+
#[derive(Debug, Deserialize)]
140+
struct DomainNode {
141+
urn: Urn,
142+
properties: Option<DomainProperties>,
143+
}
144+
145+
#[derive(Debug, Deserialize)]
146+
struct DomainProperties {
147+
name: String,
148+
description: Option<String>,
149+
}
150+
151+
/// The result of the `DataProductContains` relationship query. Each relationship's related entity is
152+
/// a data product the resource belongs to.
153+
#[derive(Debug, Deserialize)]
154+
struct EntityRelationships {
155+
relationships: Vec<EntityRelationship>,
156+
}
157+
158+
#[derive(Debug, Deserialize)]
159+
struct EntityRelationship {
160+
entity: DataProductNode,
161+
}
162+
163+
#[derive(Debug, Deserialize)]
164+
struct DataProductNode {
165+
urn: Urn,
166+
properties: Option<DataProductProperties>,
167+
}
168+
169+
#[derive(Debug, Deserialize)]
170+
struct DataProductProperties {
171+
name: String,
172+
description: Option<String>,
173+
}
174+
115175
#[derive(Debug, Deserialize)]
116176
struct Ownership {
117177
owners: Vec<Owner>,
@@ -189,6 +249,39 @@ impl Entity {
189249
})
190250
.collect();
191251

252+
let domain = self.domain.map(|association| {
253+
let DomainNode { urn, properties } = association.domain;
254+
// A domain without a properties aspect has no name; fall back to its URN.
255+
let (name, description) = match properties {
256+
Some(properties) => (properties.name, properties.description),
257+
None => (urn.0.clone(), None),
258+
};
259+
Domain {
260+
urn,
261+
name,
262+
description,
263+
}
264+
});
265+
266+
let data_products = self
267+
.data_products
268+
.into_iter()
269+
.flat_map(|relationships| relationships.relationships)
270+
.map(|relationship| {
271+
let DataProductNode { urn, properties } = relationship.entity;
272+
// A data product without a properties aspect has no name; fall back to its URN.
273+
let (name, description) = match properties {
274+
Some(properties) => (properties.name, properties.description),
275+
None => (urn.0.clone(), None),
276+
};
277+
DataProduct {
278+
urn,
279+
name,
280+
description,
281+
}
282+
})
283+
.collect();
284+
192285
let mut owners: BTreeMap<Urn, Owners> = BTreeMap::new();
193286
for owner in self
194287
.ownership
@@ -210,7 +303,13 @@ impl Entity {
210303
}
211304
}
212305

213-
DataHubResourceInfoResponse { urn, tags, owners }
306+
DataHubResourceInfoResponse {
307+
urn,
308+
tags,
309+
domain,
310+
data_products,
311+
owners,
312+
}
214313
}
215314
}
216315

rust/resource-info-fetcher/src/backend/data_hub/mod.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,15 @@ pub struct DataHubResourceInfoResponse {
9191
urn: Urn,
9292
tags: Vec<Tag>,
9393

94+
/// The domain the resource belongs to, e.g. `urn:li:domain:marketing`. A resource is assigned to
95+
/// at most one domain, so this is an [`Option`] rather than a list. [`None`] if the resource is
96+
/// not assigned to any domain.
97+
domain: Option<Domain>,
98+
99+
/// The data products the resource is part of, e.g. `urn:li:dataProduct:orders`. Modelled as a
100+
/// list because DataHub allows an asset to belong to more than one data product.
101+
data_products: Vec<DataProduct>,
102+
94103
/// Owners grouped by their ownership type URN, e.g.
95104
/// `urn:li:ownershipType:__system__technical_owner`. DataHub has no fixed set of ownership
96105
/// types — users can define custom ones — so this is an open map, not an enum.
@@ -114,6 +123,22 @@ pub struct Tag {
114123
name: String,
115124
}
116125

126+
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
127+
#[serde(rename_all = "camelCase")]
128+
pub struct Domain {
129+
urn: Urn,
130+
name: String,
131+
description: Option<String>,
132+
}
133+
134+
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
135+
#[serde(rename_all = "camelCase")]
136+
pub struct DataProduct {
137+
urn: Urn,
138+
name: String,
139+
description: Option<String>,
140+
}
141+
117142
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
118143
#[serde(rename_all = "camelCase")]
119144
pub struct User {

0 commit comments

Comments
 (0)