Skip to content

Fix alert sample events for filtered searches - #3091

Closed
ryux1 wants to merge 39 commits into
hyperdxio:mainfrom
ryux1:agent/3059-alert-sample-filters
Closed

Fix alert sample events for filtered searches#3091
ryux1 wants to merge 39 commits into
hyperdxio:mainfrom
ryux1:agent/3059-alert-sample-filters

Conversation

@ryux1

@ryux1 ryux1 commented Sep 8, 2026

Copy link
Copy Markdown

Summary

Alert notification sample events were built separately from the saved-search chart configuration, so filters used to calculate the alert count were omitted when fetching example events. This reuses the shared search-config builder for the sample query and carries saved-search filters through. Grouped alerts constrain examples to the firing group through shared equality-filter rendering, including expression/Dynamic fields and string, numeric, boolean, or NULL values. Unsupported values and response columns that cannot be mapped safely are omitted individually; valid predicates still apply, and partial degradation has its own metric, log message, and notification marker outside the sample-results code block. If no group predicate can be applied safely, the notification now says that samples are unavailable instead of showing unrelated cross-group events. A failed ClickHouse sample query remains separately observable and does not prevent the notification from firing.

For builder alerts, response-tail group identification separates projected values from group columns without assuming one response column per configured select. Numeric group columns therefore remain in the persisted group-history key instead of replacing the selected alert value, including for saved-search and tile alerts when ratios collapse multiple selects or formulas change the emitted value shape. The renderer now exposes the group-projection layout it uses, and the alert metadata consumer reads that shared contract for disabled, scalar, and packed-histogram projections. This corrects the prior mis-evaluation; existing numeric-group alerts will establish the corrected group-history key on their first evaluation after deployment. An already-firing alert may emit one resolution for its legacy key during that transition: the old key is empty for a numeric-only group or lacks the numeric dimension for a composite group. Alerts requiring multiple consecutive windows must then accumulate the corrected key for the remaining windows before firing again. Raw SQL keeps its existing last-numeric-column behavior because its result shape is user-defined.

A separate saved-search metadata mapping restores configured expressions only for notification sample predicates. Direct columns and ClickHouse-rewritten string expressions are mapped safely; Date/DateTime groups and ambiguous rewritten numeric expressions degrade visibly instead of silently producing empty or cross-group samples. Grouped-only metadata is represented as a discriminated view, so ungrouped notifications cannot accidentally carry sample-filter attributes. The sample window remains aligned with alert evaluation, and search ordering is resolved by one common utility shared by app and server paths.

Regression tests cover saved and source predicates, expression-based group keys, escaping and typed values, NULL handling, partial and wholly unsupported group filtering, unmapped numeric and DateTime groups, numeric-group threshold selection and history keys, collapsed ratio projections, date-typed group versus time-bucket selection, disabled and packed group projections, separate sample-query failure reporting, value-column name collisions, and the ungrouped path. Docker-backed processAlert regressions exercise bracket-access grouping, numeric group projection, collapsed grouped ratios, and stable alert/resolve event IDs for a synthetic empty group through the real renderer and ClickHouse metadata.

How to test on Vercel preview

N/A — non-UI change.

Testing

  • yarn workspace @hyperdx/common-utils jest --runInBand src/__tests__/filters.test.ts src/core/__tests__/searchChartConfig.test.ts (249 tests)
  • yarn workspace @hyperdx/common-utils jest --runInBand src/__tests__/renderChartConfig.test.ts (190 tests, 54 snapshots)
  • yarn workspace @hyperdx/common-utils ci:build
  • yarn workspace @hyperdx/api tsc --noEmit
  • make ci-lint
  • yarn workspace @hyperdx/api jest --config jest.int.config.js --runInBand --ci --forceExit src/tasks/checkAlerts/__tests__/renderAlertTemplate.int.test.ts (109 tests, 72 snapshots)
  • yarn workspace @hyperdx/api jest --config jest.int.config.js --runInBand --ci --forceExit src/tasks/checkAlerts/__tests__/checkAlerts.int.test.ts -t parseAlertData (19 tests)
  • make ci-unit (all 5 project suites plus the release-notes and changeset-hash suites passed on the rebased head)

