Skip to content

Commit 2e7e5ae

Browse files
authored
feat(dynatrace): add the Dynatrace integration (#6393)
* feat(dynatrace): add the Dynatrace integration Adds a Dynatrace block backed by 22 Environment API v2 tools, covering the surfaces an observability workflow actually reaches for: - Problems: list, get, close, list comments, add comment - Metrics: query data points, list and get descriptors, ingest line protocol - Entities: list, get, list entity types - Events: list, get, ingest - Logs: search, ingest - SLOs: list, get - Application Security: list and get security problems - Audit log: read Every request path, query parameter, and response mapping is taken from the published Dynatrace API reference — no inferred fields. Auth is an access token sent as `Authorization: Api-Token ...` against a user-supplied environment URL, so SaaS, Managed, and environment ActiveGate all work. Two details worth knowing: `ingest_event` exposes Dynatrace's event timeout as `eventTimeout`, not `timeout`. The tool transport reserves `params.timeout` for the HTTP request deadline, so the obvious name would have silently retargeted the wrong knob. `get_metric` encodes its path segment with `encodeDynatracePathSegment` rather than `encodeURIComponent`, which leaves the `:` separators in metric keys and transformation operators intact, matching the docs' own examples. * fix(dynatrace): close the gaps a validation pass turned up Three real defects and one usability gap, all found by auditing the tools against the Dynatrace API reference a second time. `ingest_logs` double-encoded its payload. `logs` is a `json` param, and a `json` param arrives as a *string* whenever it comes from a long-input field or an LLM tool call — only a block-to-block reference hands over a parsed value. `JSON.stringify` on that string produced `"[{...}]"`, so Dynatrace received a quoted string where it expected an array. The block hid this in the UI path by pre-parsing, but the parse lived in `tools.config.params` and *threw* on malformed input, and it never covered the direct tool-call path at all. Both tools now normalize through the shared `parseJsonParam`, so the tool is correct regardless of who calls it, and the block just forwards the raw value. `ingest_event.properties` had the identical bug. Path identifiers were not trimmed. A problem or entity ID pasted with a trailing newline became `%0A` in the URL and 404'd with nothing to suggest whitespace was the cause. Errors dropped the part that matters. Dynatrace's ErrorEnvelope carries `constraintViolations[]`, which names the offending selector or parameter; the generic `nested-error-object` extractor returns only `error.message` ("Constraints violated."), and which extractor won was left to fallback order. Adds a `dynatrace-errors` extractor that folds the violations into the message and pins it on all 22 tools. It sits after `nested-error-object` in the chain, which already matches this shape, so no other service's error handling changes. Adds 21 tests covering URL construction for SaaS/Managed/ActiveGate, cursor pagination dropping sibling filters, identifier trimming, metric-key colon preservation, both JSON-param paths, the `eventTimeout` -> `timeout` mapping, EntityStub flattening, the audit log's dotted `dt.settings.*` keys, and the 204/200 split on log ingestion. * docs(dynatrace): add the page intro, and pin every response key in tests Adds a MANUAL-CONTENT:intro block to the generated integration page covering what the block reaches, how to get an environment URL and a scoped token for SaaS vs Managed, how selectors work, and how cursor pagination behaves. Verified it survives `generate-docs.ts` byte-identically. Also closes the last silent-failure gap the validation pass left open. A wrong top-level response key does not throw — it maps to an empty array and reads as "no results", which is indistinguishable from a genuinely empty environment. Dynatrace is unusually easy to get wrong here: the SLO list returns `slo` (singular) and the metric query returns `result` (singular). Adds a table-driven test asserting the documented key for all ten list endpoints plus the scalar keys of the ingest and single-entity responses. Confirmed it bites by flipping `data.slo` to `data.slos` and watching only that row fail. * chore(dynatrace): type the shared param map as unknown Review follow-up. `Record<string, any>` in the block's params builder dropped compile-time checking from every operation's shared params; `unknown` is enough here since the values flow straight into the tool param maps. Matches .claude/rules/sim-typescript.md, which sibling blocks (Datadog, Grafana) still violate. * fix(dynatrace): stop three silent failures found in a final read-through All three turn a failed call into something that looks like a successful empty one, which is the worst shape for an observability integration — you cannot tell "nothing is wrong" from "the call did not work". `readJsonBody` swallowed any unparseable body and returned `{}`. A gateway HTML page, a captive-portal interstitial, or a truncated payload therefore mapped every field to null and read as "no problems found". Only genuinely empty bodies are tolerated now (201 from add-comment, 204 from log ingest); anything else that will not parse raises with a truncated preview. `ingest_logs` sent `[]` when the payload was missing or empty. Dynatrace answers 204 to that, so the tool reported `accepted: true` for a call that shipped no logs. It now fails loudly instead. `encodeDynatracePathSegment` percent-encoded the whole metric key and then regex-unescaped `%3A` back to `:`. Same output, but it undoes the encoder's work and hides the intent. Colons are structural in a metric key, so it now splits on them, encodes each part, and rejoins — which says that directly. Each fix has a test, and each test was confirmed to fail in isolation with only its own fix reverted.
1 parent 5cf1f9b commit 2e7e5ae

41 files changed

Lines changed: 6617 additions & 3 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/components/icons.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6048,6 +6048,37 @@ export function RDSIcon(props: SVGProps<SVGSVGElement>) {
60486048
)
60496049
}
60506050

