Skip to content

Commit cabd2e2

Browse files
authored
feat(datadog): extend to 40 tools and align every operation with the published OpenAPI specs (#6745)
* feat(datadog): add incidents, SLOs, dashboards, synthetics, Cloud SIEM, and APM tools Extends the Datadog block from 12 to 39 operations, all verified against Datadog's published OpenAPI specs: - Incidents (v2, public beta): list, get, create, update, add todo - SLOs (v1): list, get, create, update, delete, history - Dashboards (v1): list, get, create, delete - Synthetics (v1): list tests, get test, latest results, trigger, pause/resume - Cloud SIEM (v2): search signals, get signal, update triage state, assign, list detection rules - APM: search spans (v2), list Service Catalog definitions (v2) Adds tools/datadog/utils.ts so every tool builds its URL from the configured site/region and shares the JSON:API-aware error extraction, and handles the v1 flat vs v2 envelope shapes and cursor pagination per endpoint. * fix(datadog): align every operation with the published OpenAPI specs Validated all 39 shipped operations (plus the 12 pre-existing ones that had never been spec-checked) against the DataDog v1 and v2 OpenAPI schemas. - `POST /api/v2/downtime` requires `monitor_identifier`, so a downtime created without a monitor id was rejected. Default to the `*` monitor tag. - A one-time downtime schedule declares `additionalProperties: false` and accepts only `start`/`end`; the timezone moves to `display_timezone`. - `GET /api/v2/downtime` has no `monitor_id` filter, and the response carries no `disabled` attribute. Downtime ids are UUID strings, not numbers. - Drop scaffold types for operations that do not exist (metric metadata, event query, monitor update/delete/unmute, host listing) along with their fields. - Note that monitor mute is no longer published in the v1 specification. - Add browser Synthetic test results, which the browser-specific endpoint returns with its own camelCase step-count shape. - Replace every `any` with a spec-derived interface, keeping the polymorphic service-definition schema opaque. * fix(datadog): remove remaining any types and declare every returned output field Replace the six surviving `Record<string, any>` request-body and response-cast sites with concrete spec-derived shapes, and declare the output fields that transformResponse already returned but outputs omitted: - create_downtime / list_downtimes: timezone, created, modified - create_monitor / get_monitor: options, creator - list_monitors: message, priority, options, created, modified, creator - query_logs: content.attributes, content.tags - update_security_signal_state / _assignee: type; assignee also gained the archiveReason/archiveComment pair its sibling already declared - query_timeseries: series gained the items shape it never described * fix(datadog): stop dropping downtime targeting inputs in the block mapping create_downtime accepts monitorTags, timezone and muteFirstRecoveryNotification, but the block exposed no inputs for them and never forwarded them. Monitor-tag targeting silently fell back to the `*` tag, so a downtime meant for one team's monitors muted every monitor in scope. Adds the three advanced sub-blocks and wires them through. Also routes list_downtimes' currentOnly through toSwitchBoolean. A switch yields the strings 'true'/'false', and 'false' is truthy, so turning the toggle off still sent current_only=true. Every other switch in the block already used the helper; this was the last raw one. * fix(datadog): correct metric type codes, stop SLO update data loss, drop unpublished mute Independent re-validation of all 39 operations against the DataDog/datadog-api-client-go generator specs (v1 and v2 openapi.yaml) rather than the client-rendered docs site. Correctness: - submit_metrics sent inverted MetricIntakeType codes (gauge as 0/unspecified, rate as 1/count, count as 2/rate), silently changing how Datadog aggregated every submitted series. The spec enum is 0 unspecified, 1 count, 2 rate, 3 gauge; an unrecognized type is now omitted so Datadog infers it. Also stops stamping an invented `resources: [{name:'host'}]` default and now forwards `interval`, which Datadog requires for count and rate metrics. - update_slo replaced the whole SLO with only the fields the caller filled in, so editing one field erased description, tags, query, monitor_ids, groups, thresholds, and timeframe. PUT /api/v1/slo/{slo_id} is a full replacement, so the stored SLO is now read first and the supplied edits are overlaid onto it, with the read-only fields stripped. - update_incident admitted empty strings, so a blank input could blank a stored incident title or fail as an invalid date-time. - query_timeseries reported a failed query as success: Datadog returns 200 with a non-ok `status` and the reason in `error`. - create_monitor swallowed malformed options JSON and created a monitor with no thresholds. - send_logs rebuilt each entry from a fixed field list, discarding the custom attributes Datadog accepts as additionalProperties, and padded absent optional fields with empty strings. Removed: - mute_monitor. /api/v1/monitor/{monitor_id}/mute is absent from the v1 spec entirely, there is no unmute counterpart to reverse it, and downtimes are the supported mechanism. Contract accuracy: - Security signal search advertised relative times ("now-1h"); the spec types filter.from/to as format: date-time. Descriptions, placeholders, and wand prompts now produce ISO-8601. - list_incidents advertised an `include` value ("integrations") that is not in the spec enum, and neither incident tool trimmed the comma-separated list, so "users, attachments" 400d. - Invalid "ok" group state dropped from both monitor descriptions. - time_slice removed from SLO create input, which cannot build one without an SLI specification. - DatadogSite gains ap2, uk1, and us2.ddog-gov.com. Pagination and errors: - list_downtimes silently truncated at Datadog's default 30 with no way to page; adds page[limit]/page[offset] and surfaces totalCount. - query_logs returned a cursor it had no way to accept back. - Error extraction consolidated onto datadogErrorMessage, which now also reads the dictionary-shaped errors of the SLO delete conflict. Ten tools were reading `.detail` off plain strings or the raw entry off objects, degrading every failure to a bare status line. - Debug logging removed from list_monitors. Adds 29 regression tests, each verified to fail when its fix is reverted. * fix(datadog): add SEV-0, document page-size caps, drop unsourced output defaults - The severity dropdown omitted SEV-0, which IncidentSeverity allows and both incident tool descriptions already advertised. - Page-size descriptions now state Datadog's documented default of 10 and cap of 100 instead of an arbitrary example, so an agent does not request an out-of-range page. - trigger_synthetics_tests emitted an explicit null for a string-typed optional output, and update_synthetics_status reported 'live' on the error path regardless of what the caller actually requested. * fix(datadog): keep mute_monitor and add the missing unmute counterpart Reverses the removal in the previous commit. Absence from the datadog-api-client-go generator spec showed the endpoint is unpublished there, not that it is retired: Datadog's official Python client still implements it on master as `Monitor.mute(id, scope=, end=)` and `Monitor.unmute(id, scope=, all_scopes=)` (datadogpy datadog/api/monitors.py), which `_trigger_class_action` resolves to `POST /api/v1/monitor/{id}/mute` and `/unmute` with exactly those body fields. mute_monitor has also been in the block since #2175 in December, so dropping it would have broken existing workflows for an endpoint that two independent sources agree is live. The genuine defect was that muting was a one-way trapdoor: Sim could mute a monitor but had no way to reverse it. Adds datadog_unmute_monitor, sharing the monitor ID and scope inputs with mute, so the operation is recoverable from the same block. Also: mute no longer discards the response body (it now reports the monitor id, name, and state), routes errors through datadogErrorMessage, encodes the monitor ID in the path, and stops dropping an explicit `end` of 0. * fix(datadog): make downtime targeting explicit and reach downtime pagination from the block Addresses the review findings on the previous round. - create_downtime accepted both a monitor ID and monitor tags but `monitor_identifier` is a oneOf, so it silently kept the ID and dropped the tags, muting a different set of monitors than the caller asked for. It now rejects the ambiguous combination. - create_downtime ran Number.parseInt on the monitor ID with no validation, so a non-numeric value became NaN and serialized as null inside monitor_identifier. It now uses the same parseMonitorIds guard the SLO path already had, naming the offending value. - list_downtimes gained limit/offset in the tool but the block exposed neither, so no block-driven call could page past Datadog's default. Adds the two sub-blocks and wires them through the params mapper. - The block did not declare the totalCount the tool now returns, so nothing downstream could bind to it. * fix(datadog): tolerate non-string list inputs and keep the shipped mute subblock ids Both defects were introduced by this branch. - splitCommaList called .split on its argument, so routing create_downtime's monitorId through it turned a legitimate numeric input into a TypeError before the request was built. A <Block.output> reference to get_monitor or list_monitors resolves to a number, and an LLM tool call can pass a number or an array, so the helper now normalizes all three shapes. The previous Number.parseInt path had accepted a number by coercion. - Adding the unmute operation renamed the mute subblock ids scope/end to muteScope/muteEnd. Workflow state is persisted by subblock id, so every existing Mute Monitor block would have kept the old keys and silently lost its scope and end time. Restored the shipped ids; both are still unique block-wide and no operation reads another operation's value. * fix(datadog): compare downtime targets after parsing, not before A whitespace-only Monitor ID is truthy as a raw string but parses to no monitor, so the oneOf conflict guard rejected a valid tag-targeted downtime whenever the untouched Monitor ID field carried blank text. Both sides are now compared after parsing.
1 parent be20df9 commit cabd2e2

52 files changed

Lines changed: 8232 additions & 549 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/datadog.mdx

Lines changed: 929 additions & 8 deletions
Large diffs are not rendered by default.

apps/sim/blocks/blocks/datadog.ts

Lines changed: 1709 additions & 59 deletions
Large diffs are not rendered by default.

apps/sim/lib/integrations/integrations.json

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4933,7 +4933,11 @@
49334933
},
49344934
{
49354935
"name": "Mute Monitor",
4936-
"description": "Mute a monitor to temporarily suppress notifications."
4936+
"description": "Mute a monitor to temporarily suppress its notifications. Use Unmute Monitor to reverse it, or schedule a downtime instead when you want a planned, auditable maintenance window."
4937+
},
4938+
{
4939+
"name": "Unmute Monitor",
4940+
"description": "Unmute a monitor so it resumes sending notifications. Reverses Mute Monitor, either for one scope or for every scope at once."
49374941
},
49384942
{
49394943
"name": "Query Logs",
@@ -4954,9 +4958,121 @@
49544958
{
49554959
"name": "Cancel Downtime",
49564960
"description": "Cancel a scheduled downtime."
4961+
},
4962+
{
4963+
"name": "List Incidents",
4964+
"description": "List incidents for the organization. Requires the Incident Management `incident_read` permission; the Incidents API is in public beta."
4965+
},
4966+
{
4967+
"name": "Get Incident",
4968+
"description": "Get the details of a single incident by ID. Requires the Incident Management `incident_read` permission; the Incidents API is in public beta."
4969+
},
4970+
{
4971+
"name": "Create Incident",
4972+
"description": "Declare a new incident. Requires the Incident Management `incident_write` permission; the Incidents API is in public beta."
4973+
},
4974+
{
4975+
"name": "Update Incident",
4976+
"description": "Partially update an existing incident. Requires the Incident Management `incident_write` permission; the Incidents API is in public beta."
4977+
},
4978+
{
4979+
"name": "Add Incident Todo",
4980+
"description": "Add a follow-up task (todo) to an incident. Requires the Incident Management `incident_write` permission; the Incidents API is in public beta."
4981+
},
4982+
{
4983+
"name": "List SLOs",
4984+
"description": "List service level objectives, optionally filtered by IDs, name, tags, or underlying metrics query."
4985+
},
4986+
{
4987+
"name": "Get SLO",
4988+
"description": "Get the configuration of a single service level objective by ID."
4989+
},
4990+
{
4991+
"name": "Create SLO",
4992+
"description": "Create a service level objective from a metric query, monitors, or a time-slice condition."
4993+
},
4994+
{
4995+
"name": "Update SLO",
4996+
"description": "Update a service level objective. Reads the current SLO first and applies only the fields you supply, so anything left blank keeps its stored value."
4997+
},
4998+
{
4999+
"name": "Delete SLO",
5000+
"description": "Permanently delete a service level objective. Datadog returns a conflict when the SLO is still referenced by a dashboard."
5001+
},
5002+
{
5003+
"name": "Get SLO History",
5004+
"description": "Get an SLO’s history over a time window, including the overall SLI value and remaining error budget."
5005+
},
5006+
{
5007+
"name": "List Dashboards",
5008+
"description": "List custom created or cloned dashboards. Datadog preset dashboards are not returned."
5009+
},
5010+
{
5011+
"name": "Get Dashboard",
5012+
"description": "Get the full definition of a dashboard, including its widgets."
5013+
},
5014+
{
5015+
"name": "Create Dashboard",
5016+
"description": "Create a dashboard from a title, layout type, and widget definitions."
5017+
},
5018+
{
5019+
"name": "Delete Dashboard",
5020+
"description": "Delete a dashboard by ID."
5021+
},
5022+
{
5023+
"name": "List Synthetic Tests",
5024+
"description": "List all Synthetic tests (API, browser, and mobile) with their current status."
5025+
},
5026+
{
5027+
"name": "Get Synthetic Test",
5028+
"description": "Get the configuration of a Synthetic test by public ID. Browser test steps are not included by this type-agnostic endpoint."
5029+
},
5030+
{
5031+
"name": "Get Synthetic Test Results",
5032+
"description": "Get the latest result summaries (up to the last 150 runs) for a Synthetic API test."
5033+
},
5034+
{
5035+
"name": "Get Browser Synthetic Test Results",
5036+
"description": "Get the latest result summaries (up to the last 150 runs) for a Synthetic browser test, including step counts and errors."
5037+
},
5038+
{
5039+
"name": "Trigger Synthetic Tests",
5040+
"description": "Trigger an immediate run of one or more Synthetic tests by public ID."
5041+
},
5042+
{
5043+
"name": "Pause Or Start Synthetic Test",
5044+
"description": "Pause or resume a Synthetic test by setting its status to \"paused\" or \"live\"."
5045+
},
5046+
{
5047+
"name": "List Security Signals",
5048+
"description": "Search Cloud SIEM security signals by query and time range. Requires the `security_monitoring_signals_read` permission."
5049+
},
5050+
{
5051+
"name": "Get Security Signal",
5052+
"description": "Get the details of a single Cloud SIEM security signal. Requires the `security_monitoring_signals_read` permission."
5053+
},
5054+
{
5055+
"name": "Update Security Signal State",
5056+
"description": "Change the triage state of a Cloud SIEM security signal to open, under_review, or archived. Requires the `security_monitoring_signals_write` permission."
5057+
},
5058+
{
5059+
"name": "Assign Security Signal",
5060+
"description": "Assign a Cloud SIEM security signal to a Datadog user by UUID. Requires the `security_monitoring_signals_write` permission."
5061+
},
5062+
{
5063+
"name": "List Security Rules",
5064+
"description": "List Cloud SIEM detection rules. Requires the `security_monitoring_rules_read` permission."
5065+
},
5066+
{
5067+
"name": "Search Spans",
5068+
"description": "Search indexed APM spans using the span query syntax, with cursor pagination."
5069+
},
5070+
{
5071+
"name": "List Services",
5072+
"description": "List service definitions from the Datadog Service Catalog. Requires the `apm_service_catalog_read` permission."
49575073
}
49585074
],
4959-
"operationCount": 12,
5075+
"operationCount": 41,
49605076
"triggers": [],
49615077
"triggerCount": 0,
49625078
"authType": "api-key",
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import type { AddIncidentTodoParams, AddIncidentTodoResponse } from '@/tools/datadog/types'
2+
import {
3+
datadogApiUrl,
4+
datadogErrorMessage,
5+
datadogHeaders,
6+
splitCommaList,
7+
} from '@/tools/datadog/utils'
8+
import type { ToolConfig } from '@/tools/types'
9+
10+
export const addIncidentTodoTool: ToolConfig<AddIncidentTodoParams, AddIncidentTodoResponse> = {
11+
id: 'datadog_add_incident_todo',
12+
name: 'Datadog Add Incident Todo',
13+
description:
14+
'Add a follow-up task (todo) to an incident. Requires the Incident Management `incident_write` permission; the Incidents API is in public beta.',
15+
version: '1.0.0',
16+
17+
params: {
18+
incidentId: {
19+
type: 'string',
20+
required: true,
21+
visibility: 'user-or-llm',
22+
description: 'The UUID of the incident the todo belongs to',
23+
},
24+
content: {
25+
type: 'string',
26+
required: true,
27+
visibility: 'user-or-llm',
28+
description: 'The follow-up task content (e.g., "Restore lost data")',
29+
},
30+
assignees: {
31+
type: 'string',
32+
required: true,
33+
visibility: 'user-or-llm',
34+
description:
35+
'Comma-separated assignee handles (e.g., "@jane@example.com,@on-call"). Datadog requires at least one assignee',
36+
},
37+
dueDate: {
38+
type: 'string',
39+
required: false,
40+
visibility: 'user-or-llm',
41+
description: 'ISO-8601 timestamp for when the todo should be completed',
42+
},
43+
apiKey: {
44+
type: 'string',
45+
required: true,
46+
visibility: 'user-only',
47+
description: 'Datadog API key',
48+
},
49+
applicationKey: {
50+
type: 'string',
51+
required: true,
52+
visibility: 'user-only',
53+
description: 'Datadog Application key',
54+
},
55+
site: {
56+
type: 'string',
57+
required: false,
58+
visibility: 'user-only',
59+
description: 'Datadog site/region (default: datadoghq.com)',
60+
},
61+
},
62+
63+
request: {
64+
url: (params) =>
65+
datadogApiUrl(
66+
params.site,
67+
`/api/v2/incidents/${encodeURIComponent(params.incidentId)}/relationships/todos`
68+
),
69+
method: 'POST',
70+
headers: datadogHeaders,
71+
body: (params) => {
72+
const attributes: Record<string, unknown> = {
73+
content: params.content,
74+
assignees: splitCommaList(params.assignees) ?? [],
75+
incident_id: params.incidentId,
76+
}
77+
if (params.dueDate) attributes.due_date = params.dueDate
78+
79+
return { data: { type: 'incident_todos', attributes } }
80+
},
81+
},
82+
83+
transformResponse: async (response: Response) => {
84+
if (!response.ok) {
85+
return {
86+
success: false,
87+
output: { todo: { attributes: {} } },
88+
error: await datadogErrorMessage(response),
89+
}
90+
}
91+
92+
const data = await response.json()
93+
94+
return {
95+
success: true,
96+
output: {
97+
todo: {
98+
id: data.data?.id,
99+
type: data.data?.type,
100+
attributes: data.data?.attributes ?? {},
101+
},
102+
},
103+
}
104+
},
105+
106+
outputs: {
107+
todo: {
108+
type: 'object',
109+
description: 'The created incident todo',
110+
properties: {
111+
id: { type: 'string', description: 'Todo UUID' },
112+
type: { type: 'string', description: 'Resource type (incident_todos)' },
113+
attributes: {
114+
type: 'object',
115+
description: 'Todo attributes',
116+
properties: {
117+
content: { type: 'string', description: 'Task content' },
118+
assignees: { type: 'array', description: 'Assignee handles' },
119+
due_date: { type: 'string', description: 'Due date' },
120+
completed: { type: 'string', description: 'Completion timestamp' },
121+
incident_id: { type: 'string', description: 'UUID of the parent incident' },
122+
},
123+
},
124+
},
125+
},
126+
},
127+
}

apps/sim/tools/datadog/cancel_downtime.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { CancelDowntimeParams, CancelDowntimeResponse } from '@/tools/datadog/types'
2+
import { datadogErrorMessage } from '@/tools/datadog/utils'
23
import type { ToolConfig } from '@/tools/types'
34

45
export const cancelDowntimeTool: ToolConfig<CancelDowntimeParams, CancelDowntimeResponse> = {
@@ -49,13 +50,13 @@ export const cancelDowntimeTool: ToolConfig<CancelDowntimeParams, CancelDowntime
4950

5051
transformResponse: async (response: Response) => {
5152
if (!response.ok && response.status !== 204) {
52-
const errorData = await response.json().catch(() => ({}))
53+
const message = await datadogErrorMessage(response)
5354
return {
5455
success: false,
5556
output: {
5657
success: false,
5758
},
58-
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
59+
error: message,
5960
}
6061
}
6162

0 commit comments

Comments
 (0)