The integration suites listed above passed before the base reconciliation. On the rebased head, the shared-library build, TypeScript validation, full unit suite, lint gates, and focused 439-test common-utils regression set all pass. After the final event-ID follow-up, API TypeScript validation, API lint (300 baseline warnings, no errors), Prettier, and git diff --check pass. The focused integration command cannot start on this host because ClickHouse is unavailable at 127.0.0.1:18123; Docker is also unavailable, so hosted integration CI remains authoritative for that regression.

References

@changeset-bot

changeset-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a660bda

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@hyperdx/common-utils Patch
@hyperdx/api Patch
@hyperdx/app Patch
@hyperdx/otel-collector Patch

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

@vercel

vercel Bot commented Sep 8, 2026

Copy link
Copy Markdown

@ryux1 is attempting to deploy a commit to the HyperDX Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added external needs-vouch Author needs a maintainer to vouch for them labels Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Hi @ryux1, thanks for the pull request!

Before we review code from a first-time contributor we ask that a maintainer vouches for you, and you're not on our list yet. This PR stays open — it just isn't in the review queue until someone vouches.

To get vouched, open an issue saying hello and what you're working on:

https://github.com/hyperdxio/hyperdx/issues/new?template=introduce-yourself.md

A maintainer will usually reply within a day or two, and then this PR gets picked up as normal. More detail in our contributing guide.

@greptile-apps

greptile-apps Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR aligns alert notification samples with saved-search filtering and the firing group while improving alert-response metadata handling.

  • Reuses shared search configuration and ordering utilities for sample-event queries.
  • Preserves numeric group dimensions when identifying alert values and history keys.
  • Maps supported group expressions into typed sample predicates and fails closed when no safe group predicate remains.
  • Adds regression coverage for grouped samples, response layouts, unsupported predicates, and notification identity transitions.

Confidence Score: 5/5

The PR appears safe to merge based on the current review state and has no accepted new findings.

No new reportable issue remains. The matching prior empty-group-key finding was manually resolved, so it is not outstanding and is not reposted.

Important Files Changed

Filename Overview
packages/api/src/tasks/checkAlerts/index.ts Derives response metadata, separates alert values from group projections, and passes discriminated grouping data into notifications.
packages/api/src/tasks/checkAlerts/template.ts Builds filtered sample-event queries and visibly handles partial predicate degradation and query failures.
packages/common-utils/src/core/renderChartConfig.ts Exposes the rendered builder response layout for alert metadata consumers.
packages/common-utils/src/core/searchChartConfig.ts Centralizes alert-window bounds and search-order resolution used by app and server paths.
packages/common-utils/src/filters.ts Adds shared rendering for typed equality predicates, including NULL values.
packages/api/src/tasks/checkAlerts/tests/checkAlerts.int.test.ts Adds broad regression coverage for alert parsing, group keys, sample constraints, and event identity.
packages/api/src/tasks/checkAlerts/tests/renderAlertTemplate.int.test.ts Tests saved-search filters, typed group predicates, fail-closed behavior, and independent sample-query failures.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    A[Saved alert configuration] --> B[Shared search-config builder]
    B --> C[Alert evaluation query]
    C --> D[Response-layout metadata]
    D --> E[Alert value and group fields]
    E --> F[History and threshold evaluation]
    E --> G[Supported group predicates]
    B --> H[Notification sample query]
    G --> H
    H --> I[Filtered sample events]
    G -->|No safe predicate| J[Samples unavailable marker]
Loading

Reviews (38): Last reviewed commit: "fix: keep grouped alert event IDs stable" | Re-trigger Greptile

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Deep Review

