datadog-mcp 5.5.0 → 5.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dist/index.js +1127 -75
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -292550,7 +292550,12 @@ var configSchema = external_exports.object({
|
|
|
292550
292550
|
// Fallback when AI doesn't specify log limit
|
|
292551
292551
|
defaultMetricDataPoints: external_exports.number().default(1e3),
|
|
292552
292552
|
// Fallback for timeseries data points
|
|
292553
|
-
defaultTimeRangeHours: external_exports.number().default(24)
|
|
292553
|
+
defaultTimeRangeHours: external_exports.number().default(24),
|
|
292554
|
+
// Maximum events scanned by the `events.histogram` action before returning a
|
|
292555
|
+
// partial result with `bucketCountIncomplete: true` and `nextCursor`.
|
|
292556
|
+
// Bounds latency on bucketed queries while still giving callers a path to
|
|
292557
|
+
// continue scanning if they want a fully complete histogram.
|
|
292558
|
+
maxEventsForHistogram: external_exports.number().default(5e3)
|
|
292554
292559
|
}).default({}),
|
|
292555
292560
|
features: external_exports.object({
|
|
292556
292561
|
readOnly: external_exports.boolean().default(false),
|
|
@@ -292625,7 +292630,8 @@ function loadConfig() {
|
|
|
292625
292630
|
defaultLimit: Number.parseInt(process.env.MCP_DEFAULT_LIMIT ?? "50", 10),
|
|
292626
292631
|
defaultLogLines: Number.parseInt(process.env.MCP_DEFAULT_LOG_LINES ?? "200", 10),
|
|
292627
292632
|
defaultMetricDataPoints: Number.parseInt(process.env.MCP_DEFAULT_METRIC_POINTS ?? "1000", 10),
|
|
292628
|
-
defaultTimeRangeHours: Number.parseInt(process.env.MCP_DEFAULT_TIME_RANGE ?? "24", 10)
|
|
292633
|
+
defaultTimeRangeHours: Number.parseInt(process.env.MCP_DEFAULT_TIME_RANGE ?? "24", 10),
|
|
292634
|
+
maxEventsForHistogram: Number.parseInt(process.env.MCP_MAX_EVENTS_HISTOGRAM ?? "5000", 10)
|
|
292629
292635
|
},
|
|
292630
292636
|
features: {
|
|
292631
292637
|
readOnly: args.booleans.has("read-only") || process.env.MCP_READ_ONLY === "true",
|
|
@@ -302904,6 +302910,182 @@ function formatDurationNs(ns) {
|
|
|
302904
302910
|
return `${(ns / 6e10).toFixed(2)}m`;
|
|
302905
302911
|
}
|
|
302906
302912
|
|
|
302913
|
+
// src/utils/timezone.ts
|
|
302914
|
+
var ERROR_PREFIX = "EINVALID_TIMEZONE";
|
|
302915
|
+
var WEEKDAY_TO_INDEX = {
|
|
302916
|
+
Sunday: 0,
|
|
302917
|
+
Monday: 1,
|
|
302918
|
+
Tuesday: 2,
|
|
302919
|
+
Wednesday: 3,
|
|
302920
|
+
Thursday: 4,
|
|
302921
|
+
Friday: 5,
|
|
302922
|
+
Saturday: 6
|
|
302923
|
+
};
|
|
302924
|
+
function validateIanaZone(tz) {
|
|
302925
|
+
if (typeof tz !== "string" || tz.length === 0) {
|
|
302926
|
+
throw new Error(
|
|
302927
|
+
`${ERROR_PREFIX}: timezone must be a non-empty IANA identifier (e.g. "UTC", "Europe/Paris", "America/New_York")`
|
|
302928
|
+
);
|
|
302929
|
+
}
|
|
302930
|
+
try {
|
|
302931
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
302932
|
+
return;
|
|
302933
|
+
} catch {
|
|
302934
|
+
const suggestions = suggestZones(tz);
|
|
302935
|
+
const suggestionText = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : "";
|
|
302936
|
+
throw new Error(`${ERROR_PREFIX}: "${tz}" is not a valid IANA timezone.${suggestionText}`);
|
|
302937
|
+
}
|
|
302938
|
+
}
|
|
302939
|
+
function suggestZones(input) {
|
|
302940
|
+
let zones;
|
|
302941
|
+
try {
|
|
302942
|
+
zones = Intl.supportedValuesOf("timeZone");
|
|
302943
|
+
} catch {
|
|
302944
|
+
return [];
|
|
302945
|
+
}
|
|
302946
|
+
const lower = input.toLowerCase();
|
|
302947
|
+
const scored = zones.map((zone) => ({
|
|
302948
|
+
zone,
|
|
302949
|
+
score: scoreZone(zone, lower)
|
|
302950
|
+
}));
|
|
302951
|
+
scored.sort((a, b) => a.score - b.score);
|
|
302952
|
+
return scored.slice(0, 3).map((s) => s.zone);
|
|
302953
|
+
}
|
|
302954
|
+
function scoreZone(zone, lowerInput) {
|
|
302955
|
+
const lowerZone = zone.toLowerCase();
|
|
302956
|
+
const baseDistance = levenshtein(lowerZone, lowerInput);
|
|
302957
|
+
if (lowerZone.includes(lowerInput) || lowerInput.includes(lowerZone)) {
|
|
302958
|
+
return baseDistance - 100;
|
|
302959
|
+
}
|
|
302960
|
+
return baseDistance;
|
|
302961
|
+
}
|
|
302962
|
+
function levenshtein(a, b) {
|
|
302963
|
+
if (a.length === 0) return b.length;
|
|
302964
|
+
if (b.length === 0) return a.length;
|
|
302965
|
+
if (a.length > b.length) {
|
|
302966
|
+
const tmp = a;
|
|
302967
|
+
a = b;
|
|
302968
|
+
b = tmp;
|
|
302969
|
+
}
|
|
302970
|
+
let previous = new Array(a.length + 1);
|
|
302971
|
+
let current = new Array(a.length + 1);
|
|
302972
|
+
for (let i = 0; i <= a.length; i++) previous[i] = i;
|
|
302973
|
+
for (let j = 1; j <= b.length; j++) {
|
|
302974
|
+
current[0] = j;
|
|
302975
|
+
const bChar = b.charCodeAt(j - 1);
|
|
302976
|
+
for (let i = 1; i <= a.length; i++) {
|
|
302977
|
+
const cost = a.charCodeAt(i - 1) === bChar ? 0 : 1;
|
|
302978
|
+
const prevI = previous[i] ?? 0;
|
|
302979
|
+
const currIMinus1 = current[i - 1] ?? 0;
|
|
302980
|
+
const prevIMinus1 = previous[i - 1] ?? 0;
|
|
302981
|
+
current[i] = Math.min(prevI + 1, currIMinus1 + 1, prevIMinus1 + cost);
|
|
302982
|
+
}
|
|
302983
|
+
const tmp = previous;
|
|
302984
|
+
previous = current;
|
|
302985
|
+
current = tmp;
|
|
302986
|
+
}
|
|
302987
|
+
return previous[a.length] ?? 0;
|
|
302988
|
+
}
|
|
302989
|
+
var partsFormatterCache = /* @__PURE__ */ new Map();
|
|
302990
|
+
var offsetFormatterCache = /* @__PURE__ */ new Map();
|
|
302991
|
+
function getPartsFormatter(tz) {
|
|
302992
|
+
const cached2 = partsFormatterCache.get(tz);
|
|
302993
|
+
if (cached2) return cached2;
|
|
302994
|
+
const fmt = new Intl.DateTimeFormat("en-US", {
|
|
302995
|
+
timeZone: tz,
|
|
302996
|
+
hourCycle: "h23",
|
|
302997
|
+
// 0-23, avoids "24" edge case at midnight
|
|
302998
|
+
year: "numeric",
|
|
302999
|
+
month: "2-digit",
|
|
303000
|
+
day: "2-digit",
|
|
303001
|
+
hour: "2-digit",
|
|
303002
|
+
minute: "2-digit",
|
|
303003
|
+
second: "2-digit",
|
|
303004
|
+
weekday: "long"
|
|
303005
|
+
});
|
|
303006
|
+
partsFormatterCache.set(tz, fmt);
|
|
303007
|
+
return fmt;
|
|
303008
|
+
}
|
|
303009
|
+
function getOffsetFormatter(tz) {
|
|
303010
|
+
const cached2 = offsetFormatterCache.get(tz);
|
|
303011
|
+
if (cached2) return cached2;
|
|
303012
|
+
const fmt = new Intl.DateTimeFormat("en-US", {
|
|
303013
|
+
timeZone: tz,
|
|
303014
|
+
timeZoneName: "longOffset"
|
|
303015
|
+
});
|
|
303016
|
+
offsetFormatterCache.set(tz, fmt);
|
|
303017
|
+
return fmt;
|
|
303018
|
+
}
|
|
303019
|
+
function getDateParts(epochMs, tz) {
|
|
303020
|
+
let parts;
|
|
303021
|
+
try {
|
|
303022
|
+
parts = getPartsFormatter(tz).formatToParts(epochMs);
|
|
303023
|
+
} catch {
|
|
303024
|
+
throw new Error(`${ERROR_PREFIX}: "${tz}" is not a valid IANA timezone.`);
|
|
303025
|
+
}
|
|
303026
|
+
const out = {
|
|
303027
|
+
year: "",
|
|
303028
|
+
month: "",
|
|
303029
|
+
day: "",
|
|
303030
|
+
hour: "",
|
|
303031
|
+
minute: "",
|
|
303032
|
+
second: "",
|
|
303033
|
+
weekday: ""
|
|
303034
|
+
};
|
|
303035
|
+
for (const part of parts) {
|
|
303036
|
+
if (part.type === "year") out.year = part.value;
|
|
303037
|
+
else if (part.type === "month") out.month = part.value;
|
|
303038
|
+
else if (part.type === "day") out.day = part.value;
|
|
303039
|
+
else if (part.type === "hour") out.hour = part.value;
|
|
303040
|
+
else if (part.type === "minute") out.minute = part.value;
|
|
303041
|
+
else if (part.type === "second") out.second = part.value;
|
|
303042
|
+
else if (part.type === "weekday") out.weekday = part.value;
|
|
303043
|
+
}
|
|
303044
|
+
return out;
|
|
303045
|
+
}
|
|
303046
|
+
function getOffsetIso(epochMs, tz) {
|
|
303047
|
+
let parts;
|
|
303048
|
+
try {
|
|
303049
|
+
parts = getOffsetFormatter(tz).formatToParts(epochMs);
|
|
303050
|
+
} catch {
|
|
303051
|
+
throw new Error(`${ERROR_PREFIX}: "${tz}" is not a valid IANA timezone.`);
|
|
303052
|
+
}
|
|
303053
|
+
const offsetPart = parts.find((p) => p.type === "timeZoneName");
|
|
303054
|
+
const raw = offsetPart?.value ?? "";
|
|
303055
|
+
if (raw === "GMT" || raw === "GMT+00:00" || raw === "GMT-00:00") {
|
|
303056
|
+
return "Z";
|
|
303057
|
+
}
|
|
303058
|
+
if (raw.startsWith("GMT")) {
|
|
303059
|
+
return raw.slice(3);
|
|
303060
|
+
}
|
|
303061
|
+
return raw;
|
|
303062
|
+
}
|
|
303063
|
+
function formatLocal(epochMs, tz) {
|
|
303064
|
+
validateIanaZone(tz);
|
|
303065
|
+
const parts = getDateParts(epochMs, tz);
|
|
303066
|
+
const offset = getOffsetIso(epochMs, tz);
|
|
303067
|
+
return `${parts.year}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}:${parts.second}${offset}`;
|
|
303068
|
+
}
|
|
303069
|
+
function bucketHourOfDay(epochMs, tz) {
|
|
303070
|
+
validateIanaZone(tz);
|
|
303071
|
+
const parts = getDateParts(epochMs, tz);
|
|
303072
|
+
return Number(parts.hour);
|
|
303073
|
+
}
|
|
303074
|
+
function bucketDayOfWeek(epochMs, tz) {
|
|
303075
|
+
validateIanaZone(tz);
|
|
303076
|
+
const parts = getDateParts(epochMs, tz);
|
|
303077
|
+
const index = WEEKDAY_TO_INDEX[parts.weekday];
|
|
303078
|
+
if (index === void 0) {
|
|
303079
|
+
throw new Error(`${ERROR_PREFIX}: could not resolve weekday for "${parts.weekday}" in "${tz}"`);
|
|
303080
|
+
}
|
|
303081
|
+
return index;
|
|
303082
|
+
}
|
|
303083
|
+
function bucketDayOfMonth(epochMs, tz) {
|
|
303084
|
+
validateIanaZone(tz);
|
|
303085
|
+
const parts = getDateParts(epochMs, tz);
|
|
303086
|
+
return Number(parts.day);
|
|
303087
|
+
}
|
|
303088
|
+
|
|
302907
303089
|
// src/tools/events.ts
|
|
302908
303090
|
var ActionSchema = external_exports.enum([
|
|
302909
303091
|
"list",
|
|
@@ -302914,8 +303096,10 @@ var ActionSchema = external_exports.enum([
|
|
|
302914
303096
|
"top",
|
|
302915
303097
|
"timeseries",
|
|
302916
303098
|
"incidents",
|
|
302917
|
-
"discover"
|
|
303099
|
+
"discover",
|
|
303100
|
+
"histogram"
|
|
302918
303101
|
]);
|
|
303102
|
+
var HistogramBucketBySchema = external_exports.enum(["hour_of_day", "day_of_week", "day_of_month"]);
|
|
302919
303103
|
var InputSchema = {
|
|
302920
303104
|
action: ActionSchema.describe("Action to perform"),
|
|
302921
303105
|
id: external_exports.string().optional().describe("Event ID (for get action)"),
|
|
@@ -302945,8 +303129,35 @@ var InputSchema = {
|
|
|
302945
303129
|
),
|
|
302946
303130
|
maxEvents: external_exports.number().min(1).max(5e3).optional().describe(
|
|
302947
303131
|
"Maximum events to fetch for grouping in top action (default: 5000, max: 5000). Higher = more accurate but slower"
|
|
303132
|
+
),
|
|
303133
|
+
// Monitor transition filter (additive — see requirement 5.2 / monitors action=history)
|
|
303134
|
+
transitionType: external_exports.array(
|
|
303135
|
+
external_exports.enum([
|
|
303136
|
+
"alert",
|
|
303137
|
+
"alert recovery",
|
|
303138
|
+
"warning",
|
|
303139
|
+
"warning recovery",
|
|
303140
|
+
"no data",
|
|
303141
|
+
"no data recovery",
|
|
303142
|
+
"renotify"
|
|
303143
|
+
])
|
|
303144
|
+
).optional().describe(
|
|
303145
|
+
'Filter events by monitor state transition type. When set, restricts results to events with @monitor.transition.transition_type matching any value. Use ["alert","alert recovery"] to count real fires/recoveries and skip renotifies. Empty array is treated as undefined (no filter). For a fires-only count by monitor ID, prefer monitors action=history.'
|
|
303146
|
+
),
|
|
303147
|
+
// Histogram action (Requirement 3): bucket events by local hour/day-of-week/day-of-month.
|
|
303148
|
+
bucket_by: HistogramBucketBySchema.optional().describe(
|
|
303149
|
+
"Bucket dimension for histogram action: hour_of_day (0-23), day_of_week (0=Sun..6=Sat), day_of_month (1-31)."
|
|
303150
|
+
),
|
|
303151
|
+
timezone: external_exports.string().optional().describe(
|
|
303152
|
+
'Optional IANA timezone (e.g. "UTC", "Europe/Paris"). DST-safe. For histogram: controls hour/day bucketing (default: UTC). For search/aggregate/top/incidents read actions: adds sibling *Local ISO 8601 strings (e.g. timestampLocal) next to existing timestamps. Omit for byte-identical legacy shape.'
|
|
302948
303153
|
)
|
|
302949
303154
|
};
|
|
303155
|
+
function annotateEventTimezone(event, tz) {
|
|
303156
|
+
if (!event.timestamp) return event;
|
|
303157
|
+
const ms = new Date(event.timestamp).getTime();
|
|
303158
|
+
if (!Number.isFinite(ms)) return event;
|
|
303159
|
+
return { ...event, timestampLocal: formatLocal(ms, tz) };
|
|
303160
|
+
}
|
|
302950
303161
|
function extractMonitorInfo(title) {
|
|
302951
303162
|
const priorityMatch = title.match(/^\[P(\d+)\]\s*/);
|
|
302952
303163
|
const priority = priorityMatch ? `P${priorityMatch[1]}` : void 0;
|
|
@@ -303133,17 +303344,17 @@ async function listEventsV1(api, params, limits) {
|
|
|
303133
303344
|
tags: params.tags?.join(","),
|
|
303134
303345
|
unaggregated: true
|
|
303135
303346
|
});
|
|
303136
|
-
let
|
|
303347
|
+
let events2 = response.events ?? [];
|
|
303137
303348
|
if (params.query) {
|
|
303138
303349
|
const lowerQuery = params.query.toLowerCase();
|
|
303139
|
-
|
|
303350
|
+
events2 = events2.filter(
|
|
303140
303351
|
(e) => e.title?.toLowerCase().includes(lowerQuery) || e.text?.toLowerCase().includes(lowerQuery)
|
|
303141
303352
|
);
|
|
303142
303353
|
}
|
|
303143
|
-
const result =
|
|
303354
|
+
const result = events2.slice(0, effectiveLimit).map(formatEventV1);
|
|
303144
303355
|
return {
|
|
303145
303356
|
events: result,
|
|
303146
|
-
total:
|
|
303357
|
+
total: events2.length
|
|
303147
303358
|
};
|
|
303148
303359
|
}
|
|
303149
303360
|
async function getEventV1(api, id) {
|
|
@@ -303172,6 +303383,88 @@ async function createEventV1(api, params) {
|
|
|
303172
303383
|
}
|
|
303173
303384
|
};
|
|
303174
303385
|
}
|
|
303386
|
+
var UNINDEXED_ALERT_TAG_PREFIXES = [
|
|
303387
|
+
"monitor_priority",
|
|
303388
|
+
"notification_preset",
|
|
303389
|
+
"monitor_tags",
|
|
303390
|
+
"alert_cycle_key",
|
|
303391
|
+
"monitor_group_key",
|
|
303392
|
+
"notification_method"
|
|
303393
|
+
];
|
|
303394
|
+
var NARROW_TIME_RANGE_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
303395
|
+
function extractTagPrefixes(query, tags) {
|
|
303396
|
+
const prefixes = [];
|
|
303397
|
+
if (query) {
|
|
303398
|
+
const re = /(?:^|\s)([a-zA-Z_][a-zA-Z0-9_]*):[^\s)]+/g;
|
|
303399
|
+
let match;
|
|
303400
|
+
while ((match = re.exec(query)) !== null) {
|
|
303401
|
+
if (match[1]) {
|
|
303402
|
+
prefixes.push(match[1]);
|
|
303403
|
+
}
|
|
303404
|
+
}
|
|
303405
|
+
}
|
|
303406
|
+
if (tags) {
|
|
303407
|
+
for (const tag of tags) {
|
|
303408
|
+
const colonIdx = tag.indexOf(":");
|
|
303409
|
+
if (colonIdx > 0) {
|
|
303410
|
+
prefixes.push(tag.slice(0, colonIdx));
|
|
303411
|
+
}
|
|
303412
|
+
}
|
|
303413
|
+
}
|
|
303414
|
+
return prefixes;
|
|
303415
|
+
}
|
|
303416
|
+
function countNonSourceTerms(query) {
|
|
303417
|
+
if (!query) return 0;
|
|
303418
|
+
const tokens = query.split(/\s+/).filter((t) => t.length > 0 && t !== "OR" && t !== "AND");
|
|
303419
|
+
let nonSource = 0;
|
|
303420
|
+
for (const token of tokens) {
|
|
303421
|
+
const stripped = token.replace(/[()]/g, "");
|
|
303422
|
+
if (stripped.length === 0) continue;
|
|
303423
|
+
if (stripped.startsWith("source:")) continue;
|
|
303424
|
+
nonSource++;
|
|
303425
|
+
}
|
|
303426
|
+
return nonSource;
|
|
303427
|
+
}
|
|
303428
|
+
function computeDiagnostics(input) {
|
|
303429
|
+
const diagnostics = [];
|
|
303430
|
+
const query = input.query ?? "";
|
|
303431
|
+
const queryHasSourceAlert = /(^|\s|\()source:alert(\s|\)|$)/.test(query) || // NOSONAR S5852: anchored alternation, bounded input, no nested quantifiers
|
|
303432
|
+
(input.sources?.includes("alert") ?? false) || (input.tags?.includes("source:alert") ?? false);
|
|
303433
|
+
if (queryHasSourceAlert) {
|
|
303434
|
+
const prefixes = extractTagPrefixes(input.query, input.tags);
|
|
303435
|
+
const unindexedHits = prefixes.filter((p) => UNINDEXED_ALERT_TAG_PREFIXES.includes(p));
|
|
303436
|
+
const uniqueHits = Array.from(new Set(unindexedHits));
|
|
303437
|
+
if (uniqueHits.length > 0) {
|
|
303438
|
+
diagnostics.push({
|
|
303439
|
+
code: "UNINDEXED_TAG_PREFIX",
|
|
303440
|
+
message: `Query filters on tag prefix(es) that Datadog does not index for source:alert events: ${uniqueHits.join(", ")}.`,
|
|
303441
|
+
hint: "Drop these filters and post-filter the results client-side, or aggregate via monitors/get + monitors.list with matching options."
|
|
303442
|
+
});
|
|
303443
|
+
}
|
|
303444
|
+
}
|
|
303445
|
+
if (typeof input.fromMs === "number" && typeof input.toMs === "number" && input.toMs > input.fromMs && input.toMs - input.fromMs < NARROW_TIME_RANGE_THRESHOLD_MS) {
|
|
303446
|
+
diagnostics.push({
|
|
303447
|
+
code: "NARROW_TIME_RANGE",
|
|
303448
|
+
message: "Time range is shorter than 5 minutes; alert events may not have been indexed yet.",
|
|
303449
|
+
hint: "Widen the range (e.g. last 1h) or retry after the indexing delay (~30s) has elapsed."
|
|
303450
|
+
});
|
|
303451
|
+
}
|
|
303452
|
+
if (queryHasSourceAlert) {
|
|
303453
|
+
const otherTerms = countNonSourceTerms(input.query);
|
|
303454
|
+
const otherTags = (input.tags ?? []).filter((t) => !t.startsWith("source:")).length;
|
|
303455
|
+
if (otherTerms === 0 && otherTags === 0) {
|
|
303456
|
+
diagnostics.push({
|
|
303457
|
+
code: "RESTRICTIVE_SOURCE_FILTER",
|
|
303458
|
+
message: "Only source:alert filter was applied; the matching event set may genuinely be empty.",
|
|
303459
|
+
hint: "Use events.aggregate or monitors.list to confirm no alerts fired in the window, or broaden sources (e.g. source:monitor, source:audit)."
|
|
303460
|
+
});
|
|
303461
|
+
}
|
|
303462
|
+
}
|
|
303463
|
+
return diagnostics;
|
|
303464
|
+
}
|
|
303465
|
+
function quoteIfNeeded(value) {
|
|
303466
|
+
return /^[A-Za-z0-9_.-]+$/.test(value) ? value : `"${value}"`;
|
|
303467
|
+
}
|
|
303175
303468
|
function buildEventQuery(params) {
|
|
303176
303469
|
const parts = [];
|
|
303177
303470
|
if (params.query) {
|
|
@@ -303189,9 +303482,16 @@ function buildEventQuery(params) {
|
|
|
303189
303482
|
if (params.priority) {
|
|
303190
303483
|
parts.push(`priority:${params.priority}`);
|
|
303191
303484
|
}
|
|
303485
|
+
if (params.transitionType && params.transitionType.length > 0) {
|
|
303486
|
+
const inner = params.transitionType.map(quoteIfNeeded).join(" OR ");
|
|
303487
|
+
parts.push(`@monitor.transition.transition_type:(${inner})`);
|
|
303488
|
+
}
|
|
303192
303489
|
return parts.length > 0 ? parts.join(" ") : "*";
|
|
303193
303490
|
}
|
|
303194
303491
|
async function searchEventsV2(api, params, limits, site) {
|
|
303492
|
+
if (params.timezone !== void 0) {
|
|
303493
|
+
validateIanaZone(params.timezone);
|
|
303494
|
+
}
|
|
303195
303495
|
const defaultFrom = hoursAgo(limits.defaultTimeRangeHours);
|
|
303196
303496
|
const defaultTo = now();
|
|
303197
303497
|
const [validFrom, validTo] = ensureValidTimeRange(
|
|
@@ -303204,7 +303504,8 @@ async function searchEventsV2(api, params, limits, site) {
|
|
|
303204
303504
|
query: params.query,
|
|
303205
303505
|
sources: params.sources,
|
|
303206
303506
|
tags: params.tags,
|
|
303207
|
-
priority: params.priority
|
|
303507
|
+
priority: params.priority,
|
|
303508
|
+
transitionType: params.transitionType
|
|
303208
303509
|
});
|
|
303209
303510
|
const effectiveLimit = params.limit ?? limits.defaultLimit;
|
|
303210
303511
|
const body = {
|
|
@@ -303220,12 +303521,13 @@ async function searchEventsV2(api, params, limits, site) {
|
|
|
303220
303521
|
}
|
|
303221
303522
|
};
|
|
303222
303523
|
const response = await api.searchEvents({ body });
|
|
303223
|
-
const
|
|
303524
|
+
const rawEvents = (response.data ?? []).map(formatEventV2);
|
|
303525
|
+
const events2 = params.timezone !== void 0 ? rawEvents.map((e) => annotateEventTimezone(e, params.timezone)) : rawEvents;
|
|
303224
303526
|
const nextCursor = response.meta?.page?.after;
|
|
303225
|
-
|
|
303226
|
-
events,
|
|
303527
|
+
const baseResult = {
|
|
303528
|
+
events: events2,
|
|
303227
303529
|
meta: {
|
|
303228
|
-
count:
|
|
303530
|
+
count: events2.length,
|
|
303229
303531
|
query: fullQuery,
|
|
303230
303532
|
from: fromTime,
|
|
303231
303533
|
to: toTime,
|
|
@@ -303233,8 +303535,131 @@ async function searchEventsV2(api, params, limits, site) {
|
|
|
303233
303535
|
datadog_url: buildEventsUrl(fullQuery, validFrom, validTo, site)
|
|
303234
303536
|
}
|
|
303235
303537
|
};
|
|
303538
|
+
if (events2.length === 0) {
|
|
303539
|
+
const diagnostics = computeDiagnostics({
|
|
303540
|
+
query: params.query,
|
|
303541
|
+
tags: params.tags,
|
|
303542
|
+
sources: params.sources,
|
|
303543
|
+
fromMs: validFrom * 1e3,
|
|
303544
|
+
toMs: validTo * 1e3
|
|
303545
|
+
});
|
|
303546
|
+
return { ...baseResult, diagnostics };
|
|
303547
|
+
}
|
|
303548
|
+
return baseResult;
|
|
303549
|
+
}
|
|
303550
|
+
function bucketEvent(epochMs, bucketBy, tz) {
|
|
303551
|
+
switch (bucketBy) {
|
|
303552
|
+
case "hour_of_day":
|
|
303553
|
+
return bucketHourOfDay(epochMs, tz);
|
|
303554
|
+
case "day_of_week":
|
|
303555
|
+
return bucketDayOfWeek(epochMs, tz);
|
|
303556
|
+
case "day_of_month":
|
|
303557
|
+
return bucketDayOfMonth(epochMs, tz);
|
|
303558
|
+
default: {
|
|
303559
|
+
const exhaustive = bucketBy;
|
|
303560
|
+
throw new Error(`Unhandled bucket_by: ${String(exhaustive)}`);
|
|
303561
|
+
}
|
|
303562
|
+
}
|
|
303563
|
+
}
|
|
303564
|
+
function eventEpochMs(event) {
|
|
303565
|
+
const ts = event.attributes?.timestamp;
|
|
303566
|
+
if (ts === void 0 || ts === null) return null;
|
|
303567
|
+
if (ts instanceof Date) {
|
|
303568
|
+
const ms2 = ts.getTime();
|
|
303569
|
+
return Number.isFinite(ms2) ? ms2 : null;
|
|
303570
|
+
}
|
|
303571
|
+
const ms = new Date(String(ts)).getTime();
|
|
303572
|
+
return Number.isFinite(ms) ? ms : null;
|
|
303573
|
+
}
|
|
303574
|
+
async function histogramEventsV2(api, params, limits, site) {
|
|
303575
|
+
const timezone = params.timezone ?? "UTC";
|
|
303576
|
+
validateIanaZone(timezone);
|
|
303577
|
+
const defaultFrom = hoursAgo(limits.defaultTimeRangeHours);
|
|
303578
|
+
const defaultTo = now();
|
|
303579
|
+
const [validFrom, validTo] = ensureValidTimeRange(
|
|
303580
|
+
parseTime(params.from, defaultFrom),
|
|
303581
|
+
parseTime(params.to, defaultTo)
|
|
303582
|
+
);
|
|
303583
|
+
const fromTime = new Date(validFrom * 1e3).toISOString();
|
|
303584
|
+
const toTime = new Date(validTo * 1e3).toISOString();
|
|
303585
|
+
const fullQuery = buildEventQuery({
|
|
303586
|
+
query: params.query,
|
|
303587
|
+
sources: params.sources,
|
|
303588
|
+
tags: params.tags
|
|
303589
|
+
});
|
|
303590
|
+
const cap = limits.maxEventsForHistogram;
|
|
303591
|
+
const perPage = Math.max(1, Math.min(1e3, cap));
|
|
303592
|
+
const buckets = {};
|
|
303593
|
+
let totalEvents = 0;
|
|
303594
|
+
let cursor = params.cursor;
|
|
303595
|
+
let bucketCountIncomplete = false;
|
|
303596
|
+
let exhaustedPages = false;
|
|
303597
|
+
const maxPages = 100;
|
|
303598
|
+
let pageCount = 0;
|
|
303599
|
+
while (pageCount < maxPages) {
|
|
303600
|
+
const body = {
|
|
303601
|
+
filter: {
|
|
303602
|
+
query: fullQuery,
|
|
303603
|
+
from: fromTime,
|
|
303604
|
+
to: toTime
|
|
303605
|
+
},
|
|
303606
|
+
sort: "timestamp",
|
|
303607
|
+
page: {
|
|
303608
|
+
limit: perPage,
|
|
303609
|
+
cursor
|
|
303610
|
+
}
|
|
303611
|
+
};
|
|
303612
|
+
const response = await api.searchEvents({ body });
|
|
303613
|
+
const data = response.data ?? [];
|
|
303614
|
+
const responseCursor = response.meta?.page?.after ?? void 0;
|
|
303615
|
+
for (const event of data) {
|
|
303616
|
+
const epochMs = eventEpochMs(event);
|
|
303617
|
+
if (epochMs === null) continue;
|
|
303618
|
+
const bucket = bucketEvent(epochMs, params.bucket_by, timezone);
|
|
303619
|
+
const key = String(bucket);
|
|
303620
|
+
buckets[key] = (buckets[key] ?? 0) + 1;
|
|
303621
|
+
totalEvents++;
|
|
303622
|
+
if (totalEvents >= cap) {
|
|
303623
|
+
bucketCountIncomplete = true;
|
|
303624
|
+
cursor = responseCursor;
|
|
303625
|
+
break;
|
|
303626
|
+
}
|
|
303627
|
+
}
|
|
303628
|
+
if (bucketCountIncomplete) break;
|
|
303629
|
+
if (data.length === 0 || !responseCursor) {
|
|
303630
|
+
exhaustedPages = true;
|
|
303631
|
+
break;
|
|
303632
|
+
}
|
|
303633
|
+
cursor = responseCursor;
|
|
303634
|
+
pageCount++;
|
|
303635
|
+
}
|
|
303636
|
+
const result = {
|
|
303637
|
+
buckets,
|
|
303638
|
+
bucketBy: params.bucket_by,
|
|
303639
|
+
timezone,
|
|
303640
|
+
totalEvents,
|
|
303641
|
+
meta: {
|
|
303642
|
+
query: fullQuery,
|
|
303643
|
+
from: fromTime,
|
|
303644
|
+
to: toTime,
|
|
303645
|
+
datadog_url: buildEventsUrl(fullQuery, validFrom, validTo, site)
|
|
303646
|
+
}
|
|
303647
|
+
};
|
|
303648
|
+
if (bucketCountIncomplete) {
|
|
303649
|
+
result.bucketCountIncomplete = true;
|
|
303650
|
+
if (cursor) {
|
|
303651
|
+
result.nextCursor = cursor;
|
|
303652
|
+
}
|
|
303653
|
+
} else if (!exhaustedPages && pageCount >= maxPages && cursor) {
|
|
303654
|
+
result.bucketCountIncomplete = true;
|
|
303655
|
+
result.nextCursor = cursor;
|
|
303656
|
+
}
|
|
303657
|
+
return result;
|
|
303236
303658
|
}
|
|
303237
303659
|
async function aggregateEventsV2(api, params, limits, site) {
|
|
303660
|
+
if (params.timezone !== void 0) {
|
|
303661
|
+
validateIanaZone(params.timezone);
|
|
303662
|
+
}
|
|
303238
303663
|
const counts = /* @__PURE__ */ new Map();
|
|
303239
303664
|
const defaultFrom = hoursAgo(limits.defaultTimeRangeHours);
|
|
303240
303665
|
const defaultTo = now();
|
|
@@ -303247,7 +303672,8 @@ async function aggregateEventsV2(api, params, limits, site) {
|
|
|
303247
303672
|
const fullQuery = buildEventQuery({
|
|
303248
303673
|
query: params.query,
|
|
303249
303674
|
sources: params.sources,
|
|
303250
|
-
tags: params.tags
|
|
303675
|
+
tags: params.tags,
|
|
303676
|
+
transitionType: params.transitionType
|
|
303251
303677
|
});
|
|
303252
303678
|
const groupByFields = params.groupBy ?? ["monitor_name"];
|
|
303253
303679
|
const maxEventsToAggregate = 1e4;
|
|
@@ -303270,9 +303696,9 @@ async function aggregateEventsV2(api, params, limits, site) {
|
|
|
303270
303696
|
while (pageCount < maxPages && eventCount < maxEventsToAggregate) {
|
|
303271
303697
|
const pageBody = { ...body, page: { ...body.page, cursor } };
|
|
303272
303698
|
const response = await api.searchEvents({ body: pageBody });
|
|
303273
|
-
const
|
|
303274
|
-
if (
|
|
303275
|
-
for (const event of
|
|
303699
|
+
const events2 = response.data ?? [];
|
|
303700
|
+
if (events2.length === 0) break;
|
|
303701
|
+
for (const event of events2) {
|
|
303276
303702
|
const formatted = formatEventV2(event);
|
|
303277
303703
|
const groupKey = buildGroupKey(formatted, groupByFields);
|
|
303278
303704
|
const existing = counts.get(groupKey);
|
|
@@ -303293,7 +303719,8 @@ async function aggregateEventsV2(api, params, limits, site) {
|
|
|
303293
303719
|
const buckets = sorted.map(([key, data]) => ({
|
|
303294
303720
|
key,
|
|
303295
303721
|
count: data.count,
|
|
303296
|
-
|
|
303722
|
+
// Requirement 4: annotate sample timestamps only when timezone is supplied.
|
|
303723
|
+
sample: params.timezone !== void 0 ? annotateEventTimezone(data.sample, params.timezone) : data.sample
|
|
303297
303724
|
}));
|
|
303298
303725
|
return {
|
|
303299
303726
|
buckets,
|
|
@@ -303310,6 +303737,9 @@ async function aggregateEventsV2(api, params, limits, site) {
|
|
|
303310
303737
|
};
|
|
303311
303738
|
}
|
|
303312
303739
|
async function topEventsV2(api, params, limits, site) {
|
|
303740
|
+
if (params.timezone !== void 0) {
|
|
303741
|
+
validateIanaZone(params.timezone);
|
|
303742
|
+
}
|
|
303313
303743
|
if (params.contextTags !== void 0) {
|
|
303314
303744
|
if (!Array.isArray(params.contextTags)) {
|
|
303315
303745
|
throw new Error("contextTags must be an array");
|
|
@@ -303329,7 +303759,8 @@ async function topEventsV2(api, params, limits, site) {
|
|
|
303329
303759
|
to: params.to,
|
|
303330
303760
|
sources: params.sources,
|
|
303331
303761
|
tags: effectiveTags,
|
|
303332
|
-
limit: params.maxEvents ?? 5e3
|
|
303762
|
+
limit: params.maxEvents ?? 5e3,
|
|
303763
|
+
transitionType: params.transitionType
|
|
303333
303764
|
},
|
|
303334
303765
|
limits,
|
|
303335
303766
|
site
|
|
@@ -303346,7 +303777,7 @@ async function topEventsV2(api, params, limits, site) {
|
|
|
303346
303777
|
value = event.monitorInfo?.name ?? event.title;
|
|
303347
303778
|
} else {
|
|
303348
303779
|
const tag = event.tags.find((t) => t.startsWith(`${field}:`));
|
|
303349
|
-
value = tag ? tag.split(":", 2)[1] : "unknown";
|
|
303780
|
+
value = tag ? tag.split(":", 2)[1] ?? "unknown" : "unknown";
|
|
303350
303781
|
}
|
|
303351
303782
|
groupValues[field] = value;
|
|
303352
303783
|
keyParts.push(`${field}:${value}`);
|
|
@@ -303407,6 +303838,9 @@ function parseIntervalToMs(interval) {
|
|
|
303407
303838
|
return ns ? Math.floor(ns / 1e6) : 36e5;
|
|
303408
303839
|
}
|
|
303409
303840
|
async function timeseriesEventsV2(api, params, limits, site) {
|
|
303841
|
+
if (params.timezone !== void 0) {
|
|
303842
|
+
validateIanaZone(params.timezone);
|
|
303843
|
+
}
|
|
303410
303844
|
const defaultFrom = hoursAgo(limits.defaultTimeRangeHours);
|
|
303411
303845
|
const defaultTo = now();
|
|
303412
303846
|
const [validFrom, validTo] = ensureValidTimeRange(
|
|
@@ -303418,7 +303852,8 @@ async function timeseriesEventsV2(api, params, limits, site) {
|
|
|
303418
303852
|
const fullQuery = buildEventQuery({
|
|
303419
303853
|
query: params.query ?? "source:alert",
|
|
303420
303854
|
sources: params.sources,
|
|
303421
|
-
tags: params.tags
|
|
303855
|
+
tags: params.tags,
|
|
303856
|
+
transitionType: params.transitionType
|
|
303422
303857
|
});
|
|
303423
303858
|
const intervalMs = parseIntervalToMs(params.interval);
|
|
303424
303859
|
const groupByFields = params.groupBy ?? ["monitor_name"];
|
|
@@ -303440,9 +303875,9 @@ async function timeseriesEventsV2(api, params, limits, site) {
|
|
|
303440
303875
|
while (pageCount < maxPages && eventCount < maxEventsToProcess) {
|
|
303441
303876
|
const pageBody = { ...body, page: { ...body.page, cursor } };
|
|
303442
303877
|
const response = await api.searchEvents({ body: pageBody });
|
|
303443
|
-
const
|
|
303444
|
-
if (
|
|
303445
|
-
for (const event of
|
|
303878
|
+
const events2 = response.data ?? [];
|
|
303879
|
+
if (events2.length === 0) break;
|
|
303880
|
+
for (const event of events2) {
|
|
303446
303881
|
const formatted = formatEventV2(event);
|
|
303447
303882
|
const groupKey = buildGroupKey(formatted, groupByFields);
|
|
303448
303883
|
const eventTs = new Date(formatted.timestamp).getTime();
|
|
@@ -303459,6 +303894,7 @@ async function timeseriesEventsV2(api, params, limits, site) {
|
|
|
303459
303894
|
if (!cursor) break;
|
|
303460
303895
|
pageCount++;
|
|
303461
303896
|
}
|
|
303897
|
+
const tz = params.timezone;
|
|
303462
303898
|
const sortedBuckets = [...timeBuckets.entries()].sort((a, b) => a[0] - b[0]).map(([bucketTs, groupCounts]) => {
|
|
303463
303899
|
const counts = {};
|
|
303464
303900
|
let total = 0;
|
|
@@ -303466,12 +303902,16 @@ async function timeseriesEventsV2(api, params, limits, site) {
|
|
|
303466
303902
|
counts[key] = count;
|
|
303467
303903
|
total += count;
|
|
303468
303904
|
}
|
|
303469
|
-
|
|
303905
|
+
const bucket = {
|
|
303470
303906
|
timestamp: new Date(bucketTs).toISOString(),
|
|
303471
303907
|
timestampMs: bucketTs,
|
|
303472
303908
|
counts,
|
|
303473
303909
|
total
|
|
303474
303910
|
};
|
|
303911
|
+
if (tz !== void 0) {
|
|
303912
|
+
bucket.timestampLocal = formatLocal(bucketTs, tz);
|
|
303913
|
+
}
|
|
303914
|
+
return bucket;
|
|
303475
303915
|
});
|
|
303476
303916
|
const effectiveLimit = params.limit ?? 100;
|
|
303477
303917
|
const limitedBuckets = sortedBuckets.slice(0, effectiveLimit);
|
|
@@ -303492,6 +303932,9 @@ async function timeseriesEventsV2(api, params, limits, site) {
|
|
|
303492
303932
|
};
|
|
303493
303933
|
}
|
|
303494
303934
|
async function incidentsEventsV2(api, params, limits, site) {
|
|
303935
|
+
if (params.timezone !== void 0) {
|
|
303936
|
+
validateIanaZone(params.timezone);
|
|
303937
|
+
}
|
|
303495
303938
|
const defaultFrom = hoursAgo(limits.defaultTimeRangeHours);
|
|
303496
303939
|
const defaultTo = now();
|
|
303497
303940
|
const [validFrom, validTo] = ensureValidTimeRange(
|
|
@@ -303503,7 +303946,8 @@ async function incidentsEventsV2(api, params, limits, site) {
|
|
|
303503
303946
|
const fullQuery = buildEventQuery({
|
|
303504
303947
|
query: params.query ?? "source:alert",
|
|
303505
303948
|
sources: params.sources,
|
|
303506
|
-
tags: params.tags
|
|
303949
|
+
tags: params.tags,
|
|
303950
|
+
transitionType: params.transitionType
|
|
303507
303951
|
});
|
|
303508
303952
|
const dedupeWindowNs = parseDurationToNs(params.dedupeWindow ?? "5m");
|
|
303509
303953
|
const dedupeWindowMs = dedupeWindowNs ? Math.floor(dedupeWindowNs / 1e6) : 3e5;
|
|
@@ -303525,9 +303969,9 @@ async function incidentsEventsV2(api, params, limits, site) {
|
|
|
303525
303969
|
while (pageCount < maxPages && eventCount < maxEventsToProcess) {
|
|
303526
303970
|
const pageBody = { ...body, page: { ...body.page, cursor } };
|
|
303527
303971
|
const response = await api.searchEvents({ body: pageBody });
|
|
303528
|
-
const
|
|
303529
|
-
if (
|
|
303530
|
-
for (const event of
|
|
303972
|
+
const events2 = response.data ?? [];
|
|
303973
|
+
if (events2.length === 0) break;
|
|
303974
|
+
for (const event of events2) {
|
|
303531
303975
|
const formatted = formatEventV2(event);
|
|
303532
303976
|
const monitorName = formatted.monitorInfo?.name ?? formatted.title;
|
|
303533
303977
|
if (!monitorName) {
|
|
@@ -303595,6 +304039,7 @@ async function incidentsEventsV2(api, params, limits, site) {
|
|
|
303595
304039
|
if (!cursor) break;
|
|
303596
304040
|
pageCount++;
|
|
303597
304041
|
}
|
|
304042
|
+
const tz = params.timezone;
|
|
303598
304043
|
const incidentList = [...incidents.values()].map((inc) => {
|
|
303599
304044
|
let duration3;
|
|
303600
304045
|
if (inc.recoveredAt) {
|
|
@@ -303607,7 +304052,7 @@ async function incidentsEventsV2(api, params, limits, site) {
|
|
|
303607
304052
|
duration3 = `${(durationMs / 36e5).toFixed(1)}h`;
|
|
303608
304053
|
}
|
|
303609
304054
|
}
|
|
303610
|
-
|
|
304055
|
+
const base = {
|
|
303611
304056
|
monitorName: inc.monitorName,
|
|
303612
304057
|
firstTrigger: inc.firstTrigger.toISOString(),
|
|
303613
304058
|
lastTrigger: inc.lastTrigger.toISOString(),
|
|
@@ -303615,8 +304060,17 @@ async function incidentsEventsV2(api, params, limits, site) {
|
|
|
303615
304060
|
recovered: inc.recovered,
|
|
303616
304061
|
recoveredAt: inc.recoveredAt?.toISOString(),
|
|
303617
304062
|
duration: duration3,
|
|
303618
|
-
|
|
304063
|
+
// Requirement 4: annotate the nested sample event timestamp when tz is supplied.
|
|
304064
|
+
sample: tz !== void 0 ? annotateEventTimezone(inc.sample, tz) : inc.sample
|
|
303619
304065
|
};
|
|
304066
|
+
if (tz !== void 0) {
|
|
304067
|
+
base.firstTriggerLocal = formatLocal(inc.firstTrigger.getTime(), tz);
|
|
304068
|
+
base.lastTriggerLocal = formatLocal(inc.lastTrigger.getTime(), tz);
|
|
304069
|
+
if (inc.recoveredAt) {
|
|
304070
|
+
base.recoveredAtLocal = formatLocal(inc.recoveredAt.getTime(), tz);
|
|
304071
|
+
}
|
|
304072
|
+
}
|
|
304073
|
+
return base;
|
|
303620
304074
|
});
|
|
303621
304075
|
incidentList.sort(
|
|
303622
304076
|
(a, b) => new Date(b.firstTrigger).getTime() - new Date(a.firstTrigger).getTime()
|
|
@@ -303639,15 +304093,15 @@ async function incidentsEventsV2(api, params, limits, site) {
|
|
|
303639
304093
|
}
|
|
303640
304094
|
};
|
|
303641
304095
|
}
|
|
303642
|
-
async function enrichWithMonitorMetadata(
|
|
304096
|
+
async function enrichWithMonitorMetadata(events2, monitorsApi) {
|
|
303643
304097
|
const monitorIds = /* @__PURE__ */ new Set();
|
|
303644
|
-
for (const event of
|
|
304098
|
+
for (const event of events2) {
|
|
303645
304099
|
if (event.monitorId) {
|
|
303646
304100
|
monitorIds.add(event.monitorId);
|
|
303647
304101
|
}
|
|
303648
304102
|
}
|
|
303649
304103
|
if (monitorIds.size === 0) {
|
|
303650
|
-
return
|
|
304104
|
+
return events2;
|
|
303651
304105
|
}
|
|
303652
304106
|
const monitorCache = /* @__PURE__ */ new Map();
|
|
303653
304107
|
try {
|
|
@@ -303661,9 +304115,9 @@ async function enrichWithMonitorMetadata(events, monitorsApi) {
|
|
|
303661
304115
|
}
|
|
303662
304116
|
}
|
|
303663
304117
|
} catch {
|
|
303664
|
-
return
|
|
304118
|
+
return events2;
|
|
303665
304119
|
}
|
|
303666
|
-
return
|
|
304120
|
+
return events2.map((event) => {
|
|
303667
304121
|
const enriched = { ...event };
|
|
303668
304122
|
if (event.monitorId) {
|
|
303669
304123
|
const monitor = monitorCache.get(event.monitorId);
|
|
@@ -303688,18 +304142,26 @@ async function enrichWithMonitorMetadata(events, monitorsApi) {
|
|
|
303688
304142
|
function registerEventsTool(server, apiV1, apiV2, monitorsApi, limits, readOnly = false, site = "datadoghq.com") {
|
|
303689
304143
|
server.tool(
|
|
303690
304144
|
"events",
|
|
303691
|
-
`Track Datadog events. Actions: list, get, create, search, aggregate, top, timeseries, incidents, discover.
|
|
304145
|
+
`Track Datadog events. Actions: list, get, create, search, aggregate, top, timeseries, incidents, discover, histogram.
|
|
303692
304146
|
For monitor alerts, use tags: ["source:alert"].
|
|
303693
304147
|
|
|
304148
|
+
IMPORTANT \u2014 re-evaluation vs transition:
|
|
304149
|
+
- source:alert events INCLUDE renotifies and re-evaluations (every Datadog re-evaluation of an alerting monitor emits an event). A "how many times did monitor X fire" question answered with source:alert alone over-counts.
|
|
304150
|
+
- To restrict to real state transitions, pass transitionType (e.g. ["alert","alert recovery"]). This appends @monitor.transition.transition_type:(...) to the query and matches the design's live investigation.
|
|
304151
|
+
- For a fires-only numeric count rooted in a single monitor ID, prefer the higher-level primitive monitors action=history \u2014 it returns {transitions, count, meta} with the same filter applied for you.
|
|
304152
|
+
|
|
304153
|
+
transitionType: Optional array of monitor transition types (alert, alert recovery, warning, warning recovery, no data, no data recovery, renotify). Empty array is treated as undefined.
|
|
303694
304154
|
top: Generic event grouping by any fields (groupBy parameter). Returns groups ranked by count with optional context breakdown.
|
|
303695
304155
|
- Example: {groupBy: ["service"], message: "...", service: "api", total_count: 50, by_context: [{context: "queue:X", count: 30}]}
|
|
303696
304156
|
- Use for deployments, configs, custom events, or monitor alerts
|
|
303697
304157
|
- Returns "message" field (event title), NOT monitor name (use monitors tool for real names)
|
|
304158
|
+
- total_count includes renotifies when source:alert is used without transitionType \u2014 see monitors action=history for fires-only counts
|
|
303698
304159
|
discover: Returns available tag prefixes from events.
|
|
303699
304160
|
aggregate: Custom groupBy, returns pipe-delimited keys.
|
|
303700
304161
|
search: Full event details.
|
|
303701
304162
|
timeseries: Time-bucketed trends with interval.
|
|
303702
|
-
incidents: Deduplicate alerts with dedupeWindow
|
|
304163
|
+
incidents: Deduplicate alerts with dedupeWindow.
|
|
304164
|
+
histogram: Bucket events by local hour_of_day / day_of_week / day_of_month in the requested IANA timezone (DST-safe). Pass bucket_by (required) and optional timezone (default UTC) and cursor (for continuation). Caps at limits.maxEventsForHistogram (default 5000); when reached returns bucketCountIncomplete:true + nextCursor.`,
|
|
303703
304165
|
InputSchema,
|
|
303704
304166
|
async ({
|
|
303705
304167
|
action,
|
|
@@ -303720,7 +304182,10 @@ incidents: Deduplicate alerts with dedupeWindow.`,
|
|
|
303720
304182
|
dedupeWindow,
|
|
303721
304183
|
enrich,
|
|
303722
304184
|
contextTags,
|
|
303723
|
-
maxEvents
|
|
304185
|
+
maxEvents,
|
|
304186
|
+
transitionType,
|
|
304187
|
+
bucket_by,
|
|
304188
|
+
timezone
|
|
303724
304189
|
}) => {
|
|
303725
304190
|
try {
|
|
303726
304191
|
checkReadOnly(action, readOnly);
|
|
@@ -303769,7 +304234,9 @@ incidents: Deduplicate alerts with dedupeWindow.`,
|
|
|
303769
304234
|
tags,
|
|
303770
304235
|
priority,
|
|
303771
304236
|
limit,
|
|
303772
|
-
cursor
|
|
304237
|
+
cursor,
|
|
304238
|
+
transitionType,
|
|
304239
|
+
timezone
|
|
303773
304240
|
},
|
|
303774
304241
|
limits,
|
|
303775
304242
|
site
|
|
@@ -303791,7 +304258,9 @@ incidents: Deduplicate alerts with dedupeWindow.`,
|
|
|
303791
304258
|
sources,
|
|
303792
304259
|
tags,
|
|
303793
304260
|
groupBy,
|
|
303794
|
-
limit
|
|
304261
|
+
limit,
|
|
304262
|
+
transitionType,
|
|
304263
|
+
timezone
|
|
303795
304264
|
},
|
|
303796
304265
|
limits,
|
|
303797
304266
|
site
|
|
@@ -303810,7 +304279,9 @@ incidents: Deduplicate alerts with dedupeWindow.`,
|
|
|
303810
304279
|
limit,
|
|
303811
304280
|
groupBy,
|
|
303812
304281
|
contextTags,
|
|
303813
|
-
maxEvents
|
|
304282
|
+
maxEvents,
|
|
304283
|
+
transitionType,
|
|
304284
|
+
timezone
|
|
303814
304285
|
},
|
|
303815
304286
|
limits,
|
|
303816
304287
|
site
|
|
@@ -303843,7 +304314,9 @@ incidents: Deduplicate alerts with dedupeWindow.`,
|
|
|
303843
304314
|
tags,
|
|
303844
304315
|
groupBy,
|
|
303845
304316
|
interval,
|
|
303846
|
-
limit
|
|
304317
|
+
limit,
|
|
304318
|
+
transitionType,
|
|
304319
|
+
timezone
|
|
303847
304320
|
},
|
|
303848
304321
|
limits,
|
|
303849
304322
|
site
|
|
@@ -303860,12 +304333,34 @@ incidents: Deduplicate alerts with dedupeWindow.`,
|
|
|
303860
304333
|
sources,
|
|
303861
304334
|
tags,
|
|
303862
304335
|
dedupeWindow,
|
|
303863
|
-
limit
|
|
304336
|
+
limit,
|
|
304337
|
+
transitionType,
|
|
304338
|
+
timezone
|
|
303864
304339
|
},
|
|
303865
304340
|
limits,
|
|
303866
304341
|
site
|
|
303867
304342
|
)
|
|
303868
304343
|
);
|
|
304344
|
+
case "histogram": {
|
|
304345
|
+
const histogramBucketBy = requireParam(bucket_by, "bucket_by", "histogram");
|
|
304346
|
+
return toolResult(
|
|
304347
|
+
await histogramEventsV2(
|
|
304348
|
+
apiV2,
|
|
304349
|
+
{
|
|
304350
|
+
query,
|
|
304351
|
+
from,
|
|
304352
|
+
to,
|
|
304353
|
+
sources,
|
|
304354
|
+
tags,
|
|
304355
|
+
bucket_by: histogramBucketBy,
|
|
304356
|
+
timezone,
|
|
304357
|
+
cursor
|
|
304358
|
+
},
|
|
304359
|
+
limits,
|
|
304360
|
+
site
|
|
304361
|
+
)
|
|
304362
|
+
);
|
|
304363
|
+
}
|
|
303869
304364
|
default:
|
|
303870
304365
|
throw new Error(`Unknown action: ${action}`);
|
|
303871
304366
|
}
|
|
@@ -303876,6 +304371,177 @@ incidents: Deduplicate alerts with dedupeWindow.`,
|
|
|
303876
304371
|
);
|
|
303877
304372
|
}
|
|
303878
304373
|
|
|
304374
|
+
// src/utils/templatePreview.ts
|
|
304375
|
+
var SUPPORTED_CONDITIONALS = [
|
|
304376
|
+
"is_alert",
|
|
304377
|
+
"is_warning",
|
|
304378
|
+
"is_no_data",
|
|
304379
|
+
"is_recovery",
|
|
304380
|
+
"is_alert_to_warning",
|
|
304381
|
+
"is_warning_to_alert"
|
|
304382
|
+
];
|
|
304383
|
+
var SUPPORTED_SET = new Set(SUPPORTED_CONDITIONALS);
|
|
304384
|
+
function unsupportedSyntaxError(detail) {
|
|
304385
|
+
const supportedList = SUPPORTED_CONDITIONALS.join(", ");
|
|
304386
|
+
return new Error(
|
|
304387
|
+
`EUNSUPPORTED_TEMPLATE_SYNTAX: ${detail}. Supported syntax is {{variable.name}} and conditionals {{#name}}...{{/name}} / {{^name}}...{{/name}} where name is one of: ${supportedList}. Loops ({{#each ...}}) and partials ({{> ...}}) are not supported.`
|
|
304388
|
+
);
|
|
304389
|
+
}
|
|
304390
|
+
function lookupVariable(path, variables) {
|
|
304391
|
+
if (Object.prototype.hasOwnProperty.call(variables, path)) {
|
|
304392
|
+
const value = variables[path];
|
|
304393
|
+
return value === void 0 || value === null ? void 0 : String(value);
|
|
304394
|
+
}
|
|
304395
|
+
const segments = path.split(".");
|
|
304396
|
+
let cursor = variables;
|
|
304397
|
+
for (const segment of segments) {
|
|
304398
|
+
if (cursor === null || cursor === void 0) {
|
|
304399
|
+
return void 0;
|
|
304400
|
+
}
|
|
304401
|
+
if (typeof cursor !== "object") {
|
|
304402
|
+
return void 0;
|
|
304403
|
+
}
|
|
304404
|
+
const record2 = cursor;
|
|
304405
|
+
if (!Object.prototype.hasOwnProperty.call(record2, segment)) {
|
|
304406
|
+
return void 0;
|
|
304407
|
+
}
|
|
304408
|
+
cursor = record2[segment];
|
|
304409
|
+
}
|
|
304410
|
+
if (cursor === void 0 || cursor === null) {
|
|
304411
|
+
return void 0;
|
|
304412
|
+
}
|
|
304413
|
+
if (typeof cursor === "object") {
|
|
304414
|
+
return void 0;
|
|
304415
|
+
}
|
|
304416
|
+
return String(cursor);
|
|
304417
|
+
}
|
|
304418
|
+
var TAG_REGEX = /\{\{\s*([#^/>])?\s*([^}]*?)\s*\}\}/g;
|
|
304419
|
+
function parseBlocks(template) {
|
|
304420
|
+
const stack = [{ children: [] }];
|
|
304421
|
+
let cursor = 0;
|
|
304422
|
+
TAG_REGEX.lastIndex = 0;
|
|
304423
|
+
let match;
|
|
304424
|
+
while ((match = TAG_REGEX.exec(template)) !== null) {
|
|
304425
|
+
const [fullTag, prefix, rawName] = match;
|
|
304426
|
+
const tagStart = match.index;
|
|
304427
|
+
const name = rawName?.trim() ?? "";
|
|
304428
|
+
if (tagStart > cursor) {
|
|
304429
|
+
const top = stack[stack.length - 1];
|
|
304430
|
+
if (top) {
|
|
304431
|
+
top.children.push({ kind: "literal", text: template.slice(cursor, tagStart) });
|
|
304432
|
+
}
|
|
304433
|
+
}
|
|
304434
|
+
if (prefix === ">") {
|
|
304435
|
+
throw unsupportedSyntaxError(`partials are not supported (found {{> ${name}}})`);
|
|
304436
|
+
}
|
|
304437
|
+
if (prefix === "#" || prefix === "^") {
|
|
304438
|
+
if (name.startsWith("each") || /\s/.test(name)) {
|
|
304439
|
+
throw unsupportedSyntaxError(`loops are not supported (found {{${prefix}${name}}})`);
|
|
304440
|
+
}
|
|
304441
|
+
if (!SUPPORTED_SET.has(name)) {
|
|
304442
|
+
throw unsupportedSyntaxError(`unknown conditional '${name}' in {{${prefix}${name}}}`);
|
|
304443
|
+
}
|
|
304444
|
+
stack.push({
|
|
304445
|
+
children: [],
|
|
304446
|
+
closer: { conditional: name, negated: prefix === "^" }
|
|
304447
|
+
});
|
|
304448
|
+
} else if (prefix === "/") {
|
|
304449
|
+
const frame = stack.pop();
|
|
304450
|
+
if (!frame || !frame.closer) {
|
|
304451
|
+
throw unsupportedSyntaxError(`unmatched closing tag {{/${name}}}`);
|
|
304452
|
+
}
|
|
304453
|
+
if (frame.closer.conditional !== name) {
|
|
304454
|
+
throw unsupportedSyntaxError(
|
|
304455
|
+
`mismatched closing tag {{/${name}}} (expected {{/${frame.closer.conditional}}})`
|
|
304456
|
+
);
|
|
304457
|
+
}
|
|
304458
|
+
const parent = stack[stack.length - 1];
|
|
304459
|
+
if (!parent) {
|
|
304460
|
+
throw unsupportedSyntaxError("block stack underflow while closing tag");
|
|
304461
|
+
}
|
|
304462
|
+
parent.children.push({
|
|
304463
|
+
kind: "block",
|
|
304464
|
+
conditional: frame.closer.conditional,
|
|
304465
|
+
negated: frame.closer.negated,
|
|
304466
|
+
children: frame.children
|
|
304467
|
+
});
|
|
304468
|
+
} else {
|
|
304469
|
+
const top = stack[stack.length - 1];
|
|
304470
|
+
if (top) {
|
|
304471
|
+
top.children.push({ kind: "literal", text: fullTag });
|
|
304472
|
+
}
|
|
304473
|
+
}
|
|
304474
|
+
cursor = tagStart + fullTag.length;
|
|
304475
|
+
}
|
|
304476
|
+
if (cursor < template.length) {
|
|
304477
|
+
const top = stack[stack.length - 1];
|
|
304478
|
+
if (top) {
|
|
304479
|
+
top.children.push({ kind: "literal", text: template.slice(cursor) });
|
|
304480
|
+
}
|
|
304481
|
+
}
|
|
304482
|
+
if (stack.length !== 1) {
|
|
304483
|
+
const open = stack[stack.length - 1]?.closer?.conditional;
|
|
304484
|
+
throw unsupportedSyntaxError(`unclosed conditional block ${open ? `{{#${open}}}` : ""}`);
|
|
304485
|
+
}
|
|
304486
|
+
const root = stack[0];
|
|
304487
|
+
if (!root) {
|
|
304488
|
+
throw unsupportedSyntaxError("parser produced no root frame");
|
|
304489
|
+
}
|
|
304490
|
+
return root.children;
|
|
304491
|
+
}
|
|
304492
|
+
function renderBlocks(tokens, conditionals, resolved) {
|
|
304493
|
+
let out = "";
|
|
304494
|
+
for (const token of tokens) {
|
|
304495
|
+
if (token.kind === "literal") {
|
|
304496
|
+
out += token.text;
|
|
304497
|
+
continue;
|
|
304498
|
+
}
|
|
304499
|
+
const flag = conditionals[token.conditional] ?? false;
|
|
304500
|
+
resolved[token.conditional] = flag;
|
|
304501
|
+
const include = token.negated ? !flag : flag;
|
|
304502
|
+
if (include) {
|
|
304503
|
+
out += renderBlocks(token.children, conditionals, resolved);
|
|
304504
|
+
} else {
|
|
304505
|
+
renderBlocks(token.children, conditionals, resolved);
|
|
304506
|
+
}
|
|
304507
|
+
}
|
|
304508
|
+
return out;
|
|
304509
|
+
}
|
|
304510
|
+
var VARIABLE_TAG_REGEX = /\{\{\s*([^#^/>\s][^}]*?)\s*\}\}/g;
|
|
304511
|
+
function substituteVariables(text, variables) {
|
|
304512
|
+
const usedSet = /* @__PURE__ */ new Set();
|
|
304513
|
+
const missingSet = /* @__PURE__ */ new Set();
|
|
304514
|
+
const rendered = text.replace(VARIABLE_TAG_REGEX, (_match, captured) => {
|
|
304515
|
+
const name = captured.trim();
|
|
304516
|
+
const value = lookupVariable(name, variables);
|
|
304517
|
+
if (value === void 0) {
|
|
304518
|
+
missingSet.add(name);
|
|
304519
|
+
return `{{undefined:${name}}}`;
|
|
304520
|
+
}
|
|
304521
|
+
usedSet.add(name);
|
|
304522
|
+
return value;
|
|
304523
|
+
});
|
|
304524
|
+
return {
|
|
304525
|
+
rendered,
|
|
304526
|
+
used: [...usedSet],
|
|
304527
|
+
missing: [...missingSet]
|
|
304528
|
+
};
|
|
304529
|
+
}
|
|
304530
|
+
function renderMonitorTemplate(template, context) {
|
|
304531
|
+
const variables = context.variables ?? {};
|
|
304532
|
+
const conditionals = context.conditionals ?? {};
|
|
304533
|
+
const tree = parseBlocks(template);
|
|
304534
|
+
const conditionalsResolved = {};
|
|
304535
|
+
const afterConditionals = renderBlocks(tree, conditionals, conditionalsResolved);
|
|
304536
|
+
const { rendered, used, missing } = substituteVariables(afterConditionals, variables);
|
|
304537
|
+
return {
|
|
304538
|
+
rendered,
|
|
304539
|
+
variablesUsed: used,
|
|
304540
|
+
variablesMissing: missing,
|
|
304541
|
+
conditionalsResolved
|
|
304542
|
+
};
|
|
304543
|
+
}
|
|
304544
|
+
|
|
303879
304545
|
// src/tools/monitors.ts
|
|
303880
304546
|
var ActionSchema2 = external_exports.enum([
|
|
303881
304547
|
"list",
|
|
@@ -303886,8 +304552,15 @@ var ActionSchema2 = external_exports.enum([
|
|
|
303886
304552
|
"delete",
|
|
303887
304553
|
"mute",
|
|
303888
304554
|
"unmute",
|
|
303889
|
-
"top"
|
|
304555
|
+
"top",
|
|
304556
|
+
"history",
|
|
304557
|
+
"preview",
|
|
304558
|
+
"test_notification"
|
|
303890
304559
|
]);
|
|
304560
|
+
var PreviewContextSchema = external_exports.object({
|
|
304561
|
+
variables: external_exports.record(external_exports.unknown()).optional(),
|
|
304562
|
+
conditionals: external_exports.record(external_exports.enum(SUPPORTED_CONDITIONALS), external_exports.boolean()).optional()
|
|
304563
|
+
}).optional().describe("Substitution context for monitors.preview (variables + conditionals).");
|
|
303891
304564
|
var InputSchema2 = {
|
|
303892
304565
|
action: ActionSchema2.describe("Action to perform"),
|
|
303893
304566
|
id: external_exports.string().optional().describe("Monitor ID (required for get/update/delete/mute/unmute)"),
|
|
@@ -303899,15 +304572,44 @@ var InputSchema2 = {
|
|
|
303899
304572
|
),
|
|
303900
304573
|
limit: external_exports.number().min(1).optional().describe("Maximum number of monitors to return (default: 50)"),
|
|
303901
304574
|
config: external_exports.record(external_exports.unknown()).optional().describe("Monitor configuration (for create/update)"),
|
|
303902
|
-
message: external_exports.string().optional().describe(
|
|
304575
|
+
message: external_exports.string().optional().describe(
|
|
304576
|
+
"Mute message (for mute action) OR inline template source for the preview action. For preview, supply either this inline string or `monitor_id` (or the existing `id` field) so the action can load the monitor message via getMonitor."
|
|
304577
|
+
),
|
|
303903
304578
|
end: external_exports.number().optional().describe("Mute end timestamp (for mute action)"),
|
|
304579
|
+
monitor_id: external_exports.number().optional().describe(
|
|
304580
|
+
"Numeric monitor ID used by the preview action when no inline `message` is supplied. Equivalent to passing the existing `id` field as a numeric string."
|
|
304581
|
+
),
|
|
304582
|
+
context: PreviewContextSchema,
|
|
303904
304583
|
// Top action parameters
|
|
303905
304584
|
from: external_exports.string().optional().describe('Start time (ISO 8601, relative like "1h", or Unix timestamp)'),
|
|
303906
304585
|
to: external_exports.string().optional().describe('End time (ISO 8601, relative like "1h", or Unix timestamp)'),
|
|
303907
304586
|
contextTags: external_exports.array(external_exports.string()).optional().describe(
|
|
303908
304587
|
"Tag prefixes for context breakdown in top action (default: queue, service, ingress, pod_name, kube_namespace, kube_container_name)"
|
|
303909
304588
|
),
|
|
303910
|
-
maxEvents: external_exports.number().min(1).max(5e3).optional().describe("Maximum events to fetch for top action (default: 5000, max: 5000)")
|
|
304589
|
+
maxEvents: external_exports.number().min(1).max(5e3).optional().describe("Maximum events to fetch for top action (default: 5000, max: 5000)"),
|
|
304590
|
+
// History action parameters
|
|
304591
|
+
transitionType: external_exports.array(
|
|
304592
|
+
external_exports.enum([
|
|
304593
|
+
"alert",
|
|
304594
|
+
"alert recovery",
|
|
304595
|
+
"warning",
|
|
304596
|
+
"warning recovery",
|
|
304597
|
+
"no data",
|
|
304598
|
+
"no data recovery",
|
|
304599
|
+
"renotify"
|
|
304600
|
+
])
|
|
304601
|
+
).optional().describe(
|
|
304602
|
+
'For history action: filter by monitor state transition types. Default: ["alert","alert recovery"] (real fires + recoveries, excludes renotifies). Pass ["alert"] for fires only, or include "renotify" for full chronological audit.'
|
|
304603
|
+
),
|
|
304604
|
+
group: external_exports.string().optional().describe(
|
|
304605
|
+
'For history action: filter transitions to a specific multi-alert monitor group (e.g., "pod_name:foo,kube_namespace:bar"). Optional; omit for all groups.'
|
|
304606
|
+
),
|
|
304607
|
+
dry_run: external_exports.boolean().optional().describe(
|
|
304608
|
+
"When create + dry_run=true, validate the monitor body via POST /api/v1/monitor/validate without creating it. Allowed under --read-only because no monitor is created. Returns { valid, dryRun, monitor }. 400 responses surface verbatim like a failed create."
|
|
304609
|
+
),
|
|
304610
|
+
timezone: external_exports.string().optional().describe(
|
|
304611
|
+
'Optional IANA timezone (e.g. "UTC", "Europe/Paris"). When supplied on get/list, the response adds sibling createdLocal/modifiedLocal ISO 8601 strings next to created/modified. Omit for byte-identical legacy shape. Invalid zones return EINVALID_TIMEZONE.'
|
|
304612
|
+
)
|
|
303911
304613
|
};
|
|
303912
304614
|
var MonitorThresholdsSchema = external_exports.object({
|
|
303913
304615
|
critical: external_exports.number().optional(),
|
|
@@ -304001,6 +304703,18 @@ function summarizeZodIssue(error2) {
|
|
|
304001
304703
|
const expected = issue2.code === "invalid_type" && "expected" in issue2 ? `expected ${String(issue2.expected)}` : issue2.message;
|
|
304002
304704
|
return `${path}: ${expected}`;
|
|
304003
304705
|
}
|
|
304706
|
+
function annotateMonitorTimezone(monitor, tz) {
|
|
304707
|
+
const annotated = { ...monitor };
|
|
304708
|
+
if (monitor.created) {
|
|
304709
|
+
const ms = new Date(monitor.created).getTime();
|
|
304710
|
+
if (Number.isFinite(ms)) annotated.createdLocal = formatLocal(ms, tz);
|
|
304711
|
+
}
|
|
304712
|
+
if (monitor.modified) {
|
|
304713
|
+
const ms = new Date(monitor.modified).getTime();
|
|
304714
|
+
if (Number.isFinite(ms)) annotated.modifiedLocal = formatLocal(ms, tz);
|
|
304715
|
+
}
|
|
304716
|
+
return annotated;
|
|
304717
|
+
}
|
|
304004
304718
|
function formatMonitor(m, site = "datadoghq.com") {
|
|
304005
304719
|
const monitorId = m.id ?? 0;
|
|
304006
304720
|
return {
|
|
@@ -304032,14 +304746,165 @@ function formatMonitorDetail(m, site = "datadoghq.com") {
|
|
|
304032
304746
|
}
|
|
304033
304747
|
return detail;
|
|
304034
304748
|
}
|
|
304035
|
-
|
|
304749
|
+
var DEFAULT_HISTORY_TRANSITION_TYPES = [
|
|
304750
|
+
"alert",
|
|
304751
|
+
"alert recovery"
|
|
304752
|
+
];
|
|
304753
|
+
function quoteIfNeeded2(value) {
|
|
304754
|
+
return /^[A-Za-z0-9_.-]+$/.test(value) ? value : `"${value}"`;
|
|
304755
|
+
}
|
|
304756
|
+
function buildMonitorHistoryQuery(params) {
|
|
304757
|
+
const parts = ["source:alert", `@monitor.id:${params.monitorId}`];
|
|
304758
|
+
const transitionTypes = params.transitionType && params.transitionType.length > 0 ? params.transitionType : void 0;
|
|
304759
|
+
if (transitionTypes) {
|
|
304760
|
+
const inner = transitionTypes.map(quoteIfNeeded2).join(" OR ");
|
|
304761
|
+
parts.push(`@monitor.transition.transition_type:(${inner})`);
|
|
304762
|
+
}
|
|
304763
|
+
if (params.group && params.group.length > 0) {
|
|
304764
|
+
const escaped = params.group.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
304765
|
+
parts.push(`@monitor.groups:"${escaped}"`);
|
|
304766
|
+
}
|
|
304767
|
+
return parts.join(" ");
|
|
304768
|
+
}
|
|
304769
|
+
function isMonitorState(value) {
|
|
304770
|
+
return value === "Alert" || value === "Warn" || value === "OK" || value === "No Data";
|
|
304771
|
+
}
|
|
304772
|
+
function isTransitionType(value) {
|
|
304773
|
+
return value === "alert" || value === "alert recovery" || value === "warning" || value === "warning recovery" || value === "no data" || value === "no data recovery" || value === "renotify";
|
|
304774
|
+
}
|
|
304775
|
+
function extractTimestamp(outer, inner) {
|
|
304776
|
+
const outerTs = outer.timestamp;
|
|
304777
|
+
if (outerTs instanceof Date) {
|
|
304778
|
+
return outerTs.toISOString();
|
|
304779
|
+
}
|
|
304780
|
+
if (typeof outerTs === "string" && outerTs.length > 0) {
|
|
304781
|
+
const d = new Date(outerTs);
|
|
304782
|
+
if (!Number.isNaN(d.getTime())) return d.toISOString();
|
|
304783
|
+
}
|
|
304784
|
+
const innerTs = inner.timestamp;
|
|
304785
|
+
if (typeof innerTs === "number" && Number.isFinite(innerTs)) {
|
|
304786
|
+
return new Date(innerTs).toISOString();
|
|
304787
|
+
}
|
|
304788
|
+
if (typeof innerTs === "string" && innerTs.length > 0) {
|
|
304789
|
+
const parsed = Number.parseInt(innerTs, 10);
|
|
304790
|
+
if (!Number.isNaN(parsed)) {
|
|
304791
|
+
return new Date(parsed).toISOString();
|
|
304792
|
+
}
|
|
304793
|
+
}
|
|
304794
|
+
return "";
|
|
304795
|
+
}
|
|
304796
|
+
function formatMonitorTransition(event) {
|
|
304797
|
+
const outer = event.attributes ?? {};
|
|
304798
|
+
const inner = outer.attributes ?? {};
|
|
304799
|
+
const monitor = inner.monitor;
|
|
304800
|
+
if (!monitor) {
|
|
304801
|
+
return null;
|
|
304802
|
+
}
|
|
304803
|
+
const transition = monitor.transition ?? monitor.additionalProperties?.transition;
|
|
304804
|
+
if (!transition) {
|
|
304805
|
+
return null;
|
|
304806
|
+
}
|
|
304807
|
+
const fromState = isMonitorState(transition.source_state) ? transition.source_state : null;
|
|
304808
|
+
const toState = isMonitorState(transition.destination_state) ? transition.destination_state : null;
|
|
304809
|
+
const transitionType = isTransitionType(transition.transition_type) ? transition.transition_type : null;
|
|
304810
|
+
if (!fromState || !toState || !transitionType) {
|
|
304811
|
+
return null;
|
|
304812
|
+
}
|
|
304813
|
+
const groupsRaw = monitor.groups;
|
|
304814
|
+
const group = Array.isArray(groupsRaw) && groupsRaw.length > 0 ? groupsRaw.map((g) => String(g)).join(",") : null;
|
|
304815
|
+
const monitorId = typeof monitor.id === "number" ? monitor.id : 0;
|
|
304816
|
+
const monitorName = typeof monitor.name === "string" && monitor.name.length > 0 ? monitor.name : `Monitor ${monitorId}`;
|
|
304817
|
+
return {
|
|
304818
|
+
timestamp: extractTimestamp(outer, inner),
|
|
304819
|
+
monitorId,
|
|
304820
|
+
monitorName,
|
|
304821
|
+
group,
|
|
304822
|
+
fromState,
|
|
304823
|
+
toState,
|
|
304824
|
+
transitionType,
|
|
304825
|
+
eventId: String(event.id ?? "")
|
|
304826
|
+
};
|
|
304827
|
+
}
|
|
304828
|
+
async function historyMonitor(eventsApi, monitorId, params, limits, site) {
|
|
304829
|
+
const defaultFrom = hoursAgo(limits.defaultTimeRangeHours);
|
|
304830
|
+
const defaultTo = now();
|
|
304831
|
+
const [validFrom, validTo] = ensureValidTimeRange(
|
|
304832
|
+
parseTime(params.from, defaultFrom),
|
|
304833
|
+
parseTime(params.to, defaultTo)
|
|
304834
|
+
);
|
|
304835
|
+
const fromTime = new Date(validFrom * 1e3).toISOString();
|
|
304836
|
+
const toTime = new Date(validTo * 1e3).toISOString();
|
|
304837
|
+
const effectiveTransitionTypes = params.transitionType && params.transitionType.length > 0 ? params.transitionType : [...DEFAULT_HISTORY_TRANSITION_TYPES];
|
|
304838
|
+
const query = buildMonitorHistoryQuery({
|
|
304839
|
+
monitorId,
|
|
304840
|
+
transitionType: effectiveTransitionTypes,
|
|
304841
|
+
group: params.group
|
|
304842
|
+
});
|
|
304843
|
+
const transitions = [];
|
|
304844
|
+
const maxEventsToProcess = 1e4;
|
|
304845
|
+
const maxPages = 100;
|
|
304846
|
+
let eventCount = 0;
|
|
304847
|
+
let pageCount = 0;
|
|
304848
|
+
const body = {
|
|
304849
|
+
filter: {
|
|
304850
|
+
query,
|
|
304851
|
+
from: fromTime,
|
|
304852
|
+
to: toTime
|
|
304853
|
+
},
|
|
304854
|
+
sort: "timestamp",
|
|
304855
|
+
page: { limit: 1e3 }
|
|
304856
|
+
};
|
|
304857
|
+
let cursor;
|
|
304858
|
+
while (pageCount < maxPages && eventCount < maxEventsToProcess) {
|
|
304859
|
+
const pageBody = { ...body, page: { ...body.page, cursor } };
|
|
304860
|
+
const response = await eventsApi.searchEvents({ body: pageBody });
|
|
304861
|
+
const events2 = response.data ?? [];
|
|
304862
|
+
if (events2.length === 0) break;
|
|
304863
|
+
for (const event of events2) {
|
|
304864
|
+
const transition = formatMonitorTransition(event);
|
|
304865
|
+
if (transition !== null) {
|
|
304866
|
+
transitions.push(transition);
|
|
304867
|
+
}
|
|
304868
|
+
eventCount++;
|
|
304869
|
+
if (eventCount >= maxEventsToProcess) break;
|
|
304870
|
+
}
|
|
304871
|
+
cursor = response.meta?.page?.after;
|
|
304872
|
+
if (!cursor) break;
|
|
304873
|
+
pageCount++;
|
|
304874
|
+
}
|
|
304875
|
+
const truncated = eventCount >= maxEventsToProcess;
|
|
304876
|
+
const resolvedGroup = params.group && params.group.length > 0 ? params.group : null;
|
|
304877
|
+
const count = transitions.length;
|
|
304878
|
+
const meta = {
|
|
304879
|
+
monitorId,
|
|
304880
|
+
query,
|
|
304881
|
+
from: fromTime,
|
|
304882
|
+
to: toTime,
|
|
304883
|
+
transitionTypes: effectiveTransitionTypes,
|
|
304884
|
+
group: resolvedGroup,
|
|
304885
|
+
count,
|
|
304886
|
+
totalFetched: eventCount,
|
|
304887
|
+
truncated,
|
|
304888
|
+
datadog_url: buildEventsUrl(query, validFrom, validTo, site)
|
|
304889
|
+
};
|
|
304890
|
+
return {
|
|
304891
|
+
transitions,
|
|
304892
|
+
count,
|
|
304893
|
+
meta
|
|
304894
|
+
};
|
|
304895
|
+
}
|
|
304896
|
+
async function listMonitors(api, params, limits, site, timezone) {
|
|
304897
|
+
if (timezone !== void 0) {
|
|
304898
|
+
validateIanaZone(timezone);
|
|
304899
|
+
}
|
|
304036
304900
|
const effectiveLimit = params.limit ?? limits.defaultLimit;
|
|
304037
304901
|
const response = await api.listMonitors({
|
|
304038
304902
|
name: params.name,
|
|
304039
304903
|
tags: params.tags?.join(","),
|
|
304040
304904
|
groupStates: params.groupStates?.join(",")
|
|
304041
304905
|
});
|
|
304042
|
-
const
|
|
304906
|
+
const baseMonitors = response.slice(0, effectiveLimit).map((m) => formatMonitor(m, site));
|
|
304907
|
+
const monitors2 = timezone !== void 0 ? baseMonitors.map((m) => annotateMonitorTimezone(m, timezone)) : baseMonitors;
|
|
304043
304908
|
const statusCounts = {
|
|
304044
304909
|
total: response.length,
|
|
304045
304910
|
alert: response.filter((m) => m.overallState === "Alert").length,
|
|
@@ -304056,14 +304921,19 @@ async function listMonitors(api, params, limits, site) {
|
|
|
304056
304921
|
)
|
|
304057
304922
|
};
|
|
304058
304923
|
}
|
|
304059
|
-
async function getMonitor(api, id, site) {
|
|
304924
|
+
async function getMonitor(api, id, site, timezone) {
|
|
304925
|
+
if (timezone !== void 0) {
|
|
304926
|
+
validateIanaZone(timezone);
|
|
304927
|
+
}
|
|
304060
304928
|
const monitorId = Number.parseInt(id, 10);
|
|
304061
304929
|
if (Number.isNaN(monitorId)) {
|
|
304062
304930
|
throw new Error(`Invalid monitor ID: ${id}`);
|
|
304063
304931
|
}
|
|
304064
304932
|
const monitor = await api.getMonitor({ monitorId });
|
|
304933
|
+
const baseDetail = formatMonitorDetail(monitor, site);
|
|
304934
|
+
const detail = timezone !== void 0 ? annotateMonitorTimezone(baseDetail, timezone) : baseDetail;
|
|
304065
304935
|
return {
|
|
304066
|
-
monitor:
|
|
304936
|
+
monitor: detail,
|
|
304067
304937
|
datadog_url: buildMonitorUrl(monitorId, site)
|
|
304068
304938
|
};
|
|
304069
304939
|
}
|
|
@@ -304165,6 +305035,37 @@ async function createMonitor(api, config2, site = "datadoghq.com") {
|
|
|
304165
305035
|
}
|
|
304166
305036
|
return result;
|
|
304167
305037
|
}
|
|
305038
|
+
async function dryRunMonitor(api, config2) {
|
|
305039
|
+
const normalized = normalizeMonitorConfig(config2);
|
|
305040
|
+
const body = normalized;
|
|
305041
|
+
await api.validateMonitor({ body });
|
|
305042
|
+
return {
|
|
305043
|
+
valid: true,
|
|
305044
|
+
dryRun: true,
|
|
305045
|
+
monitor: normalized
|
|
305046
|
+
};
|
|
305047
|
+
}
|
|
305048
|
+
async function previewMonitor(api, args, site = "datadoghq.com") {
|
|
305049
|
+
let template;
|
|
305050
|
+
let monitorId;
|
|
305051
|
+
if (args.inlineMessage !== void 0 && args.inlineMessage !== "") {
|
|
305052
|
+
template = args.inlineMessage;
|
|
305053
|
+
} else if (args.monitorIdSource !== void 0 && args.monitorIdSource !== "") {
|
|
305054
|
+
const loaded = await getMonitor(api, args.monitorIdSource, site);
|
|
305055
|
+
template = loaded.monitor.message ?? "";
|
|
305056
|
+
monitorId = loaded.monitor.id;
|
|
305057
|
+
} else {
|
|
305058
|
+
throw new Error(
|
|
305059
|
+
"Action 'preview' requires either an inline 'message' or a 'monitor_id' (or 'id') to load the template from."
|
|
305060
|
+
);
|
|
305061
|
+
}
|
|
305062
|
+
const result = renderMonitorTemplate(template, args.context ?? {});
|
|
305063
|
+
return monitorId !== void 0 ? { ...result, monitorId } : result;
|
|
305064
|
+
}
|
|
305065
|
+
var TEST_NOTIFICATION_NOT_SUPPORTED_MESSAGE = "ENOT_SUPPORTED: action 'test_notification' is not implemented \u2014 the Datadog public REST API exposes no monitor test-notification endpoint at v1 or v2 as of the events-dx-improvements spec. Use the Datadog UI's 'Test Notifications' button on the monitor page, or open an issue if Datadog publishes such an endpoint. Reference: https://docs.datadoghq.com/api/latest/monitors/";
|
|
305066
|
+
function testNotificationMonitor(_args) {
|
|
305067
|
+
throw new McpError(ErrorCode.InvalidRequest, TEST_NOTIFICATION_NOT_SUPPORTED_MESSAGE);
|
|
305068
|
+
}
|
|
304168
305069
|
async function updateMonitor(api, id, config2, site = "datadoghq.com") {
|
|
304169
305070
|
const monitorId = Number.parseInt(id, 10);
|
|
304170
305071
|
const normalized = normalizeMonitorConfig(config2, true);
|
|
@@ -304249,7 +305150,7 @@ async function topMonitors(eventsApi, monitorsApi, params, limits, site) {
|
|
|
304249
305150
|
}
|
|
304250
305151
|
});
|
|
304251
305152
|
const rawEvents = searchResponse.data ?? [];
|
|
304252
|
-
const
|
|
305153
|
+
const events2 = rawEvents.map(formatEventV2);
|
|
304253
305154
|
const contextPrefixes = new Set(
|
|
304254
305155
|
params.contextTags ?? [
|
|
304255
305156
|
"queue",
|
|
@@ -304261,7 +305162,7 @@ async function topMonitors(eventsApi, monitorsApi, params, limits, site) {
|
|
|
304261
305162
|
]
|
|
304262
305163
|
);
|
|
304263
305164
|
const monitorGroups = /* @__PURE__ */ new Map();
|
|
304264
|
-
for (const event of
|
|
305165
|
+
for (const event of events2) {
|
|
304265
305166
|
const monitorId = event.monitorId;
|
|
304266
305167
|
if (typeof monitorId !== "number") continue;
|
|
304267
305168
|
let group = monitorGroups.get(monitorId);
|
|
@@ -304330,7 +305231,7 @@ async function topMonitors(eventsApi, monitorsApi, params, limits, site) {
|
|
|
304330
305231
|
from: fromTime,
|
|
304331
305232
|
to: toTime,
|
|
304332
305233
|
totalMonitors: monitorGroups.size,
|
|
304333
|
-
totalEvents:
|
|
305234
|
+
totalEvents: events2.length,
|
|
304334
305235
|
contextPrefixes: Array.from(contextPrefixes),
|
|
304335
305236
|
datadog_url: buildEventsUrl(query, validFrom, validTo, site)
|
|
304336
305237
|
}
|
|
@@ -304339,7 +305240,7 @@ async function topMonitors(eventsApi, monitorsApi, params, limits, site) {
|
|
|
304339
305240
|
function registerMonitorsTool(server, api, eventsApi, limits, readOnly = false, site = "datadoghq.com") {
|
|
304340
305241
|
server.tool(
|
|
304341
305242
|
"monitors",
|
|
304342
|
-
`Manage Datadog monitors. Actions: list, get, search, create, update, delete, mute, unmute, top.
|
|
305243
|
+
`Manage Datadog monitors. Actions: list, get, search, create, update, delete, mute, unmute, top, history, preview, test_notification.
|
|
304343
305244
|
Filters: name, tags, groupStates (alert/warn/ok/no data).
|
|
304344
305245
|
get/create/update return the full options object so callers can safely read-then-patch.
|
|
304345
305246
|
|
|
@@ -304361,8 +305262,40 @@ top: Ranked monitors by alert frequency with real monitor names and context brea
|
|
|
304361
305262
|
- Returns: {rank, monitor_id, name (with {{template.vars}}), message (template), total_count, by_context}
|
|
304362
305263
|
- Perfect for weekly/daily alert reports
|
|
304363
305264
|
- Gets real monitor names from monitors API (not event titles)
|
|
304364
|
-
|
|
304365
|
-
For
|
|
305265
|
+
- WARNING: total_count is the raw alert-event count and INCLUDES renotifies/re-evaluations.
|
|
305266
|
+
For monitors stuck in Alert state, Datadog emits a renotify event every renotify_interval
|
|
305267
|
+
minutes, which inflates this count well beyond the number of real fires. When the question
|
|
305268
|
+
is "how many times did this monitor actually fire", use action=history instead.
|
|
305269
|
+
|
|
305270
|
+
history: Count and list real state transitions for one monitor over a time window.
|
|
305271
|
+
- Inputs: id (required, monitor ID), from/to (optional time range), transitionType (optional
|
|
305272
|
+
filter, defaults to ["alert","alert recovery"]), group (optional multi-alert group filter).
|
|
305273
|
+
- Returns: {transitions: [{timestamp, monitorId, monitorName, group, fromState, toState,
|
|
305274
|
+
transitionType, eventId}], count, meta}
|
|
305275
|
+
- count = transitions.length \u2014 the number of REAL state changes (fires + recoveries by
|
|
305276
|
+
default), NOT the renotify-inflated count returned by action=top or events action=search.
|
|
305277
|
+
- Backed by Datadog v2 events search with a hardcoded source:alert + @monitor.transition.
|
|
305278
|
+
transition_type filter that excludes renotifies by default. To include renotifies, pass
|
|
305279
|
+
transitionType including "renotify".
|
|
305280
|
+
|
|
305281
|
+
preview: Render a Datadog monitor message template against a context (read-only safe).
|
|
305282
|
+
- Inputs: either inline 'message' OR 'monitor_id' (or existing 'id'); plus optional 'context' { variables, conditionals }.
|
|
305283
|
+
- Supported syntax: {{variable.name}} substitution and conditional blocks {{#name}}...{{/name}} / {{^name}}...{{/name}}
|
|
305284
|
+
where name is one of: ${SUPPORTED_CONDITIONALS.join(", ")}.
|
|
305285
|
+
- Missing variables render as {{undefined:name}} markers and are reported in 'variablesMissing'.
|
|
305286
|
+
- Loops ({{#each ...}}) and partials ({{> ...}}) return EUNSUPPORTED_TEMPLATE_SYNTAX.
|
|
305287
|
+
- Allowed under --read-only (no mutation; at most a getMonitor load).
|
|
305288
|
+
|
|
305289
|
+
test_notification: KNOWN LIMITATION \u2014 always returns ENOT_SUPPORTED.
|
|
305290
|
+
- Datadog's public REST API exposes no monitor test-notification endpoint at v1 or v2
|
|
305291
|
+
(audited against the official OpenAPI specs). The v1 SDK has no notifyMonitor / testMonitor method.
|
|
305292
|
+
- Allowed under --read-only because no Datadog HTTP call is attempted.
|
|
305293
|
+
- If Datadog publishes such an endpoint in future, this action will be reimplemented to invoke it.
|
|
305294
|
+
- Workaround: use the 'Test Notifications' button in the Datadog monitor UI.
|
|
305295
|
+
|
|
305296
|
+
For generic event grouping (deployments, configs), use events tool instead. Note that the
|
|
305297
|
+
events tool's action=search with source:alert ALSO includes renotifies; use its
|
|
305298
|
+
transitionType filter (or this action=history) for fires-only counts.`,
|
|
304366
305299
|
InputSchema2,
|
|
304367
305300
|
async ({
|
|
304368
305301
|
action,
|
|
@@ -304373,22 +305306,32 @@ For generic event grouping (deployments, configs), use events tool instead.`,
|
|
|
304373
305306
|
groupStates,
|
|
304374
305307
|
limit,
|
|
304375
305308
|
config: config2,
|
|
305309
|
+
message,
|
|
304376
305310
|
end,
|
|
304377
305311
|
from,
|
|
304378
305312
|
to,
|
|
304379
305313
|
contextTags,
|
|
304380
|
-
maxEvents
|
|
305314
|
+
maxEvents,
|
|
305315
|
+
transitionType,
|
|
305316
|
+
group,
|
|
305317
|
+
dry_run: dryRun,
|
|
305318
|
+
monitor_id: monitorIdNum,
|
|
305319
|
+
context,
|
|
305320
|
+
timezone
|
|
304381
305321
|
}) => {
|
|
304382
305322
|
try {
|
|
304383
|
-
|
|
305323
|
+
const isDryRunCreate = action === "create" && dryRun === true;
|
|
305324
|
+
if (!isDryRunCreate) {
|
|
305325
|
+
checkReadOnly(action, readOnly);
|
|
305326
|
+
}
|
|
304384
305327
|
switch (action) {
|
|
304385
305328
|
case "list":
|
|
304386
305329
|
return toolResult(
|
|
304387
|
-
await listMonitors(api, { name, tags, groupStates, limit }, limits, site)
|
|
305330
|
+
await listMonitors(api, { name, tags, groupStates, limit }, limits, site, timezone)
|
|
304388
305331
|
);
|
|
304389
305332
|
case "get": {
|
|
304390
305333
|
const monitorId = requireParam(id, "id", "get");
|
|
304391
|
-
return toolResult(await getMonitor(api, monitorId, site));
|
|
305334
|
+
return toolResult(await getMonitor(api, monitorId, site, timezone));
|
|
304392
305335
|
}
|
|
304393
305336
|
case "search": {
|
|
304394
305337
|
const searchQuery = requireParam(query, "query", "search");
|
|
@@ -304396,6 +305339,9 @@ For generic event grouping (deployments, configs), use events tool instead.`,
|
|
|
304396
305339
|
}
|
|
304397
305340
|
case "create": {
|
|
304398
305341
|
const monitorConfig = requireParam(config2, "config", "create");
|
|
305342
|
+
if (dryRun) {
|
|
305343
|
+
return toolResult(await dryRunMonitor(api, monitorConfig));
|
|
305344
|
+
}
|
|
304399
305345
|
return toolResult(await createMonitor(api, monitorConfig, site));
|
|
304400
305346
|
}
|
|
304401
305347
|
case "update": {
|
|
@@ -304432,6 +305378,40 @@ For generic event grouping (deployments, configs), use events tool instead.`,
|
|
|
304432
305378
|
site
|
|
304433
305379
|
)
|
|
304434
305380
|
);
|
|
305381
|
+
case "history": {
|
|
305382
|
+
const monitorIdString = requireParam(id, "id", "history");
|
|
305383
|
+
const monitorId = Number.parseInt(monitorIdString, 10);
|
|
305384
|
+
if (Number.isNaN(monitorId)) {
|
|
305385
|
+
throw new Error(`Invalid monitor ID: ${monitorIdString}`);
|
|
305386
|
+
}
|
|
305387
|
+
return toolResult(
|
|
305388
|
+
await historyMonitor(
|
|
305389
|
+
eventsApi,
|
|
305390
|
+
monitorId,
|
|
305391
|
+
{ from, to, transitionType, group },
|
|
305392
|
+
limits,
|
|
305393
|
+
site
|
|
305394
|
+
)
|
|
305395
|
+
);
|
|
305396
|
+
}
|
|
305397
|
+
case "preview": {
|
|
305398
|
+
const monitorIdSource = monitorIdNum !== void 0 ? String(monitorIdNum) : id ?? void 0;
|
|
305399
|
+
return toolResult(
|
|
305400
|
+
await previewMonitor(
|
|
305401
|
+
api,
|
|
305402
|
+
{
|
|
305403
|
+
inlineMessage: message,
|
|
305404
|
+
monitorIdSource,
|
|
305405
|
+
context
|
|
305406
|
+
},
|
|
305407
|
+
site
|
|
305408
|
+
)
|
|
305409
|
+
);
|
|
305410
|
+
}
|
|
305411
|
+
case "test_notification": {
|
|
305412
|
+
const monitorIdSource = monitorIdNum !== void 0 ? String(monitorIdNum) : id ?? void 0;
|
|
305413
|
+
return testNotificationMonitor({ monitorId: monitorIdSource });
|
|
305414
|
+
}
|
|
304435
305415
|
default:
|
|
304436
305416
|
throw new Error(`Unknown action: ${action}`);
|
|
304437
305417
|
}
|
|
@@ -305048,6 +306028,56 @@ var InputSchema5 = {
|
|
|
305048
306028
|
"Maximum data points per timeseries (for query action). AI controls resolution vs token usage (default: 1000)."
|
|
305049
306029
|
)
|
|
305050
306030
|
};
|
|
306031
|
+
var ROLLUP_METHODS = /* @__PURE__ */ new Set(["avg", "max", "min", "sum", "count"]);
|
|
306032
|
+
var DEFAULT_ROLLUP_METHOD = "avg";
|
|
306033
|
+
function parseRollupFromQuery(query) {
|
|
306034
|
+
if (typeof query !== "string") return null;
|
|
306035
|
+
const matches = [...query.matchAll(/\.rollup\(\s*([^)]*?)\s*\)/g)];
|
|
306036
|
+
const lastMatch = matches[matches.length - 1];
|
|
306037
|
+
if (lastMatch === void 0) return null;
|
|
306038
|
+
const inner = lastMatch[1];
|
|
306039
|
+
if (inner === void 0 || inner.length === 0) return null;
|
|
306040
|
+
const parts = inner.split(",").map((p) => p.trim());
|
|
306041
|
+
if (parts.length === 1) {
|
|
306042
|
+
const raw = parts[0] ?? "";
|
|
306043
|
+
const interval = Number.parseInt(raw, 10);
|
|
306044
|
+
if (!Number.isFinite(interval) || interval <= 0 || String(interval) !== raw) {
|
|
306045
|
+
return null;
|
|
306046
|
+
}
|
|
306047
|
+
return { interval, method: DEFAULT_ROLLUP_METHOD, methodInferred: true };
|
|
306048
|
+
}
|
|
306049
|
+
if (parts.length === 2) {
|
|
306050
|
+
const method = parts[0] ?? "";
|
|
306051
|
+
const secondsRaw = parts[1] ?? "";
|
|
306052
|
+
if (!ROLLUP_METHODS.has(method)) return null;
|
|
306053
|
+
const interval = Number.parseInt(secondsRaw, 10);
|
|
306054
|
+
if (!Number.isFinite(interval) || interval <= 0 || String(interval) !== secondsRaw) {
|
|
306055
|
+
return null;
|
|
306056
|
+
}
|
|
306057
|
+
return { interval, method, methodInferred: false };
|
|
306058
|
+
}
|
|
306059
|
+
return null;
|
|
306060
|
+
}
|
|
306061
|
+
function computeEffectiveRollup(series) {
|
|
306062
|
+
const intervals = [];
|
|
306063
|
+
for (const s of series) {
|
|
306064
|
+
const pts = s.pointlist ?? [];
|
|
306065
|
+
if (pts.length < 2) continue;
|
|
306066
|
+
const first = pts[0];
|
|
306067
|
+
const second = pts[1];
|
|
306068
|
+
if (first === void 0 || second === void 0) continue;
|
|
306069
|
+
const deltaMs = (second[0] ?? 0) - (first[0] ?? 0);
|
|
306070
|
+
if (deltaMs <= 0) continue;
|
|
306071
|
+
intervals.push(Math.round(deltaMs / 1e3));
|
|
306072
|
+
}
|
|
306073
|
+
const primary = intervals[0];
|
|
306074
|
+
if (primary === void 0) return null;
|
|
306075
|
+
const unique = Array.from(new Set(intervals)).sort((a, b) => a - b);
|
|
306076
|
+
if (unique.length === 1) {
|
|
306077
|
+
return { interval: primary };
|
|
306078
|
+
}
|
|
306079
|
+
return { interval: primary, intervalsObserved: unique };
|
|
306080
|
+
}
|
|
305051
306081
|
async function queryMetrics(api, params, limits, site) {
|
|
305052
306082
|
const defaultFrom = hoursAgo(limits.defaultTimeRangeHours);
|
|
305053
306083
|
const defaultTo = now();
|
|
@@ -305060,7 +306090,8 @@ async function queryMetrics(api, params, limits, site) {
|
|
|
305060
306090
|
to: toTs,
|
|
305061
306091
|
query: params.query
|
|
305062
306092
|
});
|
|
305063
|
-
const
|
|
306093
|
+
const rawSeries = response.series ?? [];
|
|
306094
|
+
const series = rawSeries.map((s) => ({
|
|
305064
306095
|
metric: s.metric ?? "",
|
|
305065
306096
|
points: (s.pointlist ?? []).slice(0, params.pointLimit ?? limits.defaultMetricDataPoints).map((p) => ({
|
|
305066
306097
|
timestamp: p[0] ?? 0,
|
|
@@ -305069,6 +306100,13 @@ async function queryMetrics(api, params, limits, site) {
|
|
|
305069
306100
|
scope: s.scope ?? "",
|
|
305070
306101
|
tags: s.tagSet ?? []
|
|
305071
306102
|
}));
|
|
306103
|
+
const rollupRequested = parseRollupFromQuery(params.query);
|
|
306104
|
+
const rollupEffective = computeEffectiveRollup(
|
|
306105
|
+
rawSeries.map((s) => ({
|
|
306106
|
+
pointlist: s.pointlist ?? []
|
|
306107
|
+
}))
|
|
306108
|
+
);
|
|
306109
|
+
const rollupOverridden = rollupRequested !== null && rollupEffective !== null && (rollupEffective.interval !== rollupRequested.interval || (rollupEffective.intervalsObserved?.some((i) => i !== rollupRequested.interval) ?? false));
|
|
305072
306110
|
return {
|
|
305073
306111
|
series,
|
|
305074
306112
|
meta: {
|
|
@@ -305076,7 +306114,10 @@ async function queryMetrics(api, params, limits, site) {
|
|
|
305076
306114
|
from: new Date(fromTs * 1e3).toISOString(),
|
|
305077
306115
|
to: new Date(toTs * 1e3).toISOString(),
|
|
305078
306116
|
seriesCount: series.length,
|
|
305079
|
-
datadog_url: buildMetricsUrl(params.query, fromTs, toTs, site)
|
|
306117
|
+
datadog_url: buildMetricsUrl(params.query, fromTs, toTs, site),
|
|
306118
|
+
rollupRequested,
|
|
306119
|
+
rollupEffective,
|
|
306120
|
+
rollupOverridden
|
|
305080
306121
|
}
|
|
305081
306122
|
};
|
|
305082
306123
|
}
|
|
@@ -306617,11 +307658,11 @@ async function searchEvents(api, params, limits, site) {
|
|
|
306617
307658
|
sort: params.sort === "timestamp" ? "timestamp" : "-timestamp",
|
|
306618
307659
|
pageLimit: params.limit ?? limits.defaultLimit
|
|
306619
307660
|
});
|
|
306620
|
-
const
|
|
307661
|
+
const events2 = (response.data ?? []).map(formatEvent);
|
|
306621
307662
|
return {
|
|
306622
|
-
events,
|
|
307663
|
+
events: events2,
|
|
306623
307664
|
meta: {
|
|
306624
|
-
totalCount:
|
|
307665
|
+
totalCount: events2.length,
|
|
306625
307666
|
timeRange: {
|
|
306626
307667
|
from: new Date(fromTime * 1e3).toISOString(),
|
|
306627
307668
|
to: new Date(toTime * 1e3).toISOString()
|
|
@@ -306834,19 +307875,19 @@ async function getSessionWaterfall(api, params, limits, site) {
|
|
|
306834
307875
|
sort: "timestamp",
|
|
306835
307876
|
pageLimit: limits.defaultLimit
|
|
306836
307877
|
});
|
|
306837
|
-
const
|
|
307878
|
+
const events2 = (response.data ?? []).map(formatWaterfallEvent);
|
|
306838
307879
|
const summary = {
|
|
306839
|
-
views:
|
|
306840
|
-
resources:
|
|
306841
|
-
actions:
|
|
306842
|
-
errors:
|
|
306843
|
-
longTasks:
|
|
307880
|
+
views: events2.filter((e) => e.type === "view").length,
|
|
307881
|
+
resources: events2.filter((e) => e.type === "resource").length,
|
|
307882
|
+
actions: events2.filter((e) => e.type === "action").length,
|
|
307883
|
+
errors: events2.filter((e) => e.type === "error").length,
|
|
307884
|
+
longTasks: events2.filter((e) => e.type === "long_task").length
|
|
306844
307885
|
};
|
|
306845
307886
|
return {
|
|
306846
|
-
events,
|
|
307887
|
+
events: events2,
|
|
306847
307888
|
summary,
|
|
306848
307889
|
meta: {
|
|
306849
|
-
totalCount:
|
|
307890
|
+
totalCount: events2.length,
|
|
306850
307891
|
applicationId: params.applicationId,
|
|
306851
307892
|
sessionId: params.sessionId,
|
|
306852
307893
|
viewId: params.viewId ?? null,
|
|
@@ -308026,6 +309067,17 @@ var dashboards = {
|
|
|
308026
309067
|
docsUrl: "https://docs.datadoghq.com/api/latest/dashboards/"
|
|
308027
309068
|
};
|
|
308028
309069
|
|
|
309070
|
+
// src/schema/events.ts
|
|
309071
|
+
var events = {
|
|
309072
|
+
/**
|
|
309073
|
+
* Diagnostic codes emitted on zero-result `events.search` responses.
|
|
309074
|
+
* Each code is also documented inline in `src/tools/events.ts` next to the
|
|
309075
|
+
* heuristic that produces it.
|
|
309076
|
+
*/
|
|
309077
|
+
diagnosticCodes: ["UNINDEXED_TAG_PREFIX", "NARROW_TIME_RANGE", "RESTRICTIVE_SOURCE_FILTER"],
|
|
309078
|
+
docsUrl: "https://docs.datadoghq.com/api/latest/events/"
|
|
309079
|
+
};
|
|
309080
|
+
|
|
308029
309081
|
// src/schema/metrics.ts
|
|
308030
309082
|
var metrics = {
|
|
308031
309083
|
aggregators: ["avg", "max", "min", "sum", "count"],
|
|
@@ -308086,14 +309138,14 @@ var slos = {
|
|
|
308086
309138
|
};
|
|
308087
309139
|
|
|
308088
309140
|
// src/schema/index.ts
|
|
308089
|
-
var schemas = { dashboards, metrics, monitors, slos };
|
|
309141
|
+
var schemas = { dashboards, events, metrics, monitors, slos };
|
|
308090
309142
|
var schemaResources = Object.keys(schemas);
|
|
308091
309143
|
|
|
308092
309144
|
// src/tools/schema.ts
|
|
308093
309145
|
var ResourceSchema2 = external_exports.enum(schemaResources);
|
|
308094
309146
|
var InputSchema20 = {
|
|
308095
309147
|
resource: ResourceSchema2.describe(
|
|
308096
|
-
"Datadog resource type to get schema for: dashboards, metrics, monitors, slos"
|
|
309148
|
+
"Datadog resource type to get schema for: dashboards, events, metrics, monitors, slos"
|
|
308097
309149
|
)
|
|
308098
309150
|
};
|
|
308099
309151
|
function getSchema(resource) {
|