feat(client): glossary node create and hierarchy enumeration (#199) - #200
Merged
Conversation
The client could operate on individual glossary terms but never answer "what is the shape of the glossary?" — no root listing, no children of a node, no parent chain, and no way to create a node at all. A glossary is a tree by design, so a consumer could only ever see a flat slice of it. Adds: CreateGlossaryNode(ctx, name, definition, parentNode) GetRootGlossaryNodes(ctx, start, count) GetRootGlossaryTerms(ctx, start, count) GetGlossaryNodeChildren(ctx, nodeURN, start, count) GetGlossaryParentChain(ctx, urn) plus types.GlossaryNode and types.GlossaryChildren. No new MCP tools; existing term operations are unchanged. How children are enumerated was the open question in the issue, since RelationshipsInput.types is a free-form string list and the schema names no relationship for glossary parentage. Settled against a live DataHub v1.6.0 rather than guessed: children are the INCOMING side of the IsPartOf relationship on the parent node. Verified on a real tree — the edge returns both nodes and terms, pages on start/count, and its total matches the node's own childrenCount. This matches how DataHub's own UI fetches node children (datahub-web-react/src/graphql/glossaryNode.graphql). Two behaviours only the live instance revealed: - Children lag writes. They come from the graph index, which DataHub populates asynchronously, so a just-created child is not visible at once. Documented on the method; the integration test polls. - An unknown node returns an empty stub rather than an error, which is indistinguishable from a childless node, so the client selects exists and returns ErrNotFound. It is read through a pointer, so a DataHub version that omits the field is not misread as absent. GetGlossaryParentChain reads parentNodes on the entity itself and is immediately consistent. It returns the chain direct-parent first, matching DataHub's order, and fills each node's ParentNode from the next link so a caller can rebuild the branch without another round trip. Non-glossary URNs are rejected with ErrInvalidURN rather than silently returning an empty chain. No change to the support floor: createGlossaryNode, getRootGlossaryNodes, getRootGlossaryTerms, parentNodes, and childrenCount are all present in entity.graphql at the v1.3.0 tag, the documented minimum. Also repairs write_integration_test.go, which no longer compiled: getAspect, readGlobalTags, readGlossaryTerms, and readInstitutionalMemory had each gained an entityType parameter that the integration tests were never updated for. The full integration suite now builds and passes against v1.6.0. Verified: make verify clean (lint 0 issues, coverage 93.0%, client 95.1%); make test-integration green against a live DataHub v1.6.0, including the new TestIntegrationGlossaryHierarchy, which builds a glossary tree, enumerates it every way the issue asks for, and deletes it.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #200 +/- ##
==========================================
+ Coverage 92.16% 92.39% +0.22%
==========================================
Files 65 66 +1
Lines 4673 4812 +139
==========================================
+ Hits 4307 4446 +139
Misses 221 221
Partials 145 145 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #199.
The client could operate on individual glossary terms but never answer "what is the shape of the glossary?" — no root listing, no children of a node, no parent chain, and no way to create a node at all. A glossary is a tree by design, so a consumer could only ever see a flat, unordered slice of it. This adds node creation and full hierarchy enumeration.
API
New domain types:
No new MCP tools — this is client/library surface only, per the issue's scope and the project's lean-tool-count rule. Existing term operations (get, create with parent, delete, description update) are untouched.
The open question in the issue, settled empirically
The issue flagged one detail as genuinely open: how children are enumerated.
RelationshipsInput.typesis[String!]!— a free-form string list — so the schema files name no relationship for glossary parentage. It could not be answered by readingentity.graphql, and it was not guessed.A DataHub v1.6.0 quickstart was available locally, so I built a real glossary tree against it and probed the API directly. Children are the
INCOMINGside of theIsPartOfrelationship on the parent node:Against a tree of
root → {child node → grandchild term, child term}, that edge returned:It returns both kinds of children, pages correctly (
start: 1, count: 1→ the second child,totalstill 2), and itstotalmatches the node's ownchildrenCount— the cheap correctness check the issue suggested. This also matches how DataHub's own UI fetches node children (datahub-web-react/src/graphql/glossaryNode.graphql), so the relationship name is not an artifact of one deployment.The alternative the issue floated —
searchAcrossEntitiesfiltered onparentNode— was not needed.Two behaviours only the live instance revealed
Children lag writes. They are served from the graph index, which DataHub populates asynchronously through MCL consumers. The first integration run failed with
Total = 0immediately after creating the children; the same query passed seconds later. This is documented on the method, and the integration test polls rather than asserting once. It matters to downstream consumers building a portal browser — a node created by a user will not immediately show its child.An unknown node does not error. DataHub answers a lookup of a missing glossary node with an empty stub (
exists: false), which is otherwise indistinguishable from a node that simply has no children:{"missing": {"urn": "...:does-not-exist", "exists": false}, "real": {"urn": "...:mcpdh-test-root", "exists": true}}So the children query selects
existsand returnsErrNotFound. It is decoded through a*bool, so only a definitivefalsecounts as absent — a DataHub version that omits the field leaves the pointerniland the page still stands, rather than every node reading as missing.Parent chain
GetGlossaryParentChainuses the polymorphicentity(urn:)lookup with inline fragments on bothGlossaryTermandGlossaryNode, so one query serves either. It reads the entity itself and is immediately consistent — verified on a term created moments earlier, while its parent's children query was still empty. Prefer it when confirming a just-written parent.Ordering is DataHub's own: direct parent first, up to the root (verified — a grandchild term returned
[child node, root]). Each returned node'sParentNodeis filled from the next link in the chain, so a caller can rebuild the branch without another round trip.Non-glossary URNs are rejected with
ErrInvalidURNrather than silently returning an empty chain —entity(urn:)happily resolves a tag or dataset URN and just omitsparentNodes, which would otherwise look like "this term is at the root".Version compatibility
No change to the documented support floor. Every mechanism used here is present in
entity.graphqlat the v1.3.0 tag (the project's stated minimum), checked against the upstream repo rather than assumed:createGlossaryNodeentity.graphql:763getRootGlossaryTerms/getRootGlossaryNodes:180/:187GlossaryNode.childrenCount:2693ParentNodesResult:2718All four new queries are registered in
TestGraphQLQueriesMatchSchema, so they are validated against the pinned schema (v1.5.0.1) on everymake schema-check.Drive-by fix: the integration suite did not compile
pkg/client/write_integration_test.gowas stale —getAspect,readGlobalTags,readGlossaryTerms, andreadInstitutionalMemoryhad each gained anentityTypeparameter that the integration tests were never updated for, somake test-integrationfailed at build time and had presumably been failing for a while. Fixed (all affected call sites use dataset URNs, so"dataset"). The whole integration suite now builds and passes against v1.6.0.Testing
Unit —
pkg/client/glossary_test.go, GraphQL httptest mocks in the style ofwrite_entities_test.go: field mapping, the mixed node/term split, relationship input (IsPartOf/INCOMING/ paging), paging clamps,existshandling both ways, parent-chain ordering and parent linkage, and URN rejection. PlusTestCreateGlossaryNodealongside the existing term test.Integration —
TestIntegrationGlossaryHierarchy(new file,//go:build integration) builds the tree, enumerates it every way the issue asks for, cross-checkschildrenCount, and deletes everything on cleanup. This is the documented live verification the acceptance criteria call for, and it is runnable rather than a note in a doc:Gates —
make verifyclean: lint 0 issues, coverage 93.0% (client package 95.1%), schema-check, gosec, govulncheck, build-check.make patch-coverage: 182/182 changed lines = 100%.Not run —
make mutation. At--workers 1gremlins re-runs the whole suite per mutant and was tracking to hours on this repo; it is a repo-wide gate outsidemake verifyand was stopped, not skipped silently.Acceptance criteria
CreateGlossaryNode, live-verifiedtotalstart/count+total, clamped toMaxLimitTestIntegrationGlossaryHierarchyDownstream
Unblocks txn2/mcp-data-platform#1155 — its DataHub semantic adapter can currently only search glossary terms by name, so its portal cannot present the glossary as a browsable tree or create a node. Node create plus hierarchy enumeration is the missing piece for the portal glossary browser and editor (txn2/mcp-data-platform#1158).
Review notes
pkg/client/glossary.go.exists: falseasErrNotFound(a behaviour choice, not forced by the API) and the decision to fillParentNodein the parent chain by inference from the next link rather than a second fetch.