Repository navigation
docs(incidents): list the entity types that support incidents - #18685
cnpierrepapi wants to merge 9 commits into
Conversation
|
+1 on documenting this. I hit the same limitation independently on DataHub Core v1.5.0.6, while trying to raise an incident on an Adding the other error message for searchability, since it differs from the one in the description. A This PR quotes One note on why this matters beyond documentation, for the ML case specifically. The model is the thing that degraded, the thing an on-call engineer opens, and the thing whose health a team watches. Because it cannot carry an incident, the model page reads healthy while the model is measurably wrong, and the signal has to live one hop upstream on a dataset that is itself fine. Supporting ML entities as incident resources, even without full health roll-up, would close that gap. Happy to test a patch against a local v1.5.0.6 instance if that is useful. |
… criteria Two upstream contributions filed: - acryldata/mcp-server-datahub#200: update_description with a column_path that matches no field returns {"success": true} and writes nothing. Unclaimed across 39 open issues. Includes the repro, the 14-fields-versus-0 sibling table, the environment, and a concrete ask. - A comment on datahub-project/datahub#18685 with the second error message that DataHub Core v1.5.0.6 produces for the mlModel incident rejection, since that PR quotes a different string and a reader searching one will not find the other. The datahub-ml-lineage skill remains deliberately unfiled. PR #77 ships the same skill name and path, opened before mine. Declining it is the honest call and a better story than a near-duplicate. All three are now past tense with live URLs in README, SUBMISSION and DATAHUB_FINDINGS. Added a "For judges" table near the top of the README mapping each of the five judging criteria, plus the open-source bonus, to the specific section and artifact that answers it. The evidence was already in the repo but spread across a long document, and a judge reading their thirtieth submission should not have to hunt for it.
There was a problem hiding this comment.
2 issues found across 1 file
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docs/incidents/incidents.md">
<violation number="1" location="docs/incidents/incidents.md:103">
P3: The supported-type rule also applies to every entity supplied through `resourceUrns`, which the current GraphQL schema supports for multi-resource incidents. Describing only singular `resourceUrn` makes this new section incomplete for that API path; mentioning both inputs and that every destination must support `incidentsSummary` would keep the restriction accurate.</violation>
<violation number="2" location="docs/incidents/incidents.md:132">
P2: A model's training dataset is not always one lineage hop away. `mlModelTrainingData` creates a direct relationship, but the documented training-run representation is `Dataset → Training Run → ML Model` (two hops), and an instance may not have training lineage populated at all. This workaround should avoid promising a one-hop relationship and instead qualify it as a linked training dataset, directly or through a training run.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
|
Confirmed, and thanks for the second string. I only ever hit the first one, so I had no way to know there were two. They look like different checks. Yours comes back wrapped in On the ML point, that is exactly the case I hit. I have an agent that does root cause on model degradation by walking lineage, and the one thing it cannot do is leave the finding on the model. It writes to the training dataset instead. So the dataset page ends up carrying a warning about something that is not wrong with the dataset, and the model page stays clean while the model is the thing that is broken. On call opens the model page. I want to keep this PR as docs so it can land on its own. I will open a separate issue for supporting mlModel as an incident resource and tag you there. If a maintainer signals they would take it, that v1.5.0.6 test would be worth a lot, because the registry side looks small and the UI side probably is not. |
|
Issue opened for the ML case: #18999. This PR stays docs only. |
|
Correcting my earlier comment on this PR, because it was wrong and it should not I posted this as an mlModel rejection: I cannot reproduce it. On DataHub Core v1.5.0.6 a My best guess at what I actually hit is that I misattributed a failure whose Where that leaves the docs change in this PR: the supported list here comes from Full sweep and raw output in #18999. Separately, on the review note about the workaround being "one hop of lineage": Sorry for the noise. Better to correct it here than leave it for whoever searches |
AdrianMachado
left a comment
There was a problem hiding this comment.
See my comment on your issue. In addition to
- Gaps that need to be closed and
- Consistency check that needs to be implemented so we have an aligned definition of "supported"
this table should be generated from code, not handwritten
How to generate the incident support table
Pattern to copy
Ingestion already generates support tables and inlines them into docs:
- A Python generator writes a
.md.snippetunderdocs/generated/… - The hand-authored page pulls it in with
{{ inline … }} - Docs build expands that at assemble time
Example today:
- Generator:
generate_operation_capture_support_table()inmetadata-ingestion/scripts/docgen.py - Consumer:
docs/api/tutorials/operations.md
{{ inline /docs/generated/ingestion/operation_capture_support_table.md.snippet }}Do the same for incidents.
Source of truth for the table
Once the CI consistency check exists, generate from one layer only. Prefer:
entities in entity-registry.yml that declare incidentsSummary
That’s the product-facing “this asset carries incident health/summary” set. CI keeps PDL destinations and GraphQL incidents fields aligned with it.
Do not hand-copy the list into docs/incidents/incidents.md.
Minimal generator example
# metadata-ingestion/scripts/generate_incident_support_table.py
"""Generate docs/generated/incidents/supported_entities.md.snippet from the entity registry."""
from pathlib import Path
import yaml
REPO_ROOT = Path(__file__).resolve().parents[2]
REGISTRY = REPO_ROOT / "metadata-models/src/main/resources/entity-registry.yml"
OUT = REPO_ROOT / "docs/generated/incidents/supported_entities.md.snippet"
# Optional display names; fall back to raw entity type.
DISPLAY_NAMES = {
"dataset": "Dataset",
"dataJob": "Data Job",
"dataFlow": "Data Flow",
"chart": "Chart",
"dashboard": "Dashboard",
"service": "Service",
"aiAgent": "AI Agent",
"mlModel": "ML Model",
"mlFeature": "ML Feature",
"mlFeatureTable": "ML Feature Table",
"schemaField": "Schema Field",
}
def entities_with_incidents_summary() -> list[str]:
registry = yaml.safe_load(REGISTRY.read_text())
names = []
for entity in registry.get("entities", []):
aspects = entity.get("aspects") or []
if "incidentsSummary" in aspects:
names.append(entity["name"])
return sorted(names, key=str.casefold)
def main() -> None:
entities = entities_with_incidents_summary()
OUT.parent.mkdir(parents=True, exist_ok=True)
lines = [
"<!-- DO NOT EDIT. Generated by generate_incident_support_table.py -->",
"",
"| Entity | URN prefix |",
"| --- | --- |",
]
for name in entities:
display = DISPLAY_NAMES.get(name, name)
lines.append(f"| {display} | `urn:li:{name}:` |")
OUT.write_text("\n".join(lines) + "\n")
print(f"Wrote {OUT} ({len(entities)} entities)")
if __name__ == "__main__":
main()Example output with today’s registry:
<!-- DO NOT EDIT. Generated by generate_incident_support_table.py -->
| Entity | URN prefix |
| --- | --- |
| AI Agent | `urn:li:aiAgent:` |
| Chart | `urn:li:chart:` |
| Dashboard | `urn:li:dashboard:` |
| Data Flow | `urn:li:dataFlow:` |
| Data Job | `urn:li:dataJob:` |
| Dataset | `urn:li:dataset:` |
| Service | `urn:li:service:` |Wire it into the docs page
In docs/incidents/incidents.md:
### Supported entity types
An entity supports incidents when it declares the `incidentsSummary` aspect.
Write validation and GraphQL read support must stay aligned with that set
(enforced by CI).
{{ inline /docs/generated/incidents/supported_entities.md.snippet }}Hook into the docs build
Either:
- Call the script from
docgen.py/modelDocGen(same place asgenerate_operation_capture_support_table), or - Add a small Gradle dependency so
:docs-website/ doc generation runs it before assemble
Generated file should be gitignored or committed — follow whatever docs/generated/ingestion/*.snippet already does in this repo.
Let me know what you think
|
@AdrianMachado agreed on generating it. Two things before I push, and the first one is me correcting my own PR. The table is wrong, and generating it from
|
| entity | IncidentOn (write) | incidentsSummary | GraphQL incidents |
|---|---|---|---|
| dataset, chart, dashboard, dataFlow, dataJob | yes | yes | yes |
| service, aiAgent | yes | yes | no |
| mlModel, mlFeature, schemaField | yes | no | no |
| mlModelGroup, mlFeatureTable, dataProcessInstance | no | no | no |
Ten write destinations, seven summaries, five readable.
So on master, "Anything else is rejected and nothing is written" is false, and "mlModel ... do not support incidents" is false. Those writes are accepted and the caller gets an incident URN back. They just cannot be read. That is my error, it came from generalising one version's behaviour, and it should not ship regardless of what we decide about generation.
The error strings need redoing for the same reason. Of the two I quoted, the second is the wording Jonathan could not reproduce against an mlModel, and @Ahmedxsaad hit a third wording on v1.5.0.6 that is in neither. I would rather quote what is reproducible and say which version produced it than list strings from memory.
Which is the argument for generating more than one column. A table generated from incidentsSummary alone prints exactly the seven rows I hand wrote, and still tells a reader that mlModel is unsupported while their write returns an incident URN. Generating all three columns from the three sources describes what actually happens per layer, keeps the gaps visible instead of averaging them into a yes or no, and tightens itself as #18911 lands. Same reasoning as the CI guard in #19097, applied to the page a user reads first.
On where the generator lives
You pointed at generate_operation_capture_support_table in metadata-ingestion/scripts/docgen.py. That one builds its rows from the ingestion plugin scan and never reads the entity registry, so an incidents table there means running the connector doc scan to emit seven rows.
modeldocgen.py is the closer fit. It already takes --registry ../metadata-models/src/main/resources/entity-registry.yml, already builds entity_registry: Dict[str, EntityDefinition] with .aspects per entity, and already writes into docs/generated/metamodel. It runs as :metadata-ingestion:modelDocGen, which docs-website already depends on next to docGen. The summary column is nearly free there. The PDL and GraphQL columns need two small readers, which is the same parsing #19097 does on the test side.
Happy to put it in docgen.py instead if you would rather keep docs generation in one place. Your call, it is a small difference in the wiring and none in the output.
Two details, since they came up. docs/generated/ is gitignored, so the snippet is build output and not committed, same as the ingestion one. And the file is .md.snippet while mdPrettierCheck globs **/*.md, so the generated table is not prettier's problem either way.
The failing check
markdown_format_check is only cell padding in my hand written table. prettier wants the columns aligned. It disappears with the generated snippet, and I will clear it in the meantime so this is not sitting red.
Plan
- Rewrite the section around the three layers and what each one controls, with the ML statement corrected and the error strings cut back to what is reproducible.
- Three column table generated in
modeldocgen.py, inlined with{{ inline ... }}. - Keep it accurate as the gaps close, rather than needing an edit per merge.
If you would rather this PR carried only the correction and the generated table came with PR1 of #18911, that is faster to review and I am happy either way. I have said in #18999 that I will take PR1, so the two would land together.
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
0dfe7dd to
50c37a4
Compare
|
Pushed, four days later than it needed to be. I asked which file the generator should live in, said in the same breath that it was my call and the output was identical either way, and then waited on the answer anyway. Novel way to block yourself. Sorry for the delay. The generator went into Output is Three columns, backend only: the Re-derived against master before pushing: ten write destinations, seven summaries, five readable. The correction to my own text is in. The page no longer says |
|
Two cubic threads were still open on here, and I had assumed both were stale. They were not. Both were right, and 493d62f fixes them. The The second one is my error rather than an omission. I wrote that for Worth noting what the shape of this was. Both findings sat under a resolved-looking pile of bot comments and both were marked outdated, because the lines they were attached to had moved. Outdated is not the same as addressed, and I treated it as if it were. Nothing else changed. The generated table and the three-column scope are as described above, and CI was green before this push. |
The hand-maintained table was wrong. It described incidentsSummary as the write gate and said mlModel and mlFeature do not support incidents. Neither is true on master: the write gate is the IncidentOn relationship in IncidentInfo.pdl, and writes to those two are accepted and return an incident URN. They simply cannot be read back. Three files decide what happens, not one, and they do not cover the same set. IncidentOn lists ten entity types, seven of those carry incidentsSummary, and five are given an incidents field by incident.graphql. An entity that clears the first and fails the others is worse off than one that is cleanly rejected, because the call succeeds and nothing reports a failure. The table is now derived from those three files by modeldocgen.py and inlined at docs build time, so it cannot drift the way the hand-written version did. The section is rewritten around the three layers, and the quoted rejection message now names the version it came from rather than being reproduced from memory. The two frontend copies of the same list are deliberately not derived here. They live outside the metadata model, and keeping them from drifting is the subject of datahub-project#19097.
…dback claim The RaiseIncidentInput block on this page marked resourceUrn as required and omitted resourceUrns entirely, so a multi-resource incident could not be constructed from the shape shown. On master resourceUrn is optional and resourceUrns exists, with each required only when the other is absent. Saying nothing reads the incident back was also too strong. Incident implements Entity, so the URN returned by the mutation resolves through the generic entity(urn:) query. The gap for mlModel and mlFeature is on the asset side: neither can list its own incidents and neither renders them.
…ypes The generated table claimed "Readable in GraphQL" from the SDL alone. That is not what the SDL decides. A type can declare the incidents field and still have no resolver behind it, in which case the query is valid and the field comes back null, which reads as an entity with no incidents rather than an unsupported one. Split into two columns and derive the second from the entitiesWithIncidents list in GmsGraphQLEngine.java. Fix a false negative while here. The GraphQL type name was derived by capitalising the first letter of the entity name, so mlModel became MlModel and matched nothing. Every ml entity was reported as unsupported at the GraphQL gates even after datahub-project#19112, datahub-project#19132 and datahub-project#19367 closed them. The ml family spells the prefix ML, so handle that alongside the existing SchemaFieldEntity override. Prose updated for master. mlModel, mlFeature and mlFeatureTable are complete, so the worked example of an accepted write that goes nowhere is now service and aiAgent, which have no GraphQL type at all. schemaField closes the four backend gates but has no Incidents tab, so it is called out as API only. The list of copies living outside the metadata model is now three, adding the MCP list_incidents fragments from datahub-project#18341.
6248c1a to
7d35e64
Compare
|
Rebased onto master Three things went in beyond the rebase. The resolver gate is now derived. Column 3 claimed "Readable in GraphQL" and derived it from the SDL alone, which by your own gate 4 rule is not a claim the SDL can support. There are now two columns: The generator had a false negative and I would have shipped it. The GraphQL type name was derived by capitalising the first letter, so Current output, checked against master by hand:
Prose follows master. The ML paragraph is gone, since the write that looks like it worked and then goes nowhere is now One scope question rather than an assumption. Gate 7 is not derived. The MCP On verification: I cannot run |
The generator reads the entitiesWithIncidents list out of it now, so without declaring it Gradle treats modelDocGen as up to date when only that file changes and the generated table goes stale. That is the exact drift this table exists to prevent.
|
Follow-up on The Gradle wiring was wrong in my last push. On That batch is unstable across the repo at the moment rather than on this branch. Looking at the last 25 runs of I cannot re-run it, I only have pull on this repo, so it needs someone else's push or a maintainer. Not urgent from my side since it is unrelated to the diff. |
|
@AdrianMachado checking in on this one. With #19322 settled and schemaField staying API-only, the table this generates is already right, and there's nothing left for it to wait on. I re-checked it against master today. The four things the generator reads (the The only thing holding it is the changes-requested review from 10 Aug, which asked for the table to be generated, and that's what it does now. Could you take another look when you get a chance? |
| def _incident_write_gate() -> List[str]: | ||
| """Entity types accepted by the IncidentOn relationship, in declaration order.""" | ||
| match = re.search( | ||
| r'"entityTypes"\s*:\s*\[(.*?)\]', |
There was a problem hiding this comment.
This takes the first entityTypes in the file, so a second @relationship above IncidentOn silently replaces the table; anchor it on "IncidentOn".
| _INCIDENT_PDL.read_text(encoding="utf-8"), | ||
| re.DOTALL, | ||
| ) | ||
| return re.findall(r'"([A-Za-z]+)"', match.group(1)) if match else [] |
There was a problem hiding this comment.
A missing key yields an empty table with exit 0, and the docs then imply nothing supports incidents; raise ValueError instead, as docgen.py does.
| text = _strip_line_comments(_INCIDENT_ENGINE.read_text(encoding="utf-8")) | ||
| match = re.search(r"entitiesWithIncidents\s*=(.*?);", text, re.DOTALL) | ||
| if not match: | ||
| return set() |
There was a problem hiding this comment.
Renaming entitiesWithIncidents silently flips the whole resolver column to “No”; raise when the list isn’t found or is empty.
| return _INCIDENT_GRAPHQL_TYPE_OVERRIDES[entity] | ||
| if entity.startswith("ml") and len(entity) > 2 and entity[2].isupper(): | ||
| return "ML" + entity[2:] | ||
| return entity[0:1].upper() + entity[1:] |
There was a problem hiding this comment.
Check the derived name against types defined in the SDL or an explicit _NO_GRAPHQL_TYPE = {"service", "aiAgent"} so the next MlModel-style casing bug fails the build.
| return set(re.findall(r'"([A-Za-z][A-Za-z0-9_]*)"', match.group(1))) | ||
|
|
||
|
|
||
| def generate_incident_support_table(registry_file: str) -> str: |
There was a problem hiding this comment.
Add fixture tests for the six parsers in scripts/tests/test_modeldocgen.py, plus a smoke test asserting the dataset row is all “Yes”.
|
|
||
| {{ inline /docs/generated/incidents/entity-support.md.snippet }} | ||
|
|
||
| Any type absent from the table, including `mlModelGroup`, `mlModelDeployment` and |
There was a problem hiding this comment.
These hand-picked examples will contradict the generated table if any of them gains support; assert their absence in the generator or drop them.
| Entity type for urn: <urn> is not a valid destination for field path: /entities/* | ||
| ``` | ||
|
|
||
| `service` and `aiAgent` are the two types where a write can look like it worked and |
There was a problem hiding this comment.
This hard-codes what the table already shows and will go stale; the _NO_GRAPHQL_TYPE guard above would make the build catch it.
| rejection, because the call succeeds and returns an incident URN while the entity | ||
| it names never shows the incident. | ||
|
|
||
| The table below is generated at docs build time from those four files, so it cannot |
There was a problem hiding this comment.
Add a caveat that the table reflects this docs version and older servers such as v1.5.0.6 support fewer types, since the PR was prompted by mlModel on 1.5.0.6.
| The resources that the incident is associated with, for a multi-resource | ||
| incident. Must be present and not empty if resourceUrn is not defined. | ||
| """ | ||
| resourceUrns: [String!] |
There was a problem hiding this comment.
This block still omits id, startedAt, status, priority and assigneeUrns; list all 12 fields or mark the block abridged.
Review on this PR found several ways the generator could publish a wrong table and still exit 0. The write gate took the first entityTypes in the file, so a second @relationship above IncidentOn replaced the table. It now reads the block that names IncidentOn. A source that parses to nothing raises instead of printing a column of No. That covers all four sources, not just the two raised in review. Derived GraphQL type names are checked against the types defined in the SDL, so the next MlModel style casing bug fails the build. service and aiAgent are the declared exception, and that check runs both ways: if either gains a type, leaves IncidentOn or loses incidentsSummary, the build fails and points at the docs page that describes them. All *.graphql files are now modelDocGen inputs, since the check reads them. Adds fixture tests for each parser, a test that the table generator calls the guard, and a test against the real sources asserting the dataset row is all Yes.
…n caveat The input block omitted id, startedAt, status, priority and assigneeUrns. Drops the hand-picked examples of unsupported types, which would contradict the generated table if any of them gained support. Notes that the table follows the docs version and older servers support fewer types. Points the frontend drift pointer at datahub-project#19629, which superseded datahub-project#19097.
|
@kyungsoo-datahub thanks for going through this. All nine are in, as two new commits on top so you can see just the changes. I also rebased a copy onto current master and it applied clean with the same tests passing. Generator:
Docs:
I broke each fix on purpose to see if a test noticed. One didn't: nothing failed if the table generator never called the guard. There's a test for that now, and all 12 breaks get caught.
|
kyungsoo-datahub
left a comment
There was a problem hiding this comment.
LGTM with one comment. Please, address it before merging. Thank you.
| project.fileTree(dir: "../metadata-events/mxe-schemas/src/", include: "**/*.avsc"), | ||
| file(entityRegistry), | ||
| file(incidentPdl), | ||
| project.fileTree(dir: "../datahub-graphql-core/src/main/resources/", include: "*.graphql"), |
There was a problem hiding this comment.
documentation.yml doesn’t trigger on datahub-graphql-core/**, so please add it there or a schema-only PR can merge green and break this check for the next unrelated docs PR.
The
raiseIncidentdocs describeresourceUrnas "dataset, dashboard, chart,dataFlow, etc". I read the "etc" as open-ended and tried to raise an incident on
an
mlModel, which is rejected at the entity layer:The set is not open-ended. It is whichever entities declare the
incidentsSummaryaspect, and frommetadata-models/src/main/resources/entity-registry.ymlon master that is exactly:
dataset,dataJob,dataFlow,chart,dashboard,service,aiAgent.So this replaces the "etc" with the actual list, shows the error you get
otherwise, and adds a short note that ML entities are not included.
The ML note is the reason I bothered. Raising an incident on a model is a
natural thing to reach for when the thing that went wrong is a model, and the
current wording gives no reason to think it will not work. The workaround is
easy once you know: raise it on the dataset the model trained on, which is one
lineage hop away, and name the model in the title.
Two smaller things I noticed while working on this, not included here because
I am less sure they are documentation problems rather than intentional. Happy
to open separate issues if either is useful:
serviceandaiAgentsupport incidents but do not appear in any docs Icould find.
raised while testing stays in the instance.
Docs only, no code changes.