Skip to content

Commit 3a8d2a6

Browse files
committed
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.
1 parent 2e7e5ae commit 3a8d2a6

37 files changed

Lines changed: 5276 additions & 68 deletions

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

Lines changed: 596 additions & 6 deletions
Large diffs are not rendered by default.

apps/sim/blocks/blocks/dynatrace.ts

Lines changed: 903 additions & 12 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+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import type {
2+
DynatraceCreateSettingsObjectParams,
3+
DynatraceCreateSettingsObjectResponse,
4+
} from '@/tools/dynatrace/types'
5+
import {
6+
buildDynatraceUrl,
7+
dynatraceHeaders,
8+
mapSettingsWriteResult,
9+
parseJsonParam,
10+
} from '@/tools/dynatrace/utils'
11+
import { ErrorExtractorId } from '@/tools/error-extractors'
12+
import type { ToolConfig } from '@/tools/types'
13+
14+
export const createSettingsObjectTool: ToolConfig<
15+
DynatraceCreateSettingsObjectParams,
16+
DynatraceCreateSettingsObjectResponse
17+
> = {
18+
id: 'dynatrace_create_settings_object',
19+
name: 'Dynatrace Create Settings Object',
20+
description:
21+
'Create a settings object — a maintenance window, alerting profile, management zone, or any other schema-backed configuration.',
22+
version: '1.0.0',
23+
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
24+
25+
params: {
26+
environmentUrl: {
27+
type: 'string',
28+
required: true,
29+
visibility: 'user-only',
30+
description:
31+
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
32+
},
33+
apiToken: {
34+
type: 'string',
35+
required: true,
36+
visibility: 'user-only',
37+
description: 'Dynatrace access token (dt0c01...) with the settings.write scope',
38+
},
39+
schemaId: {
40+
type: 'string',
41+
required: true,
42+
visibility: 'user-or-llm',
43+
description:
44+
'Schema of the object to create, e.g. builtin:alerting.maintenance-window. List Settings Schemas returns the available IDs',
45+
},
46+
scope: {
47+
type: 'string',
48+
required: true,
49+
visibility: 'user-or-llm',
50+
description:
51+
'Scope the object applies to, e.g. environment or an entity ID such as HOST-06F288EE2A930951',
52+
},
53+
value: {
54+
type: 'json',
55+
required: true,
56+
visibility: 'user-or-llm',
57+
description:
58+
'The configuration itself. Its shape is defined by the schema — read an existing object of the same schema to see the expected fields',
59+
},
60+
schemaVersion: {
61+
type: 'string',
62+
required: false,
63+
visibility: 'user-or-llm',
64+
description: 'Schema version to validate against. Defaults to the latest',
65+
},
66+
externalId: {
67+
type: 'string',
68+
required: false,
69+
visibility: 'user-or-llm',
70+
description: 'External ID to correlate the object with a system outside Dynatrace',
71+
},
72+
validateOnly: {
73+
type: 'boolean',
74+
required: false,
75+
visibility: 'user-or-llm',
76+
description: 'Validate the payload without creating anything',
77+
},
78+
},
79+
80+
request: {
81+
url: (params) =>
82+
buildDynatraceUrl(params.environmentUrl, '/settings/objects', {
83+
validateOnly: params.validateOnly,
84+
}),
85+
method: 'POST',
86+
headers: (params) => dynatraceHeaders(params.apiToken),
87+
// The endpoint takes an array; this tool creates exactly one object.
88+
body: (params) => {
89+
const value = parseJsonParam(params.value)
90+
if (value === undefined) throw new Error('value is required')
91+
const entry: Record<string, unknown> = {
92+
schemaId: params.schemaId,
93+
scope: params.scope,
94+
value,
95+
}
96+
if (params.schemaVersion) entry.schemaVersion = params.schemaVersion
97+
if (params.externalId) entry.externalId = params.externalId
98+
return JSON.stringify([entry])
99+
},
100+
},
101+
102+
transformResponse: async (response: Response) => {
103+
const text = await response.text()
104+
const parsed = text.trim() ? JSON.parse(text) : []
105+
const entries = Array.isArray(parsed) ? (parsed as Array<Record<string, unknown>>) : []
106+
const results = entries.map(mapSettingsWriteResult)
107+
108+
return {
109+
success: true,
110+
output: {
111+
results,
112+
objectId: results[0]?.objectId ?? null,
113+
},
114+
}
115+
},
116+
117+
outputs: {
118+
results: {
119+
type: 'array',
120+
description: 'One result per submitted object',
121+
items: {
122+
type: 'object',
123+
properties: {
124+
code: { type: 'number', description: 'Per-object HTTP status', nullable: true },
125+
objectId: { type: 'string', description: 'ID of the created object', nullable: true },
126+
writeError: {
127+
type: 'json',
128+
description: 'Validation error for this object, when it failed',
129+
nullable: true,
130+
},
131+
invalidValue: {
132+
type: 'json',
133+
description: 'The value that was rejected',
134+
optional: true,
135+
},
136+
},
137+
},
138+
},
139+
objectId: {
140+
type: 'string',
141+
description: 'ID of the created object, lifted from the first result',
142+
nullable: true,
143+
},
144+
},
145+
}

0 commit comments

Comments
 (0)