Skip to content

Commit 9e89e56

Browse files
authored
fix: resolve four DataHub 1.4.x write method bugs (#108)
* fix: resolve four DataHub 1.4.x write method bugs (#107) - Rename propertyUrn to structuredPropertyUrn in UpsertStructuredProperties GraphQL mutation input (REST field name was used in GraphQL context) - Change UpdateIncidentStatusInput to IncidentStatusInput in ResolveIncident mutation (1.4.x schema change) - Route tag/glossaryTerm/description writes for domain, glossaryTerm, and glossaryNode entities through GraphQL mutations instead of REST, because the REST API does not register globalTags, glossaryTerms, or editable description aspects for these entity types * refactor: eliminate redundant LookupDescriptionAspect call in UpdateDescription Resolve aspectInfo once in the public method and pass it to updateDescriptionREST, avoiding a duplicate map lookup. * fix: remove unnecessary leading newline flagged by whitespace linter
1 parent 994577a commit 9e89e56

File tree

6 files changed

+638
-25
lines changed

6 files changed

+638
-25
lines changed

pkg/client/incidents.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ mutation raiseIncident($input: RaiseIncidentInput!) {
6868

6969
// UpdateIncidentStatusMutation resolves or reactivates an incident.
7070
UpdateIncidentStatusMutation = `
71-
mutation updateIncidentStatus($urn: String!, $input: UpdateIncidentStatusInput!) {
71+
mutation updateIncidentStatus($urn: String!, $input: IncidentStatusInput!) {
7272
updateIncidentStatus(urn: $urn, input: $input)
7373
}
7474
`

pkg/client/structured_properties.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,8 @@ func (c *Client) UpsertStructuredProperties(ctx context.Context, urn string, pro
233233
propInputs := make([]map[string]any, 0, len(properties))
234234
for _, p := range properties {
235235
propInputs = append(propInputs, map[string]any{
236-
"propertyUrn": p.PropertyURN,
237-
"values": p.Values,
236+
"structuredPropertyUrn": p.PropertyURN,
237+
"values": p.Values,
238238
})
239239
}
240240

pkg/client/write.go

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,19 +137,33 @@ func (a *descriptionAspect) setDescription(fieldName, value string) error {
137137
return nil
138138
}
139139

140-
// UpdateDescription sets the editable description for any entity using read-modify-write.
141-
// Resolves the correct aspect name and field name based on the entity type in the URN.
140+
// UpdateDescription sets the editable description for any entity.
141+
// Uses GraphQL for domain, glossaryTerm, and glossaryNode entities because the REST
142+
// API does not support their description aspects. Uses REST read-modify-write for all
143+
// other entity types.
142144
func (c *Client) UpdateDescription(ctx context.Context, urn, description string) error {
143145
entityType, err := entityTypeFromURN(urn)
144146
if err != nil {
145147
return fmt.Errorf("UpdateDescription: %w", err)
146148
}
147149

150+
// Validate the entity type is supported before choosing a path.
148151
aspectInfo, err := LookupDescriptionAspect(entityType)
149152
if err != nil {
150153
return fmt.Errorf("UpdateDescription: %w", err)
151154
}
152155

156+
// Domain, glossaryTerm, and glossaryNode do not support REST aspect writes
157+
// for descriptions. Use the GraphQL updateDescription mutation instead.
158+
if graphQLWriteTypes[entityType] {
159+
return c.updateDescriptionGraphQL(ctx, urn, description)
160+
}
161+
162+
return c.updateDescriptionREST(ctx, urn, description, entityType, aspectInfo)
163+
}
164+
165+
// updateDescriptionREST updates a description via REST read-modify-write.
166+
func (c *Client) updateDescriptionREST(ctx context.Context, urn, description, entityType string, aspectInfo DescriptionAspectInfo) error {
153167
props, err := c.readEditableProperties(ctx, entityType, urn, aspectInfo.AspectName)
154168
if err != nil {
155169
return fmt.Errorf("UpdateDescription: %w", err)
@@ -226,7 +240,10 @@ type tagAssociation struct {
226240
Tag string `json:"tag"`
227241
}
228242

229-
// AddTag adds a tag to an entity using read-modify-write on the globalTags aspect.
243+
// AddTag adds a tag to an entity.
244+
// Uses GraphQL for domain, glossaryTerm, and glossaryNode entities because
245+
// the REST API does not register globalTags as an aspect for these types.
246+
// Uses REST read-modify-write for all other entity types.
230247
func (c *Client) AddTag(ctx context.Context, urn, tagURN string) error {
231248
entityType, err := entityTypeFromURN(urn)
232249
if err != nil {
@@ -237,6 +254,10 @@ func (c *Client) AddTag(ctx context.Context, urn, tagURN string) error {
237254
return fmt.Errorf("AddTag: %w: %s", ErrUnsupportedTagEntity, entityType)
238255
}
239256

257+
if graphQLWriteTypes[entityType] {
258+
return c.addTagGraphQL(ctx, urn, tagURN)
259+
}
260+
240261
// Read current tags
241262
tags, err := c.readGlobalTags(ctx, entityType, urn)
242263
if err != nil {
@@ -261,7 +282,10 @@ func (c *Client) AddTag(ctx context.Context, urn, tagURN string) error {
261282
})
262283
}
263284

264-
// RemoveTag removes a tag from an entity using read-modify-write on the globalTags aspect.
285+
// RemoveTag removes a tag from an entity.
286+
// Uses GraphQL for domain, glossaryTerm, and glossaryNode entities because
287+
// the REST API does not register globalTags as an aspect for these types.
288+
// Uses REST read-modify-write for all other entity types.
265289
func (c *Client) RemoveTag(ctx context.Context, urn, tagURN string) error {
266290
entityType, err := entityTypeFromURN(urn)
267291
if err != nil {
@@ -272,6 +296,10 @@ func (c *Client) RemoveTag(ctx context.Context, urn, tagURN string) error {
272296
return fmt.Errorf("RemoveTag: %w: %s", ErrUnsupportedTagEntity, entityType)
273297
}
274298

299+
if graphQLWriteTypes[entityType] {
300+
return c.removeTagGraphQL(ctx, urn, tagURN)
301+
}
302+
275303
// Read current tags
276304
tags, err := c.readGlobalTags(ctx, entityType, urn)
277305
if err != nil {
@@ -326,7 +354,10 @@ type termAssociation struct {
326354
URN string `json:"urn"`
327355
}
328356

329-
// AddGlossaryTerm adds a glossary term to an entity using read-modify-write.
357+
// AddGlossaryTerm adds a glossary term to an entity.
358+
// Uses GraphQL for domain, glossaryTerm, and glossaryNode entities because
359+
// the REST API does not register glossaryTerms as an aspect for these types.
360+
// Uses REST read-modify-write for all other entity types.
330361
func (c *Client) AddGlossaryTerm(ctx context.Context, urn, termURN string) error {
331362
entityType, err := entityTypeFromURN(urn)
332363
if err != nil {
@@ -337,6 +368,10 @@ func (c *Client) AddGlossaryTerm(ctx context.Context, urn, termURN string) error
337368
return fmt.Errorf("AddGlossaryTerm: %w: %s", ErrUnsupportedGlossaryTermEntity, entityType)
338369
}
339370

371+
if graphQLWriteTypes[entityType] {
372+
return c.addTermGraphQL(ctx, urn, termURN)
373+
}
374+
340375
terms, err := c.readGlossaryTerms(ctx, entityType, urn)
341376
if err != nil {
342377
return fmt.Errorf("AddGlossaryTerm: %w", err)
@@ -360,7 +395,10 @@ func (c *Client) AddGlossaryTerm(ctx context.Context, urn, termURN string) error
360395
})
361396
}
362397

363-
// RemoveGlossaryTerm removes a glossary term from an entity using read-modify-write.
398+
// RemoveGlossaryTerm removes a glossary term from an entity.
399+
// Uses GraphQL for domain, glossaryTerm, and glossaryNode entities because
400+
// the REST API does not register glossaryTerms as an aspect for these types.
401+
// Uses REST read-modify-write for all other entity types.
364402
func (c *Client) RemoveGlossaryTerm(ctx context.Context, urn, termURN string) error {
365403
entityType, err := entityTypeFromURN(urn)
366404
if err != nil {
@@ -371,6 +409,10 @@ func (c *Client) RemoveGlossaryTerm(ctx context.Context, urn, termURN string) er
371409
return fmt.Errorf("RemoveGlossaryTerm: %w: %s", ErrUnsupportedGlossaryTermEntity, entityType)
372410
}
373411

412+
if graphQLWriteTypes[entityType] {
413+
return c.removeTermGraphQL(ctx, urn, termURN)
414+
}
415+
374416
terms, err := c.readGlossaryTerms(ctx, entityType, urn)
375417
if err != nil {
376418
return fmt.Errorf("RemoveGlossaryTerm: %w", err)

pkg/client/write_graphql.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"fmt"
6+
)
7+
8+
// GraphQL mutations for entity types where REST aspect writes are not supported.
9+
// Domain, glossaryTerm, and glossaryNode do not register globalTags, glossaryTerms,
10+
// or editable description aspects in the REST API. DataHub's GraphQL mutations
11+
// handle the entity-type routing internally.
12+
13+
const (
14+
// AddTagMutation adds a tag to any entity via GraphQL.
15+
// Signature: addTag(input: TagAssociationInput!): Boolean.
16+
AddTagMutation = `
17+
mutation addTag($input: TagAssociationInput!) {
18+
addTag(input: $input)
19+
}
20+
`
21+
22+
// RemoveTagMutation removes a tag from any entity via GraphQL.
23+
// Signature: removeTag(input: TagAssociationInput!): Boolean.
24+
RemoveTagMutation = `
25+
mutation removeTag($input: TagAssociationInput!) {
26+
removeTag(input: $input)
27+
}
28+
`
29+
30+
// AddTermMutation adds a glossary term to any entity via GraphQL.
31+
// Signature: addTerm(input: TermAssociationInput!): Boolean.
32+
AddTermMutation = `
33+
mutation addTerm($input: TermAssociationInput!) {
34+
addTerm(input: $input)
35+
}
36+
`
37+
38+
// RemoveTermMutation removes a glossary term from any entity via GraphQL.
39+
// Signature: removeTerm(input: TermAssociationInput!): Boolean.
40+
RemoveTermMutation = `
41+
mutation removeTerm($input: TermAssociationInput!) {
42+
removeTerm(input: $input)
43+
}
44+
`
45+
46+
// UpdateDescriptionMutation updates the description of any entity via GraphQL.
47+
// Signature: updateDescription(input: DescriptionUpdateInput!): Boolean.
48+
UpdateDescriptionMutation = `
49+
mutation updateDescription($input: DescriptionUpdateInput!) {
50+
updateDescription(input: $input)
51+
}
52+
`
53+
)
54+
55+
// graphQLWriteTypes lists entity types that require GraphQL mutations for write
56+
// operations because the REST API does not support their aspects (globalTags,
57+
// glossaryTerms, editable description).
58+
var graphQLWriteTypes = map[string]bool{
59+
entityTypeDomain: true,
60+
entityTypeGlossaryTerm: true,
61+
entityTypeGlossaryNode: true,
62+
}
63+
64+
// addTagGraphQL adds a tag to an entity using the GraphQL addTag mutation.
65+
func (c *Client) addTagGraphQL(ctx context.Context, urn, tagURN string) error {
66+
variables := map[string]any{
67+
"input": map[string]any{
68+
"tagUrn": tagURN,
69+
"resourceUrn": urn,
70+
},
71+
}
72+
73+
var response struct {
74+
AddTag bool `json:"addTag"`
75+
}
76+
77+
if err := c.Execute(ctx, AddTagMutation, variables, &response); err != nil {
78+
return fmt.Errorf("addTagGraphQL: %w", err)
79+
}
80+
81+
return nil
82+
}
83+
84+
// removeTagGraphQL removes a tag from an entity using the GraphQL removeTag mutation.
85+
func (c *Client) removeTagGraphQL(ctx context.Context, urn, tagURN string) error {
86+
variables := map[string]any{
87+
"input": map[string]any{
88+
"tagUrn": tagURN,
89+
"resourceUrn": urn,
90+
},
91+
}
92+
93+
var response struct {
94+
RemoveTag bool `json:"removeTag"`
95+
}
96+
97+
if err := c.Execute(ctx, RemoveTagMutation, variables, &response); err != nil {
98+
return fmt.Errorf("removeTagGraphQL: %w", err)
99+
}
100+
101+
return nil
102+
}
103+
104+
// addTermGraphQL adds a glossary term to an entity using the GraphQL addTerm mutation.
105+
func (c *Client) addTermGraphQL(ctx context.Context, urn, termURN string) error {
106+
variables := map[string]any{
107+
"input": map[string]any{
108+
"termUrn": termURN,
109+
"resourceUrn": urn,
110+
},
111+
}
112+
113+
var response struct {
114+
AddTerm bool `json:"addTerm"`
115+
}
116+
117+
if err := c.Execute(ctx, AddTermMutation, variables, &response); err != nil {
118+
return fmt.Errorf("addTermGraphQL: %w", err)
119+
}
120+
121+
return nil
122+
}
123+
124+
// removeTermGraphQL removes a glossary term from an entity using the GraphQL removeTerm mutation.
125+
func (c *Client) removeTermGraphQL(ctx context.Context, urn, termURN string) error {
126+
variables := map[string]any{
127+
"input": map[string]any{
128+
"termUrn": termURN,
129+
"resourceUrn": urn,
130+
},
131+
}
132+
133+
var response struct {
134+
RemoveTerm bool `json:"removeTerm"`
135+
}
136+
137+
if err := c.Execute(ctx, RemoveTermMutation, variables, &response); err != nil {
138+
return fmt.Errorf("removeTermGraphQL: %w", err)
139+
}
140+
141+
return nil
142+
}
143+
144+
// updateDescriptionGraphQL updates an entity's description using the GraphQL mutation.
145+
func (c *Client) updateDescriptionGraphQL(ctx context.Context, urn, description string) error {
146+
variables := map[string]any{
147+
"input": map[string]any{
148+
"description": description,
149+
"resourceUrn": urn,
150+
},
151+
}
152+
153+
var response struct {
154+
UpdateDescription bool `json:"updateDescription"`
155+
}
156+
157+
if err := c.Execute(ctx, UpdateDescriptionMutation, variables, &response); err != nil {
158+
return fmt.Errorf("updateDescriptionGraphQL: %w", err)
159+
}
160+
161+
return nil
162+
}

0 commit comments

Comments
 (0)