No critical issues found. No P0/P1 defects: escaping in equalityFiltersToQuery routes values through escapeSqlString, the sample-fetch catch still fires the notification with a [Sample fetch failed] marker (and safely logs chartConfig while it is still undefined), the grouped-alert fallback ([Sample events unavailable for this group]) prevents any unfiltered cross-group query, and the documented numeric-group history-key migration is handled by the existing missing-group resolution path. The discriminated-union view typechecks because the ungrouped branch declares groupAttributes?: never.

🟡 P2 — recommended

  • packages/api/src/tasks/checkAlerts/__tests__/checkAlerts.int.test.ts:975 — the changeset dedicates a paragraph to the numeric-group history-key migration (one legacy-key resolution, then re-accumulation across consecutive windows), but no test exercises that stateful transition; only the parseAlertData key shape is covered.
    • Fix: Add an integration test that seeds a pre-upgrade ALERT-state history under the legacy key (empty for a numeric-only group; missing the numeric dimension for a composite group), runs processAlert with a numeric groupBy across consecutive windows, and asserts exactly one legacy-key resolution followed by re-accumulation before re-firing.
🔵 P3 nitpicks (5)
  • packages/api/src/tasks/checkAlerts/index.ts:1655 — rows whose timestamp value is null are silently skipped with no log or metric, so a mis-identified builder timestamp column (dateColumns.at(-1)) could drop every row and quietly under-evaluate an alert.
    • Fix: Emit a debug log or counter when a row is skipped for a null timestamp so whole-result drops are detectable.
  • packages/api/src/tasks/checkAlerts/template.ts:912sampleFetchFailuresCounter increments inside a catch that also wraps pre-query steps (equalityFiltersToQuery, buildSearchChartConfig, resolveSearchOrderBy), so a non-ClickHouse throw is mislabeled as a sample-fetch failure.
    • Fix: Scope the fetch-failure counter to the ClickHouse query call, or add a label distinguishing query failure from render/filter-build failure.
  • packages/api/src/tasks/checkAlerts/index.ts:487 — the grouped-alert union shape is defined three times (AlertNotificationGrouping in index.ts, the inline union in AlertMessageTemplateDefaultView, and the test helper); the object is spread across the module boundary and must be hand-synced.
    • Fix: Export one shared grouping union and reference it from both fireChannelEvent's local type and AlertMessageTemplateDefaultView.
  • packages/api/src/tasks/checkAlerts/template.ts:265 — the // should match the external alert schema comment on AlertMessageTemplateDefaultView no longer maps to any enforced schema (no external zod mirror exists) after the union refactor.
    • Fix: Remove or correct the stale comment.
  • packages/api/src/tasks/checkAlerts/index.ts:1735 — numeric group-by alerts now change the notification group label, attributes keys, and reported value, and can emit new [Some group filters could not be applied] / [Sample fetch failed] markers in the message body under a patch bump.
    • Fix: Confirm downstream webhook consumers that scrape {{group}} or the body tolerate numeric groups in the key and the new markers (behavior is documented in the changeset).

Reviewers (11): correctness (self-verified), security, reliability, performance, testing, api-contract, maintainability, project-standards, agent-native, previous-comments, learnings-researcher.

Testing gaps:

  • Numeric-group history-key migration transition is untested (P2 above).
  • OTEL degradation counters (sampleFetchFailuresCounter, sampleFilterFailuresCounter) are asserted only via rendered-text side effects, not the increments themselves.
  • No equalityFiltersToQuery test for group values containing single quotes/backslashes to lock in escapeSqlString behavior, nor a test asserting the [Sample events unavailable for this group] fallback issues no ClickHouse query.
  • Coverage note: the correctness, adversarial, and kieran-typescript reviewers did not return before synthesis; the core logic they targeted was verified directly against the diff and surfaced no P0/P1. Prior-comment evidence was truncated to ~30 of 168 threads, though every visible substantive concern is resolved in the current diff.

Comment thread packages/api/src/tasks/checkAlerts/template.ts Outdated
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

PR Review

3 finding(s): 🔴 0 critical · 🟠 0 major · 🔵 3 minor

3 posted as inline comment(s) on the changed lines.


