Skip to content

Commit d317607

Browse files
authored
feat(dynatrace): add the write and configuration surfaces (#6398)
* feat(dynatrace): add the write and configuration surfaces Takes the block from 22 operations to 47. The original PR shipped the read paths plus a few ingests; this closes the gaps that made those reads dead-end. The one that was a real defect: security was read-only. The audit-vulnerabilities skill promised "a remediation queue" and then gave you no way to act on it, even though muting is the single most common triage action. Adds mute and unmute, singly and in bulk, plus the remediation items behind a third-party finding, plus the Attacks API so an exploited vulnerability can be traced to the request that exploited it. The rest, by how much they unblock: - Custom tags (read/add/delete). Entity tags already drive every selector in the block; being able to write them closes a loop that was half open. - Settings objects (schemas, list, get, create, update, delete). This is how maintenance windows, alerting profiles, and management zones are configured in modern Dynatrace, so "open a maintenance window before the deploy" was simply unreachable before. The value is a schema-defined blob, so the tool is honestly opaque rather than falsely typed; the docs tell you to mirror an existing object. Update and delete carry the updateToken so a concurrent change fails instead of being overwritten. - Synthetic monitors and on-demand batch execution, which pairs with the deploy-marker tool to gate a release on a smoke test. - Problem comment get/update/delete, and SLO create/update/delete, completing CRUD that was previously half-built. Two structural notes. Synthetic monitors are the only endpoints still on Environment API v1, so `buildDynatraceUrl` grew a v1 sibling and the shared base-URL normalizer now strips either version; the query builder also learned to repeat a param per value, which Synthetic's `tag` needs. And creating an SLO returns 201 with an empty body and the new ID in the Location header, so that tool reads the header rather than parsing nothing. Deliberately excluded: the Grail/DQL query API. It is the long-term successor to the deprecated logs/search endpoint, but it authenticates with a platform token rather than an Api-Token, so it is a second auth path and belongs in its own change. * fix(dynatrace): drill the documented JSON shapes, and require the tag selector Two problems, one found in review and one worth more than it was given. The tag operations could run without an entity selector. All three tag tools declare `entitySelector` required, but the shared block field was only marked required for List Entities, so the block let a workflow reach those tools with an invalid configuration and let Dynatrace do the rejecting. My own structural auditor missed it because it only checked that *some* visible subBlock existed for a required param, not that the specific one was required — that check is now precise, and it confirms these three were the only instances across all 47 operations. The larger one: outputs were declaring `type: 'json'` for shapes the API reference documents in full. Thirty-five of them. The top-level entities were mapped properly, but nested payloads — a problem's evidence and impact analysis, a vulnerability's risk assessment and global counts, an attack's attacker, request, entry point and exploited vulnerability, a remediation item's assessment and mute state, the synthetic execution and failure records, the metric ingest error envelope, the DQL translation — were passed through as anonymous blobs. A downstream block could not reference `attacker.sourceIp` without knowing to guess it. All of those now carry their fields. What stays opaque is now only what genuinely is, and each says why in its description: a settings object's schema-defined value, an entity's type-dependent property bag and relationship keys, caller-supplied synthetic metadata, an audit log's JSON patch, the undocumented partial-success body of log ingestion, and the handful of security-detail shapes the reference names without expanding. * fix(dynatrace): make the synthetic enabled filter tri-state Review catch. `enabled` on List Synthetic Monitors is a three-way filter — enabled, disabled, or either — and I had it as a switch. The URL builder deliberately serializes `false` (there is a test pinning that `evaluate=false` survives), so leaving "Enabled Only" unchecked sent `enabled=false` and returned only the disabled monitors: exactly backwards. Made it a dropdown with Any / Enabled only / Disabled only, matching the monitorType field directly above it, which had the same shape and already used an empty-id "Any" option. The params mapper sends nothing for "Any". Checked the other nine switches rather than assuming. None share the bug: for each of them off genuinely means false, and false is Dynatrace's own default, so serializing it is correct. A test now pins that list so the trap cannot be re-introduced by converting one of them, alongside a test covering all three states of the filter.
1 parent 2e7e5ae commit d317607

41 files changed

Lines changed: 6094 additions & 91 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/content/docs/en/integrations/dynatrace.mdx

Lines changed: 645 additions & 9 deletions
Large diffs are not rendered by default.

apps/sim/blocks/blocks/dynatrace.ts

Lines changed: 924 additions & 13 deletions
Large diffs are not rendered by default.

apps/sim/lib/integrations/integrations.json

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"updatedAt": "2026-08-07",
2+
"updatedAt": "2026-08-08",
33
"integrations": [
44
{
55
"type": "onepassword",
@@ -5431,8 +5431,8 @@
54315431
"type": "dynatrace",
54325432
"slug": "dynatrace",
54335433
"name": "Dynatrace",
5434-
"description": "Query Dynatrace problems, metrics, entities, logs, SLOs, and vulnerabilities",
5435-
"longDescription": "Integrate Dynatrace into workflows. Investigate and close Davis problems, query metrics and monitored entities, search and ingest logs, push deployment events, track SLO burn rates, review Application Security vulnerabilities, and read the audit log.",
5434+
"description": "Manage Dynatrace problems, metrics, entities, logs, SLOs, settings, and security",
5435+
"longDescription": "Integrate Dynatrace into workflows. Investigate and close Davis problems, query metrics and monitored entities, search and ingest logs, push deployment events, manage SLOs and their burn rates, triage and mute Application Security vulnerabilities, review runtime attacks, tag entities, manage settings objects such as maintenance windows and alerting profiles, run synthetic monitors on demand, and read the audit log.",
54365436
"bgColor": "#FFFFFF",
54375437
"iconName": "DynatraceIcon",
54385438
"docsUrl": "https://docs.sim.ai/integrations/dynatrace",
@@ -5521,12 +5521,112 @@
55215521
"name": "Get Security Problem",
55225522
"description": "Get a single vulnerability with its description, remediation guidance, affected entities, and risk assessment."
55235523
},
5524+
{
5525+
"name": "Mute Security Problem",
5526+
"description": "Mute a single Dynatrace vulnerability with a reason, for triaging false positives or accepted risk."
5527+
},
5528+
{
5529+
"name": "Unmute Security Problem",
5530+
"description": "Unmute a single Dynatrace vulnerability, returning it to the active triage queue."
5531+
},
5532+
{
5533+
"name": "Mute Security Problems (Bulk)",
5534+
"description": "Mute several Dynatrace vulnerabilities at once with a shared reason, for bulk triage."
5535+
},
5536+
{
5537+
"name": "Unmute Security Problems (Bulk)",
5538+
"description": "Unmute several Dynatrace vulnerabilities at once, returning them to active triage."
5539+
},
5540+
{
5541+
"name": "List Remediation Items",
5542+
"description": "List the remediation items of a third-party vulnerability — the components to upgrade, their affected entities, and their mute state."
5543+
},
5544+
{
5545+
"name": "List Attacks",
5546+
"description": "List runtime attacks Dynatrace Application Protection detected — injection attempts, their source, and whether they were blocked."
5547+
},
5548+
{
5549+
"name": "Get Attack",
5550+
"description": "Get a single attack with its entry point, payload, attacker, and the vulnerability it exploited."
5551+
},
5552+
{
5553+
"name": "List Tags",
5554+
"description": "List the custom tags applied to the monitored entities an entity selector matches."
5555+
},
5556+
{
5557+
"name": "Add Tags",
5558+
"description": "Add custom tags to every monitored entity an entity selector matches. Tags drive selectors, management zones, and alerting."
5559+
},
5560+
{
5561+
"name": "Delete Tag",
5562+
"description": "Remove a custom tag from every monitored entity an entity selector matches. Deletes one key-value pair, or every tag with the key."
5563+
},
5564+
{
5565+
"name": "List Settings Schemas",
5566+
"description": "List the settings schemas available in the environment. Use it to find the schema ID for a configuration type such as builtin:alerting.profile."
5567+
},
5568+
{
5569+
"name": "List Settings Objects",
5570+
"description": "List settings objects — the configuration behind maintenance windows, alerting profiles, management zones, and anomaly detection."
5571+
},
5572+
{
5573+
"name": "Get Settings Object",
5574+
"description": "Get a single settings object with its value and its update token. Read it before updating so the token can guard against a concurrent change."
5575+
},
5576+
{
5577+
"name": "Create Settings Object",
5578+
"description": "Create a settings object — a maintenance window, alerting profile, management zone, or any other schema-backed configuration."
5579+
},
5580+
{
5581+
"name": "Update Settings Object",
5582+
"description": "Update an existing settings object. Pass the update token from Get Settings Object to fail rather than overwrite a concurrent change."
5583+
},
5584+
{
5585+
"name": "Delete Settings Object",
5586+
"description": "Delete a settings object. Dynatrace cannot undo this."
5587+
},
5588+
{
5589+
"name": "List Synthetic Monitors",
5590+
"description": "List synthetic monitors and their IDs. Use it to find the monitor IDs to feed into an on-demand execution."
5591+
},
5592+
{
5593+
"name": "Execute Synthetic Monitors",
5594+
"description": "Trigger an on-demand batch execution of synthetic monitors, for gating a deploy on a smoke test. Returns a batch ID to poll."
5595+
},
5596+
{
5597+
"name": "Get Synthetic Batch",
5598+
"description": "Get the status and failures of an on-demand synthetic batch execution. Poll it after triggering to gate a deploy on the result."
5599+
},
5600+
{
5601+
"name": "Get Problem Comment",
5602+
"description": "Get a single comment on a Dynatrace problem."
5603+
},
5604+
{
5605+
"name": "Update Problem Comment",
5606+
"description": "Replace the text of an existing comment on a Dynatrace problem."
5607+
},
5608+
{
5609+
"name": "Delete Problem Comment",
5610+
"description": "Delete a comment from a Dynatrace problem."
5611+
},
5612+
{
5613+
"name": "Create SLO",
5614+
"description": "Create a service-level objective from a metric expression, target, and evaluation timeframe."
5615+
},
5616+
{
5617+
"name": "Update SLO",
5618+
"description": "Update an existing service-level objective. Every field is replaced, so send the complete definition."
5619+
},
5620+
{
5621+
"name": "Delete SLO",
5622+
"description": "Delete a service-level objective."
5623+
},
55245624
{
55255625
"name": "Get Audit Logs",
55265626
"description": "Read the Dynatrace audit log — who changed which configuration, when, and whether it succeeded."
55275627
}
55285628
],
5529-
"operationCount": 22,
5629+
"operationCount": 47,
55305630
"triggers": [],
55315631
"triggerCount": 0,
55325632
"authType": "api-key",
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { tagProperties } from '@/tools/dynatrace/outputs'
2+
import type { DynatraceAddTagsParams, DynatraceAddTagsResponse } from '@/tools/dynatrace/types'
3+
import {
4+
buildDynatraceUrl,
5+
dynatraceHeaders,
6+
mapTags,
7+
parseJsonParam,
8+
readJsonBody,
9+
} from '@/tools/dynatrace/utils'
10+
import { ErrorExtractorId } from '@/tools/error-extractors'
11+
import type { ToolConfig } from '@/tools/types'
12+
13+
export const addTagsTool: ToolConfig<DynatraceAddTagsParams, DynatraceAddTagsResponse> = {
14+
id: 'dynatrace_add_tags',
15+
name: 'Dynatrace Add Tags',
16+
description:
17+
'Add custom tags to every monitored entity an entity selector matches. Tags drive selectors, management zones, and alerting.',
18+
version: '1.0.0',
19+
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
20+
21+
params: {
22+
environmentUrl: {
23+
type: 'string',
24+
required: true,
25+
visibility: 'user-only',
26+
description:
27+
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
28+
},
29+
apiToken: {
30+
type: 'string',
31+
required: true,
32+
visibility: 'user-only',
33+
description: 'Dynatrace access token (dt0c01...) with the entities.write scope',
34+
},
35+
entitySelector: {
36+
type: 'string',
37+
required: true,
38+
visibility: 'user-or-llm',
39+
description: 'Entity selector for the entities to tag, e.g. type("HOST"),tag("env:staging")',
40+
},
41+
tags: {
42+
type: 'json',
43+
required: true,
44+
visibility: 'user-or-llm',
45+
description:
46+
'Tags to add, as an array of objects with a key and an optional value (e.g. [{"key":"owner","value":"platform"},{"key":"reviewed"}])',
47+
},
48+
from: {
49+
type: 'string',
50+
required: false,
51+
visibility: 'user-or-llm',
52+
description:
53+
'Start of the timeframe as UTC milliseconds, ISO 8601, or a relative expression. Defaults to now-24h',
54+
},
55+
to: {
56+
type: 'string',
57+
required: false,
58+
visibility: 'user-or-llm',
59+
description: 'End of the timeframe in the same formats as From. Defaults to now',
60+
},
61+
},
62+
63+
request: {
64+
url: (params) =>
65+
buildDynatraceUrl(params.environmentUrl, '/tags', {
66+
entitySelector: params.entitySelector,
67+
from: params.from,
68+
to: params.to,
69+
}),
70+
method: 'POST',
71+
headers: (params) => dynatraceHeaders(params.apiToken),
72+
body: (params) => {
73+
const tags = parseJsonParam(params.tags)
74+
if (!Array.isArray(tags) || tags.length === 0) {
75+
throw new Error('tags must be a non-empty array of objects with a key')
76+
}
77+
return { tags }
78+
},
79+
},
80+
81+
transformResponse: async (response: Response) => {
82+
const data = await readJsonBody(response)
83+
84+
return {
85+
success: true,
86+
output: {
87+
appliedTags: mapTags(data.appliedTags),
88+
matchedEntitiesCount: (data.matchedEntitiesCount as number) ?? null,
89+
},
90+
}
91+
},
92+
93+
outputs: {
94+
appliedTags: {
95+
type: 'array',
96+
description: 'Tags that were applied',
97+
items: { type: 'object', properties: tagProperties },
98+
},
99+
matchedEntitiesCount: {
100+
type: 'number',
101+
description: 'How many entities the selector matched and were tagged',
102+
nullable: true,
103+
},
104+
},
105+
}

0 commit comments

Comments
 (0)