6051+
export function DynatraceIcon(props: SVGProps<SVGSVGElement>) {
6052+
return (
6053+
<svg {...props} viewBox='0 0 89 90' xmlns='http://www.w3.org/2000/svg'>
6054+
<path
6055+
fill='#1496FF'
6056+
d='M29.9672771,7.52412666 C28.8124385,13.6191079 27.4009692,22.6653432 26.6310768,31.8398939 C25.2837652,48.0076335 26.1178152,58.8502844 26.1178152,58.8502844 L3.34183275,80.4714283 C3.34183275,80.4714283 1.60957493,68.3456235 0.711367168,54.680034 C0.198105592,46.211218 0.00563250016,38.7689252 0.00563250016,34.2778864 C0.00563250016,34.0212556 0.133947894,33.7646248 0.133947894,33.507994 C0.133947894,33.1872055 0.518894077,30.1717937 3.47014814,27.3488551 C6.678033,24.2692856 30.3522232,5.72771114 29.9672771,7.52412666 Z'
6057+
/>
6058+
<path
6059+
fill='#1284EA'
6060+
d='M29.9672771,7.52412666 C28.8124385,13.6191079 27.4009692,22.6653432 26.6310768,31.8398939 C26.6310768,31.8398939 1.41710184,28.8244821 0.00563250016,34.9194633 C0.00563250016,34.5986748 0.45473638,30.8775284 3.40599045,28.0545897 C6.6138753,24.9750203 30.3522232,5.72771114 29.9672771,7.52412666 L29.9672771,7.52412666 Z'
6061+
/>
6062+
<path
6063+
fill='#B4DC00'
6064+
d='M0.00563250016,33.4438363 L0.00563250016,34.8553056 C0.262263289,33.7646248 0.711367168,32.9947324 1.60957493,31.7757362 C3.47014814,29.4019014 6.48555991,28.7603244 7.70455615,28.632009 C13.8636951,27.797959 22.9740881,26.8355935 32.1486388,26.5789627 C48.3805361,26.0657011 59.0948715,27.4130128 59.0948715,27.4130128 L81.870854,5.79186884 C81.870854,5.79186884 69.9375224,3.54634944 56.3360906,1.94240701 C47.4181707,0.851726163 39.5909316,0.274306889 35.1640505,0.0176761002 C34.843262,0.0176761002 31.6995349,-0.367270083 28.7482808,2.45566859 C25.540396,5.53523805 9.24434089,20.9972431 2.70025578,27.2205397 C-0.250998288,30.0434784 0.00563250016,33.1872055 0.00563250016,33.4438363 Z'
6065+
/>
6066+
<path
6067+
fill='#6F2DA8'
6068+
d='M81.036804,61.0958038 C74.877665,61.9298538 65.767272,62.956377 56.5927214,63.2771655 C40.360824,63.790427 29.5823309,62.4431154 29.5823309,62.4431154 L6.8063484,84.128417 C6.8063484,84.128417 18.8679955,86.5022518 32.4694272,88.0420366 C40.8099279,89.004402 48.188063,89.5176636 52.6791018,89.7742944 C52.9998903,89.7742944 53.5131519,89.5176636 53.8339404,89.5176636 C54.1547289,89.5176636 57.298456,88.9402443 60.2497101,86.1173056 C63.457595,83.0377362 82.8332195,60.9033307 81.036804,61.0958038 L81.036804,61.0958038 Z'
6069+
/>
6070+
<path
6071+
fill='#591F91'
6072+
d='M81.036804,61.0958038 C74.877665,61.9298538 65.767272,62.956377 56.5927214,63.2771655 C56.5927214,63.2771655 58.3249792,88.6194558 52.229998,89.7101367 C52.5507864,89.7101367 56.7210368,89.5176636 59.6722908,86.6947249 C62.8801757,83.6151554 82.8332195,60.9033307 81.036804,61.0958038 L81.036804,61.0958038 Z'
6073+
/>
6074+
<path
6075+
fill='#73BE28'
6076+
d='M53.5773096,89.8384521 C53.1282057,89.8384521 52.6791018,89.7742944 52.1658403,89.7742944 C53.3206788,89.5818213 54.0905712,89.1968751 55.3095674,88.2986673 C57.7475599,86.5664095 58.5174523,83.5509978 58.7740831,82.3320015 C59.8647639,76.2370203 61.3403909,67.190785 62.0461256,58.0162343 C63.3292796,41.8484946 62.5593872,31.0700015 62.5593872,31.0700015 L85.3353697,9.38469988 C85.3353697,9.38469988 87.0034698,21.4463469 87.9658353,35.1119364 C88.5432545,44.0298563 88.7357276,51.9212531 88.7998853,56.2839765 C88.7998853,56.604765 89.0565161,59.7484921 86.105262,62.5714308 C82.8973772,65.6510003 66.6013221,81.177163 60.1213947,87.4004596 C57.0418252,90.2233983 53.8980981,89.8384521 53.5773096,89.8384521 L53.5773096,89.8384521 Z'
6077+
/>
6078+
</svg>
6079+
)
6080+
}
6081+
60516082
export function DynamoDBIcon(props: SVGProps<SVGSVGElement>) {
60526083
return (
60536084
<svg

apps/docs/components/ui/icon-mapping.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import {
6363
DubIcon,
6464
DuckDuckGoIcon,
6565
DynamoDBIcon,
66+
DynatraceIcon,
6667
ElasticsearchIcon,
6768
ElevenLabsIcon,
6869
EmailBisonIcon,
@@ -316,6 +317,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
316317
dub: DubIcon,
317318
duckduckgo: DuckDuckGoIcon,
318319
dynamodb: DynamoDBIcon,
320+
dynatrace: DynatraceIcon,
319321
elasticsearch: ElasticsearchIcon,
320322
elevenlabs: ElevenLabsIcon,
321323
emailbison: EmailBisonIcon,

apps/docs/content/docs/en/integrations/dynatrace.mdx

Lines changed: 601 additions & 0 deletions
Large diffs are not rendered by default.

apps/docs/content/docs/en/integrations/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
"dub",
6565
"duckduckgo",
6666
"dynamodb",
67+
"dynatrace",
6768
"elasticsearch",
6869
"elevenlabs",
6970
"emailbison",

0 commit comments

Comments
 (0)