Severity is the reviewer's own estimate and is used for ordering, not filtering.

Comment thread packages/api/src/tasks/checkAlerts/template.ts Outdated
Comment thread packages/api/src/tasks/checkAlerts/template.ts Outdated
@ryux1

ryux1 commented Sep 8, 2026

Copy link
Copy Markdown
Author

Addressed the latest deep-review follow-ups in fc3ff70: the explicit source-order test now uses SeverityText ASC, so it cannot pass through the derived timestamp fallback, and the sample path mirrors the count path by shallow-copying saved filters. The focused suite still passes 98 tests / 72 snapshots, and both make ci-lint and make ci-unit pass.

Comment thread packages/api/src/utils/searchOrderBy.ts Outdated
Comment thread packages/api/src/tasks/checkAlerts/template.ts Outdated
Comment thread packages/api/src/utils/searchOrderBy.ts Outdated
Comment thread packages/api/src/tasks/checkAlerts/template.ts Outdated
Comment thread .changeset/bright-alert-samples.md
Comment thread packages/api/src/tasks/checkAlerts/template.ts Outdated
Comment thread packages/api/src/utils/searchOrderBy.ts Outdated
@ryux1

ryux1 commented Sep 8, 2026

Copy link
Copy Markdown
Author

Added the requested trace-source regression in 22378f0. It verifies saved-search filter propagation and source ordering on the trace sample path; the focused suite now passes 100 tests and 72 snapshots.

Comment thread packages/api/src/tasks/checkAlerts/template.ts Outdated
Comment thread packages/api/src/utils/__tests__/searchOrderBy.test.ts Outdated
Comment thread packages/app/src/DBSearchPage.tsx Outdated
@ryux1

ryux1 commented Sep 8, 2026

Copy link
Copy Markdown
Author

Addressed the final review findings in d6ddd51. Group sample predicates now use shared equality-filter rendering, preserve numeric and NULL values through dispatch, and cover expression keys plus escaping. Resolver tests now live in common-utils, and the app delegates ordering entirely to the shared resolver. Focused suites, full lint/typecheck, and the full unit gate pass.

Comment thread packages/common-utils/src/filters.ts Outdated
Comment thread .changeset/bright-alert-samples.md
Comment thread packages/api/src/tasks/checkAlerts/template.ts Outdated
Comment thread packages/api/src/tasks/checkAlerts/index.ts Outdated
@ryux1

ryux1 commented Sep 8, 2026

Copy link
Copy Markdown
Author

Addressed the fresh review findings in 84e356b. Boolean and numeric group values now produce type-correct sample predicates, non-scalar values fail visibly, numeric group-by columns are separated from numeric alert values using response metadata, and sample-fetch failures emit a counter plus an explicit notification marker. The shared type, redundant branch, and app changeset were also cleaned up. All four new inline threads are resolved; focused common-utils tests, build, API typecheck, lint, and the isolated app rerun pass.

@ryux1

ryux1 commented Sep 8, 2026

Copy link
Copy Markdown
Author

Closed the remaining low-severity review items in f799802. Response-metadata coverage now pins multiple projected group columns including a numeric group, selectGroupBy=false is guarded, and non-stringified numeric equality filters remain numeric. The focused common-utils suites now pass 250 tests, with API typecheck and the repository ratchet also passing.

Comment thread packages/api/src/tasks/checkAlerts/index.ts Outdated
Comment thread packages/api/src/tasks/checkAlerts/template.ts Outdated
Comment thread packages/api/src/tasks/checkAlerts/index.ts Outdated
Comment thread packages/common-utils/src/filters.ts Outdated
Comment thread packages/api/src/tasks/checkAlerts/index.ts Outdated
@ryux1

ryux1 commented Sep 8, 2026

Copy link
Copy Markdown
Author

Addressed the latest Claude edge cases in d1f35d6. Histogram value-last metadata now fails safe without disabling alert evaluation; non-scalar group filtering is inside the guarded sample path so notifications still render; group data has one raw source of truth; and the unused helper mode plus unreachable branch are removed. All five threads are resolved. Focused common-utils tests, build, API typecheck, formatting, and the quality ratchet pass.

