Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 462
feat(app): list all webhook template variables; fix sourceQuery and thresholdMax#3071
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
5ca691e8da31656a2a124ab3ea93334e939File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| --- | ||
| '@hyperdx/api': patch | ||
| --- | ||
| Fix `{{sourceQuery}}` returning empty for inline-query and dashboard-tile | ||
| alerts. It read only the saved search's filter, so alerts backed by a chart | ||
| config — where the query lives on the alert or the tile — advertised a variable | ||
| that never rendered. It now resolves the query from whichever config backs the | ||
| alert: the builder `where` or the raw `sqlTemplate`. | ||
| Add `{{thresholdMax}}`, the upper bound of a `between` / `outside` condition. | ||
| Receivers previously saw only the lower bound and could not reconstruct the | ||
| range that fired. It renders empty for every other comparator. | ||
| Test Webhook now sends a sample value for every template variable. It carried | ||
| only the original seven, so a body using an enriched variable rendered it empty | ||
| — and because `threshold`, `thresholdMax` and `value` are emitted unquoted, a | ||
| body like `{"value": {{value}}}` was sent as `{"value": }` and rejected, | ||
| failing the test for a template that works on a real firing. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| --- | ||
| '@hyperdx/app': patch | ||
| --- | ||
| List every supported template variable in the webhook form, including the | ||
| enriched set added to Generic and incident.io bodies (`{{alertId}}`, | ||
| `{{status}}`, `{{alertType}}`, `{{comparator}}`, `{{threshold}}`, | ||
| `{{thresholdMax}}`, `{{value}}`, `{{groupKey}}`, `{{sourceQuery}}`, | ||
| `{{teamId}}`, `{{note}}` and ISO-8601 `{{startTimeISO}}` / `{{endTimeISO}}`). | ||
| Each variable now carries a one-line description, so a webhook body can be | ||
| written without leaving the form. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -18,17 +18,21 @@ a HyperDX alert without parsing the human-readable message body. | ||
| | `{{alertType}}` | string | `search`, `dashboard_chart` or `inline_query`. | | ||
| | `{{comparator}}` | string | `>=`, `>`, `<`, `<=`, `=`, `!=`, `between`, `outside`. | | ||
| | `{{threshold}}` | number | The configured threshold. | | ||
| | `{{thresholdMax}}` | number | Upper bound of a `between` / `outside` range; empty for every other comparator. | | ||
| | `{{value}}` | number | The value that triggered or resolved the alert. | | ||
| | `{{groupKey}}` | string | The breaching group, for a grouped alert. | | ||
| | `{{sourceQuery}}` | string | The search expression or SQL behind the alert. | | ||
| | `{{sourceQuery}}` | string | The query behind the alert: the saved search's filter, the chart's `where`, or the raw SQL. | | ||
| | `{{teamId}}` | string | Team the alert belongs to. | | ||
| | `{{note}}` | string | The alert's freeform note — commonly a runbook link. | | ||
| Strings are JSON-escaped, so they are safe to drop into a quoted slot. | ||
| Numbers (`startTime`, `endTime`, `threshold`, `value`) are emitted raw for | ||
| unquoted slots. Every enriched variable is optional and renders as an empty | ||
| string when the alert doesn't carry it — an alert with no group has an empty | ||
| `{{groupKey}}`, and a dashboard-tile alert has an empty `{{sourceQuery}}`. | ||
| Numbers (`startTime`, `endTime`, `threshold`, `thresholdMax`, `value`) are | ||
| emitted raw for unquoted slots. Every enriched variable is optional and renders | ||
| as an empty string when the alert doesn't carry it — an alert with no group has | ||
| an empty `{{groupKey}}`, and a non-range alert has an empty `{{thresholdMax}}`. | ||
| An empty variable in an unquoted numeric slot produces invalid JSON, so guard | ||
| the optional numbers: `{{#if thresholdMax}}"max": {{thresholdMax}},{{/if}}`. | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Recommended Handlebars | ||
| ## Example | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -8,6 +8,7 @@ import type { | ||
| import express from 'express'; | ||
| import { ObjectId } from 'mongodb'; | ||
| import mongoose from 'mongoose'; | ||
| import ms from 'ms'; | ||
| import { z } from 'zod'; | ||
| import { validateRequest } from 'zod-express-middleware'; | ||
| @@ -18,6 +19,7 @@ import { | ||
| handleSendGenericWebhook, | ||
| handleSendSlackWebhook, | ||
| } from '@/tasks/checkAlerts/transports'; | ||
| import type { Message } from '@/tasks/checkAlerts/transports/types'; | ||
| import { isDuplicateKeyError } from '@/utils/errors'; | ||
| import { | ||
| validateWebhookUrl, | ||
| @@ -459,15 +461,33 @@ router.post( | ||
| body, | ||
| }); | ||
| // Send test message | ||
| const testMessage = { | ||
| // Every field a real firing sends, so a body written against the | ||
| // documented variables renders here exactly as it will in production. | ||
| // The enriched variables especially: `threshold`, `thresholdMax` and | ||
| // `value` are emitted raw, so leaving them unset renders `"value": ` | ||
| // and the receiver rejects a template that would have worked. | ||
| // A range comparator is the useful sample — it is the one case where | ||
| // `thresholdMax` is populated. | ||
| const now = Date.now(); | ||
| const testMessage: Message = { | ||
| hdxLink: 'https://hyperdx.io', | ||
| title: 'Test Webhook from HyperDX', | ||
| body: 'This is a test message to verify your webhook configuration is working correctly.', | ||
| startTime: Date.now(), | ||
| endTime: Date.now(), | ||
| state: AlertState.INSUFFICIENT_DATA, | ||
| startTime: now - ms('5m'), | ||
| endTime: now, | ||
| state: AlertState.ALERT, | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 major — Test Webhook now sends a firing payload, so testing an incident.io webhook opens a real alert
| ||
| eventId: 'test-event-id', | ||
| alertId: 'test-alert-id', | ||
| status: 'firing', | ||
| alertType: 'search', | ||
| comparator: 'between', | ||
| threshold: 5, | ||
| thresholdMax: 10, | ||
| value: 7, | ||
| groupKey: 'test-group', | ||
| sourceQuery: 'SeverityText: "error"', | ||
| teamId: teamId.toString(), | ||
| note: 'Test webhook — no runbook', | ||
| }; | ||
| const testChannel = { type: 'webhook' as const, channel: testWebhook }; | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔵 minor — The documented
{{#if}}guard silently drops a value of 0Handlebars'
iftreats0as falsy unlessincludeZero=true, so{{#if thresholdMax}}"max": {{thresholdMax}},{{/if}}omits the bound for a validbetween -10 and 0alert, and the same pattern applied to{{value}}drops the very commonvalue: 0case. Document the guard as{{#if thresholdMax includeZero=true}}...{{/if}}.