GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — fix(api): prevent Slack notification failures when alert message exceeds 3000 chars by Tyagiquamar · Pull Request #3473 · ClickHouse/ClickStack Conversation
Contributor
Fixes #3452 Summary of changes
Signed-off-by: Mohd Quamar Tyagi [email protected]
Sorry, something went wrong. Uh oh!
There was an error while loading. Please reload this page. All reactions
|
There was an error while loading. Please reload this page.
🦋 Changeset detectedLatest commit: 2455498 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Someone is attempting to deploy a commit to the HyperDX Team on Vercel. A member of the Team first needs to authorize it. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| export const getFallbackHdxLink = (hdxLink: string): string => { | ||
| try { | ||
| const url = new URL(hdxLink); | ||
| return `${url.origin}/alerts`; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
🟠 major — Fallback link uses url.origin, which drops the base path in sub-path deployments, so the link 404s
Build the fallback from config.FRONTEND_URL (for example ${config.FRONTEND_URL}/alerts, the same way mcp/tools/alerts/getAlert.ts:81 and providers/default.ts:523 do), or add a buildAlertsLink() method to the provider. Today all alert links are built from FRONTEND_URL, and the app supports a base path (NEXT_PUBLIC_HYPERDX_BASE_PATH; ClickStack uses /clickstack in packages/app/next.config.mjs). With FRONTEND_URL=https://host/clickstack, this code turns https://host/clickstack/chart?... into https://host/alerts.
Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This is a valid concern. @Tyagiquamar Could you address it?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| } | ||
|
|
||
| // If text exceeds limit, try falling back to the shorter /alerts link | ||
| const fallbackLink = getFallbackHdxLink(hdxLink); |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
🟠 major — The deep link is swapped for generic /alerts even when the body is what's too long
Only fall back when the link itself is the problem: if header(hdxLink).length + 3 fits within maxLength, keep hdxLink and truncate the body; use /alerts only when the header alone is too big. Example: a saved-search alert has a ~120-char /search/:id?from=..&to=.. link and a ~3100-char body (sample results can be up to 2503 chars plus the template text). The code swaps in /alerts and then truncates the body anyway, so the user loses the link to the exact search for no reason.
Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| const header = `*<${link} | ${title}>*\n`; | ||
| const availableBodyLen = maxLength - header.length - 3; | ||
| if (availableBodyLen > 0) { | ||
| return `${header}${body.slice(0, availableBodyLen)}...`; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
🔵 minor — Body truncation cuts off the closing ``` of the saved-search results block
Saved-search bodies end with a -fenced `__hdx_query_results__` block (`template.ts:928-930`). Cutting the body at a fixed index drops the closing fence, so Slack shows the raw fence and the code formatting breaks. Fix: if the cut lands inside an open fence, close it (e.g. append '\n' and reserve room for it), or truncate inside the fence. Add a test for this case.
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| const header = `*<${link} | ${title}>*\n`; | ||
| const availableBodyLen = maxLength - header.length - 3; | ||
| if (availableBodyLen > 0) { | ||
| return `${header}${body.slice(0, availableBodyLen)}...`; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
🔵 minor — Hand-written slice-plus-ellipsis duplicates the existing truncateString helper
Use truncateString(body, availableBodyLen) from packages/api/src/utils/common.ts:38, which template.ts already uses for alert bodies. This follows the REQUIRED DRY/'Before you add a helper' convention.
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| return `${header}${body.slice(0, availableBodyLen)}...`; | ||
| } | ||
|
|
||
| return `*<${link} | ${title}>*`.slice(0, maxLength); |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
🔵 minor — Last-resort branch cuts through the mrkdwn link syntax
Slicing *<link | title>* at maxLength cuts through the <...> link markup, so Slack gets broken mrkdwn. Truncate title instead (keep the *<link | ...>* wrapper intact), or drop the link and send a plain truncated title.
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| const channel: WebhookChannel = { | ||
| type: 'webhook', | ||
| channel: { | ||
| _id: 'wh-1' as any, |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
🔵 minor — The webhook integration test only covers the short-message path, which already worked
Add a handleSendSlackWebhook case with a >3000-char hdxLink (like the real inline-alert /chart?config= link) and assert the posted block text is ≤3000 chars and uses the fallback link. Also add a case for a FRONTEND_URL that has a path prefix. Without these, removing the formatSlackSectionText call from the send path would still pass.
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| type: 'webhook', | ||
| channel: { | ||
| _id: 'wh-1' as any, | ||
| name: 'Slack Alerts', |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
🔵 minor — The test uses as any casts, against the TypeScript conventions
Build the fixtures with real types (WebhookService.Slack, AlertState.ALERT, a typed webhook object), and use mockResolvedValue(undefined) or a typed response instead of {} as any. The repo conventions say to avoid any and as casts.
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
PR Review
5 finding(s): 🔴 0 critical · 🟠 1 major · 🔵 4 minor 5 posted as inline comment(s) on the changed lines. Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
[Medium impact] The change reaches Slack alert notifications sent through webhook channels. The typed test fixtures and required changeset must be added before merging.
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| const url = new URL(hdxLink); | ||
| return `${url.origin}/alerts`; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Fallback links lose deployment paths
getFallbackHdxLink keeps only the origin. With FRONTEND_URL=https://host.example/hyperdx, an oversized chart link becomes https://host.example/alerts instead of https://host.example/hyperdx/alerts. The app supports this prefix through NEXT_PUBLIC_HYPERDX_BASE_PATH, so the shortened link points outside the app. Build the fallback from the configured frontend URL and add a prefixed-URL test.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| return `${header}${body.slice(0, availableBodyLen)}...`; | ||
| } | ||
|
|
||
| return `*<${link} | ${title}>*`.slice(0, maxLength); |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Long titles break link formatting
When the title makes the header exceed 3000 characters, this branch cuts through the Slack link text and removes its closing >*. Titles can come from a rendered alert.name template. The message fits the length limit but contains an unfinished link. Shorten the title before building the header, keep the link markers intact, and test this branch.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| channel: { | ||
| _id: 'wh-1' as any, | ||
| name: 'Slack Alerts', | ||
| service: 'slack' as any, | ||
| url: 'https://hooks.slack.com/services/T00/B00/X00', | ||
| } as any, |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Channel fixture bypasses type checks
Casting the entire channel fixture to any stops TypeScript from checking whether it matches WebhookChannel. The repository guide requires proper types instead of any; this requirement must be satisfied before merging. Build a complete typed fixture instead. The same pattern appears in the mocked response and message state.
Context Used: AGENTS.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| text: formatSlackSectionText( | ||
| message.hdxLink, | ||
| message.title, | ||
| message.body, | ||
| ), |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This changes notification behavior in @hyperdx/api, but the PR adds no changeset. AGENTS.md requires a changeset for behavior changes and explicitly includes @hyperdx/api. Add a patch changeset before merging so the fix is included in the release notes and version plan.
Context Used: AGENTS.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Deep Review✅ No critical issues found. The change does what it sets out to do: 🟡 P2 -- recommended
🔵 P3 nitpicks (2)
Reviewers (6): correctness, adversarial, testing, maintainability, project-standards, previous-comments. Testing gaps:
Note: the missing-changeset item from prior review comments is resolved — No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
…eds 3000 chars Fixes ClickHouse#3452 Slack section blocks enforce a strict 3000-character limit on the text field. Inline alert links encode large chartConfig objects in query params, causing the message block text to exceed 3000 chars and fail delivery with WEBHOOK_ERROR. Format Slack section text using formatSlackSectionText, which falls back to the shorter /alerts link if the link causes an overflow, and truncates the body safely if necessary. Signed-off-by: Mohd Quamar Tyagi <[email protected]>
c18a0e9 to
edd160a
Compare
|
@Tyagiquamar ping me if the PR is ready for another look |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Signed-off-by: Mohd Quamar Tyagi <[email protected]>
|
@wrn14897 All tests and automated reviews are passing green, and I have added the changeset for @hyperdx/api in 2455498. The PR is ready for another look whenever convenient. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| export const getFallbackHdxLink = (hdxLink: string): string => { | ||
| try { | ||
| const url = new URL(hdxLink); | ||
| return `${url.origin}/alerts`; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
🟠 major — Fallback link drops the deployment path prefix and points to a page that doesn't exist
The links come from ${config.FRONTEND_URL}/chart (packages/api/src/tasks/checkAlerts/providers/default.ts:523). The app also supports serving under a path prefix (NEXT_PUBLIC_HYPERDX_BASE_PATH, packages/app/src/config.ts:62-66). url.origin throws that prefix away, so a FRONTEND_URL like https://host/clickstack falls back to https://host/alerts, which is a 404. Build the fallback from config.FRONTEND_URL instead of parsing it back out of the link.
Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
|
||
| const MAX_SLACK_SECTION_LENGTH = 3000; | ||
|
|
||
| export const getFallbackHdxLink = (hdxLink: string): string => { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
🔵 minor — Transport guesses app routes by string-parsing hdxLink; the fallback belongs in the provider and should target the specific alert
The Slack transport has no knowledge of frontend routes or the alert id, so all it can do is link to the generic list. Have AlertProvider (providers/default.ts) build a short link alongside hdxLink, e.g. ${FRONTEND_URL}/alerts/${alert.id} (that detail route is used in packages/app/src/components/alerts/AlertDetails.tsx:150). Pass it on Message so the recipient lands on the alert that fired, and other length-limited transports can reuse it.
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| const header = `*<${link} | ${title}>*\n`; | ||
| const availableBodyLen = maxLength - header.length - 3; | ||
| if (availableBodyLen > 0) { | ||
| return `${header}${body.slice(0, availableBodyLen)}...`; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
🔵 minor — Re-implements the existing truncateString helper
truncateString in packages/api/src/utils/common.ts:38 already appends '...' after slicing, and template.ts uses it. Reuse it (e.g. header + truncateString(body, maxLength - header.length - 3)) rather than adding a second inline version. The repo conventions mark this DRY check as REQUIRED.
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| return `${header}${body.slice(0, availableBodyLen)}...`; | ||
| } | ||
|
|
||
| return `*<${link} | ${title}>*`.slice(0, maxLength); |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
🔵 minor — Last-resort slice cuts through the mrkdwn link markup
When the title alone pushes the header past the limit, *<link | title>*.slice(0, maxLength) drops the closing >*. Slack then renders the raw <https://... | ... text instead of a link. Truncate title so the *<link | ...>* wrapper stays intact.
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| { | ||
| text: 'CPU Alert', | ||
| blocks: [ | ||
| { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
🔵 minor — Webhook test never exercises the over-limit path the PR fixes
handleSendSlackWebhook is only tested with a short hdxLink, so it would still pass if formatSlackSectionText were removed from the payload. Add a case with a >3000-char chart link and assert the posted block text is the fallback/truncated value and is ≤3000 chars. Add a fallback test where the link has a path prefix (e.g. https://host/prefix/chart?...). While there, replace the as any casts with properly typed fixtures, per the repo conventions.
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Sorry, something went wrong.
There was an error while loading. Please reload this page.
At least 1 approving review is required to merge this pull request.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
Slack notifications for inline alerts fail when the chart-explorer link passes Slack's 3000-character block limit
There was an error while loading. Please reload this page.