Comment thread packages/api/src/tasks/checkAlerts/index.ts
Comment thread packages/api/src/tasks/checkAlerts/template.ts
Comment thread packages/api/src/tasks/checkAlerts/index.ts
@ryux1
ryux1 force-pushed the agent/3059-alert-sample-filters branch from 2a426c2 to bcb72a9 Compare September 8, 2026 17:19
@ryux1

ryux1 commented Sep 8, 2026

Copy link
Copy Markdown
Author

Closed the latest review findings in bcb72a9. Alert response columns are now classified by configured group names instead of projection position, so numeric histogram groups cannot replace the actual metric value. Unsupported non-scalar group values are isolated per key with separate observability while valid group predicates still constrain samples. I also added a processAlert integration regression that verifies each firing group reaches its own sample query. All three new threads are resolved; lint, typecheck, 106 alert-template tests / 72 snapshots, and 11 focused metadata tests pass.

Comment thread packages/api/src/tasks/checkAlerts/template.ts Outdated
Comment thread packages/api/src/tasks/checkAlerts/index.ts Outdated
Comment thread packages/api/src/tasks/checkAlerts/index.ts Outdated
Comment thread packages/common-utils/src/filters.ts
Comment thread packages/api/src/tasks/checkAlerts/template.ts
@ryux1

ryux1 commented Sep 8, 2026

Copy link
Copy Markdown
Author

Completed all review follow-ups through 600be990. Group-response fallback now preserves full positional alignment across mixed types; numeric groups that cannot participate in the legacy group key never constrain notification samples. Unmapped expressions travel explicitly through alert evaluation into the template and are combined with unsupported runtime values, with no sentinel encoding. The partial-filter marker remains outside the sample code block; unreachable histogram/alias plumbing is removed; key stringification is shared; and real bracket-access grouping is covered by the Docker-backed regression.

All current review threads are resolved. The full lint/ratchet/OpenAPI gate passes; common build, API typecheck, 249 common tests, 108 alert-template tests / 72 snapshots, and 15 metadata tests pass.

@ryux1
ryux1 force-pushed the agent/3059-alert-sample-filters branch from 0fe8575 to df6e105 Compare September 9, 2026 11:55
@ryux1

ryux1 commented Sep 9, 2026

Copy link
Copy Markdown
Author

Rebased this onto current main and reconciled the overlapping alert-query changes from #3068. The shared buildSearchChartConfig sample path remains in place while the upstream sourceQuery behavior and expanded test fixtures are preserved. The rebase exposed a missing Filter type import; that is fixed in df6e1059.

On the rebased head, make ci-lint, make ci-unit, the common-utils build, API typecheck, and the focused common-utils regression suites all pass (439 tests, 54 snapshots). This host has no Docker executable, so I could not rerun the Docker-backed integration suites locally; hosted CI remains authoritative for those. All existing review threads remain resolved.

@ryux1

ryux1 commented Sep 9, 2026

Copy link
Copy Markdown
Author

Reviewed the rebased deep-review findings.

The grouped-resolution P2 does not apply: resolved alerts take the dedicated isAlertResolved(state) branch before the saved-search sample-fetch branch, so no group sample filtering runs for them. The grouped-resolution snapshots also pin the simplified Group: "…" - The alert has been resolved. output.

The migration-wording point was valid. 16ae7ebd now documents both legacy shapes: an empty key for numeric-only grouping and a composite key missing its numeric dimension. I updated the PR description to match. yarn lint:fix, the changeset hash suite, and yarn changeset status --since=origin/main pass; the expected patch package set is unchanged.

Comment thread .changeset/bright-alert-samples.md
Comment thread packages/api/src/tasks/checkAlerts/index.ts Outdated
Comment thread packages/api/src/tasks/checkAlerts/__tests__/checkAlerts.int.test.ts Outdated
// cleaning this up next. fireChannelEvent guards against null values
// for these properties.
const grouping: AlertNotificationGrouping =
hasGroupBy && group !== ''

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minorisGroupedAlert narrowed to "non-empty group", silently changing the webhook eventId for zero-fill alerts

isGroupedAlert used to be hasGroupBy (diff: - isGroupedAlert: hasGroupBy); it is now hasGroupBy && group !== ''. template.ts:645-654 feeds view.isGroupedAlert into the objectHash that produces eventId, which transports/generic.ts:89 exposes as the notification idempotency/dedup key. A grouped alert that fires on the empty-group zero-fill path (index.ts:1691-1703, exercised by the new uses one eventId when an empty grouped bucket alerts and resolves test) hashed with isGrouped: true before this change and isGrouped: false after, so an incident opened pre-upgrade is never closed by the post-upgrade resolve. Either keep hashing hasGroupBy (pass it separately from the grouping union) or add this to the .changeset/bright-alert-samples.md transition note alongside the group-key migration.


for (const checkData of checksData.data) {
const bucketStart = new Date(checkData[meta.timestampColumnName]);
const timestampValue = checkData[meta.timestampColumnName];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — New NULL/boolean/array group handling is unreachable per the declared row type

parseAlertData/getResponseMetadata were widened to AlertQueryRow = Record<string, unknown> and the diff adds real behaviour for non-string|number values — IS NULL predicates (filters.ts:118), toString(IsError) IN ('true'), unsupportedKeys for arrays, and this timestampValue == null skip. But the only production feed, ClickhouseClient.queryChartConfig (packages/common-utils/src/clickhouse/index.ts:624), still declares Promise<ResponseJSON<Record<string, string | number>>> while internally returning resp.json<any>(), so TypeScript treats every one of those branches as dead and only the direct-call unit tests reach them. Widen queryChartConfig's return to ResponseJSON<Record<string, unknown>> so the type matches what ClickHouse actually returns and the new handling is type-visible to future readers.

// Builder SELECT order ends with the rendered time bucket, after any group
// columns (which may themselves be Date/DateTime). Raw SQL keeps its
// established first-date-column convention because its shape is user-defined.
const timestampColumnName = isBuilderChartConfig(chartConfig)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — The raw-SQL half of the new first-vs-last date-column split is untested

timestampColumnName now diverges by config kind — dateColumns.at(-1) for builder configs, dateColumns[0] for raw SQL — and the value-column exclusion is likewise gated on isBuilderChartConfig. Every new getResponseMetadata test goes through makeMetadataChartConfig, which always produces a builder config, and the existing raw-SQL fixtures (RAW_SQL_ALERT_TEMPLATE, fixtures.ts:803) project exactly one date column, so nothing distinguishes first from last on the raw-SQL branch. Add a getResponseMetadata case with a raw-SQL config whose meta has two date columns (e.g. ts then min(Timestamp) AS firstSeen) asserting timestampColumnName === 'ts', plus one asserting a numeric group column is still treated as the value column, so the deliberately-preserved raw-SQL behaviour documented in the changeset is pinned.

@pulpdrew

pulpdrew commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Numeric group columns now remain part of the alert history key instead of
being mistaken for the threshold value. On the first evaluation after this
upgrade, an already-firing numeric-group alert may emit one resolution for its
legacy group key before emitting notifications under the corrected keys. The
legacy key is empty for a numeric-only group, or lacks the numeric dimension
for a composite group. Alerts configured with multiple consecutive windows
must then accumulate the corrected key for the remaining windows before they
can fire again (for example, two more windows when configured for three).

Hi @ryux1 , is this change required in this PR? It seems unrelated to #3059 , and in general this PR seems to make a lot of changes beyond the scope of the issue.

@pulpdrew

Copy link
Copy Markdown
Contributor

Superceded by #3106

@pulpdrew pulpdrew closed this Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external needs-vouch Author needs a maintainer to vouch for them

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Alert notification sample events ignore saved-search filters — alerts show unrelated log lines as evidence

2 participants