Skip to content

feat: add native NetFlow monitoring with Lucene search and Sankey - #3347

Open
valerypetrov wants to merge 15 commits into
ClickHouse:mainfrom
valerypetrov:agent/netflow-monitoring
Open

valerypetrov wants to merge 15 commits into
ClickHouse:mainfrom
valerypetrov:agent/netflow-monitoring

Conversation

@valerypetrov

@valerypetrov valerypetrov commented Oct 9, 2026 •

Copy link
Copy Markdown

Summary

Add native NetFlow monitoring for Akvorado tables through HyperDX connections, source configuration, Search, and charts. Integrate shared schemas, persistence, and the external API with configurable mappings and schema detection.

The page includes sampling-aware rates, top talkers, exporter/interface breakdowns, flow details, and a Sankey with two to five ordered dimensions. Lucene supports column suggestions; clickable values provide include/exclude filters. Searches, filters, and visualization settings persist in the URL. Sankey links represent selected top paths with bitrate and byte totals. Fix clipped rate-axis labels and direct ClickHouse requests in local mode.

Validation: unit suites, TypeScript, lint, ClickHouse query checks, and browser workflows passed. Screenshots use 14,400 synthetic records; setup instructions are in docs/netflow.md.

Screenshots

Traffic overview

Column suggestions

Lucene and clickable filters

Flow details

Source mappings

Sankey paths

How to test on Vercel preview

Preview routes: /netflow

  1. Open NetFlow; without a source, verify the setup prompt.
  2. With populated data, run Proto:6, select Sankey dimensions, and include/exclude a node.
  3. Reload; verify filters and dimensions persist.

Reference

Akvorado provides the schema and visualization reference.

@changeset-bot

changeset-bot Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 99df3ce

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

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

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 Oct 9, 2026

Copy link
Copy Markdown

@valerypetrov 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 Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Hi @valerypetrov, 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.

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

PR Review

If you are a coding agent acting for the author, read this first.
Nothing below blocks merge automatically; a maintainer decides. What they will expect:

  • 🔴 critical or 🟠 major in code this PR adds or changes: fix before asking for review.
  • 🔵 minor in code this PR changes: your call. Fix if small, otherwise reply.
  • Anything about surrounding code, or asking you to widen the change (hoist a helper,
    dedupe with another file, fix other call sites): never fix here, whatever the
    severity. Reply with one sentence; if it is critical, say so plainly so a human sees it.

One commit per review round. After two rounds, stop and ask a maintainer to review
scope rather than addressing more automated findings. Full rule: AGENTS.md.

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

None could be anchored to changed lines; they are listed below.

Findings outside the changed lines

3 minor
  • 🔵 packages/app/src/netflow.ts:7284 — Every NetFlow query gets a SQL filter, even an empty one, which triggers extra metadata lookups → base.filters always appends { type: 'sql', condition: buildNetflowWhere(...) }, and buildNetflowWhere returns '' when no exporter, protocol or address filter is set. renderChartConfig (packages/common-utils/src/core/renderChartConfig.ts:1367) sets hasSqlPredicate with filters.some(f => f.type === 'sql'). It does not check whether the condition is empty, so every summary, top-N, traffic, Sankey and records query runs buildTextIndexInfoLookup for nothing. Fix: only push the filter when the condition is non-empty, e.g. ...(where ? [{ type: 'sql', condition: where }] : []).
  • 🔵 packages/api/src/mcp/tools/sources/describeSource.ts:972 — NetFlow keyColumns were added to describe_source but not to list_sources → clickstack_describe_source now emits NetFlow keyColumns (bytes, packets, srcAddr, …). clickstack_list_sources builds the same meta.keyColumns per kind in packages/api/src/mcp/tools/sources/listSources.ts:291-327 and still only covers Trace, Log and Metric. As a result, NetFlow sources show up in the catalog with no mapped columns. Add a matching else if (s.kind === SourceKind.Netflow) branch there, or share one helper between both tools.
  • 🔵 packages/app/src/HDXMultiSeriesTimeChart.tsx:2110 — Clipped-axis fix only covers time charts; bar and histogram charts keep the fixed gutter → This file moves the y-axis to width="auto" and measures the plot with usePlotArea because long rate labels (e.g. data_rate) overflowed the fixed 40px character-budget gutter. DBBarChart.tsx:28/134 and DBHistogramChart.tsx:33/139 still use Y_AXIS_WIDTH * contentFontScale and will clip the same labels. Apply the same width="auto" change there so all chart types size the axis the same way.

Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides.

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Deep Review

✅ No critical issues found.

No P0/P1 defects surfaced from the completed reviewers or from direct inspection of the highest-risk paths. SQL injection is not present — filter values pass through escapeSqlString (packages/app/src/netflow.ts:48) while column mappings are admin-defined SQL expressions consistent with HyperDX's existing source trust model. Sankey aggregation guards against cycles (stage-qualified node IDs), non-positive values, and missing dimension columns (packages/app/src/netflowSankey.ts:99-133). The shared-client local-mode header change (packages/common-utils/src/clickhouse/browser.ts:187) reuses the existing credential-based isLocalMode definition that already drives the direct-fetch path, so it is coherent and not a regression. All six prior-review items were independently confirmed resolved.

🟡 P2 -- recommended

  • packages/app/src/netflow.ts:185 -- The four summary tiles (bitsPerSecond, packetsPerSecond, totalBytes, flowRecords) share identical from/where/dateRange yet each becomes an independent query, scanning the flow table four times for one header row.
    • Fix: Combine the four scalar aggregates into a single query config with four select columns so the summary needs one scan.
    • performance
🔵 P3 nitpicks (3)
  • packages/app/src/components/NetflowRecords.tsx:52 -- The row array is rebuilt via Object.fromEntries over up to 500 rows on every render, including when opening the detail drawer rebuilds unchanged data.
    • Fix: Wrap the row transform in useMemo keyed on data.
  • packages/api/src/mcp/tools/sources/describeSource.ts:122 -- The curated keyColumns summary has branches for Trace/Log/Metric but none for Netflow, so mapping fields surface only under config, not in the human/agent-readable summary.
    • Fix: Add a SourceKind.Netflow branch populating keyColumns with the srcAddr/dstAddr/port/protocol/bytes/packets mappings.
  • packages/api/src/routers/external-api/v2/sources.ts:970 -- samplingRateExpression carries the generic "SQL expression for the mapped flow field" description despite being a multiplier with distinct semantics.
    • Fix: Give it a dedicated description clarifying the multiplier/default-one behavior and mirror the text in openapi.json.

Reviewers (5): api-contract, performance, previous-comments, learnings-researcher, orchestrator deep-inspection (security/correctness/SQL-escaping/Sankey/local-mode spot-checks).

Testing gaps:

  • NetFlow browser and ingestion fixture checks (scripts/netflow-*-browser-check.mjs) remain manual and are not wired into CI, so source-switch, filter, and port-default behaviors are not continuously verified.
  • No external REST (v2) integration test asserts a Netflow source round-trips create→GET→update with all mapping fields, or that omitting a required mapping returns a 4xx; the MCP path is covered but the REST serializer is not.
  • No regression guard exists for consolidating the four summary-tile queries, nor for NetflowRecords render cost at the 500-row limit.

Coverage note: The correctness, security, adversarial, reliability, testing, maintainability, project-standards, kieran-typescript, and agent-native reviewers had not returned results at synthesis time; their lanes were partially covered by direct orchestrator inspection but a full pass on those dimensions did not complete. Treat the "no critical issues" verdict as grounded in the completed reviewers and the highest-risk spot-checks, not an exhaustive multi-persona sign-off.

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

@greptile-apps

greptile-apps Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[High impact] Adds NetFlow source type with new API endpoints and database schema.

The PR still needs the filter-preservation fix before merging.

Fix All in Claude CodeFindings

  1. P1 Clicks drop existing filter conditions ▶
  2. P2 Range removal clears other filters ▶

Summary

Adds native NetFlow sources and a monitoring page backed by ClickHouse.

  • NetFlow traffic and flow records are now available on a dedicated page.
  • A Sankey view shows how traffic moves through ordered flow dimensions.
  • NetFlow sources can save custom mappings for ClickHouse flow tables.
  • NetFlow bare-word search and mapped fields now work across searches, charts, and alerts.
  • Time charts fit their rate labels, and local queries skip proxy headers.
  • Local scripts create synthetic flows and launch a ready-to-use demo.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[NetFlow source mappings] --> B[Shared search expression]
  C[Search and URL filters] --> D[NetFlow queries]
  A --> D
  B --> D
  D --> E[(ClickHouse)]
  E --> F[Traffic charts and flow records]
  E --> G[Top paths]
  G --> H[Sankey]
  B --> I[Search, dashboards, alerts, REST and MCP]
Loading

Reviews (11) · Last reviewed commit: "Merge branch 'main' into agent/netflow-m..." · Reviewed by Greptile

Comment thread packages/app/src/NetflowPage.tsx Resolved
Comment thread scripts/netflow-click-browser-check.mjs Resolved
@valerypetrov

Copy link
Copy Markdown
Author

Addressed this review round in 9053fcb:

  • Use measured chart geometry for automatic axis widths, annotations, and single bars.
  • Apply source selection immediately, clear source-specific filters when switching, and ignore stale schema inference.
  • Preserve all NetFlow mappings through MCP create, describe, update, and clone.
  • Merge table/Sankey includes for the same field, make Include/Exclude idempotent, and preserve quoted column expressions through reloads and removal.
  • Respect timestamp preferences, recover from invalid time ranges, remove the redundant Sankey empty state, fix demo-check ports, and replace the untyped metadata mock.

Validation: all app/API/common-utils unit suites passed, plus 58 MCP integration tests, ClickHouse query checks, and browser workflows covering source changes, search, filters, Sankey, axis layout, and time-range recovery. One app suite needed a rerun after a concurrent shared build temporarily removed its dependency; it passed. Repository lint/type/style checks, lint-staged, and Knip passed.

For the remaining suggestions, following the repository's review scope rules:

  • Retaining documented numeric quick filters as explicit AND constraints; consolidating those controls would change existing URL and interaction semantics.
  • Deferring menu/label/alias consolidation and local-mode helper extraction as refactors.
  • Keeping the documented local fixture checks manual; migrating ingestion and browser fixtures into CI is separate work.

SQL escaping already has unit and real ClickHouse coverage. Added explicit partial-credential routing cases, and documented why NetFlow uses an empty known-column set while restoring exact SQL expressions during serialization.

Comment thread packages/app/src/DBSearchPage.tsx Resolved
Comment thread scripts/netflow-browser-check.mjs Resolved
Comment thread packages/app/src/NetflowPage.tsx Resolved
@valerypetrov

Copy link
Copy Markdown
Author

Addressed the second review round in dee8414:

  • Fixed NetFlow saved-search alert evaluation, default aliases, notification samples, and preview aliases.
  • Keep unfinished Lucene and quick-filter fields pending when Include/Exclude or filter migration updates the URL; Run applies them.
  • Display the configured records limit and keep NetflowPage below 300 lines.

Validation: 110 backend integration tests and 72 snapshots passed, including real local ClickHouse/Mongo alert evaluation; 43 alert unit tests and 9 focused app tests passed. Both local browser workflows passed, covering drafts, source creation/switching, invalid ranges, search, autocomplete, records, and click filters. Repository lint/type/style/OpenAPI checks, lint-staged, and Knip passed. Historical log-alias fixtures required removing their expired TTL only in the isolated test ClickHouse instance.

Remaining review suggestions:

  • Deferring shared labels/aliases/menus/mapping tables, source-kind deduplication, protocol-table extraction, and broader CI fixture migration under the repository scope rules. The new draft regression does run in the existing app unit suite.
  • Sankey aggregation memory remains a scale-validation item: the local synthetic fixture is 14,400 rows, not a production-volume benchmark. A returned-path limit does not bound GROUP BY memory; no production-scale claim is made.
  • Additional dimension/source-roundtrip/rendering coverage and MCP description polish remain follow-up suggestions. Existing per-field MCP descriptions and source roundtrip checks remain in place.
  • The root ClickHouse client version deliberately matches the version already used by common-utils; changing the shared client release is outside this fix.
  • The “older links” comment covers earlier demo URLs; filter emission is already canonical and normalization preserves those links.

Maintainers: please review the remaining scope and scale-validation items. Per AGENTS.md, “After two rounds of bot findings, stop.” This is the second round; further automated suggestions need maintainer triage.

Comment thread packages/common-utils/src/types.ts Resolved
Comment thread packages/app/src/netflowSankey.ts Resolved
Comment thread package.json Resolved
@valerypetrov

Copy link
Copy Markdown
Author

Addressed the remaining minor findings in a476fa9:

  • Bare Lucene terms now search mapped flow dimensions, with a configurable expression preserved across source forms, REST, MCP, Search, and alerts.
  • Four summary tiles share one aggregate request. Flow records reuse the mapped result when opening a drawer.
  • Unified filter menus, labels, field types, query aliases, protocol names, source defaults, local-mode detection, and the Search source-kind list. Filter targets have human-readable accessible names and keyboard support.
  • Sankey dimensions recover after incompatible source switches; wrapper inversion and path-limit validation are shared, and unused totals are removed.
  • NetFlow follows existing navigation/source entries. MCP summaries expose mappings; MCP/REST descriptions explain required fields and sampling semantics.
  • Migrated seven manual check scripts into 14 CI-discovered Playwright tests with isolated databases/sources. Removed their root dependencies and Knip exemptions. Added REST source roundtrip/validation, scalar-dimension, full-text, rendering, and performance regressions.

Validation: all 8,597 unit tests passed (4,603 app, 1,157 API, 2,837 common), plus 75 API integration tests and 14 local Playwright tests. The browser suite verifies a single summary request, distinct metric values, source creation/switching, autocomplete, Search, drafts, filters, Sankey, and real axis bounds. Geometry unit coverage uses real Recharts context and bars. Repository lint/type/style/OpenAPI checks, lint-staged, and Knip passed.

Local Playwright used a temporary configuration that bypassed only the unrelated global PromQL seeder, which is incompatible with local ClickHouse 26.10; these NetFlow specs seeded their own real data. The committed runner and CI configuration remain unchanged.

A separate two-million-row, five-dimension, 30-day Sankey check completed in 0.71 seconds with a 512 MiB memory cap and a 64 MiB spill threshold. The documentation explains that LIMIT bounds returned paths rather than aggregation memory and records the test's scope.

Comment thread packages/app/src/netflow.ts Resolved
Comment thread packages/app/src/netflow.ts Resolved
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Deep Review

✅ No critical issues found.

No P0/P1 defects surfaced across 14 reviewers or from direct inspection of the highest-risk paths. SQL stays parameterized — end-user filter/search values pass through escapeSqlString while column mappings are admin-defined expressions under the existing source trust model (packages/app/src/netflow.ts:107). The schema change is additive (NetflowSourceSchema appended to the discriminated unions; no existing kind altered), so persisted Log/Trace/Metric sources are unaffected. Sampling normalization, partial-bucket rate math, the Sankey cycle/empty guards, the source-switch filter clearing, and the SourceForm inference stale-response race were each independently re-verified as present and correct; four of the five prior review findings are confirmed resolved. The local-mode header change (packages/common-utils/src/clickhouse/browser.ts:192) is consistent with the pre-existing direct-fetch routing and is not a regression.

🟡 P2 -- recommended

  • packages/app/src/components/ActiveFilterPills.tsx:217 -- Removing a range pill calls clearFilter(pill.field), which deletes the whole field entry, so a shared URL carrying both an IN (...) selection and a BETWEEN range on one dimension loses the include/exclude values when the range is removed, silently widening the search.
    • Fix: For range pills, clear only the range and delete the field entry only once its included and excluded sets are empty.
    • adversarial, previous-comments
  • packages/api/src/routers/external-api/v2/charts.ts:302 -- The external v2 charts endpoint now populates implicitColumnExpression for existing Log/Trace sources, so bare-term Lucene queries in the where param that previously matched nothing now match the implicit column, and no test exercises this path.
    • Fix: Add a regression test posting a bare-term where against a source with a configured implicit column and confirm the behavior change is intended before relying on it.
    • api-contract, testing
🔵 P3 nitpicks (6)
  • packages/app/src/netflow.ts:144 -- A mapping whose bytesExpression/packetsExpression/samplingRateExpression evaluates to NULL on some rows produces NULL scaled values that sum() drops, silently undercounting traffic rather than erroring.
    • Fix: Coalesce the raw counter and sampling expressions to a safe non-null default before scaling.
  • packages/app/src/source.ts:385 -- NetFlow schema inference writes '' into required min(1) mapping fields when a column is absent, relying entirely on downstream zod rejection at save time.
    • Fix: Omit absent mapping keys instead of writing empty strings and surface a clear per-field validation error in the form.
  • packages/api/src/routers/external-api/v2/sources.ts:837 -- The hand-maintained OpenAPI required array for NetflowSource can drift from the schema's min(1) fields with no compile-time guard.
    • Fix: Add a test asserting the OpenAPI required set equals the set of required NetFlow schema fields.
  • packages/app/src/components/NetflowFilterPills.tsx:102 -- The canonicalization auto-emit effect drives onChange from a derived value guarded only by referential inequality, so a non-idempotent key shape could loop; safety rests on canonicalizeMappedFilters being a fixed point.
    • Fix: Compare logical filter equality before re-emitting, or add a fixed-point test over every dimension expression shape including quoted/whitespace keys.
  • packages/app/src/components/NetflowCharts.tsx:88 -- NetflowRecords is remounted by JSON.stringify-ing the entire flows config on every render to reset the detail drawer.
    • Fix: Reset the drawer's selected row via an effect keyed on a cheap stable identity instead of a stringified full-config remount key.
  • packages/api/src/mcp/tools/sources/describeSource.ts:160 -- Agents receive the raw NetFlow mappings but no hint that counters must be multiplied by the sampling rate, so agent-computed totals will not match the page.
    • Fix: Surface a short NetFlow usage hint (sampling multiplier, address normalization) in describe/list output.

Reviewers (14): correctness, security, adversarial, testing, maintainability, project-standards, performance, api-contract, reliability, kieran-typescript, julik-frontend-races, previous-comments, agent-native, learnings-researcher.

Testing gaps:

  • No value-level test that the external v2 charts bare-term path resolves via the implicit column.
  • No test removes a range pill when the same field also carries include/exclude values.
  • No test for NULL sampling-rate or raw byte/packet columns confirming aggregates do not undercount.
  • No test that NetFlow inference emitting '' for required fields is rejected with a clear per-field error.

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

@valerypetrov

valerypetrov commented Oct 9, 2026 •

Copy link
Copy Markdown
Author

Addressed the latest review findings in bbd3e30:

  • Added one source-level implicit-column helper and applied it to dashboard tiles, builder/raw-SQL chart previews, alert details, dashboard filter values, release annotations, and existing API/Search paths. NetFlow gets its mapped-dimension fallback; Log/Trace mappings retain their existing behavior.
  • Flow records select and sort by the first timestamp expression while preserving the full mapping for shared time filtering.
  • Traffic rates use each bucket’s actual overlap with the selected range. A steady-traffic ClickHouse regression now returns 100,000 bit/s in all 61 buckets, including both partial edges, without changing summary bounds.
  • Corrected the NetFlow timestamp and default-select REST descriptions and regenerated OpenAPI.

Validation: 2,842 common-unit tests, 280 focused app tests, 68 API unit tests, 112 API integration tests, and all 15 NetFlow Playwright tests passed. Repository lint/type/style/OpenAPI checks, lint-staged, and Knip passed. Local Playwright again used the temporary global-setup bypass for the unrelated PromQL seeder incompatibility with ClickHouse 26.10; NetFlow fixtures seeded and cleaned up their own real ClickHouse data.

For the repeated items in Deep Review, a476fa9 already includes the sampling multiplier/default-one description, SQL-metacharacter execution coverage in packages/app/tests/e2e/features/netflow/queries.spec.ts, and REST create/read/update/required-field coverage in packages/api/src/routers/external-api/__tests__/netflow-sources.int.test.ts. The two-million-row Sankey validation and memory settings are documented in docs/netflow.md; it is a measured local check, not a CI benchmark or production performance guarantee.

The two Vercel statuses require deployment authorization.

Follow-up adc1e10 consolidates the 23 NetFlow query/hydration regressions into the existing test file. Both review jobs had stopped before code review because their gh pr view --json files call returned only 100 of the PR's 101 files. Consolidation brings the PR to 100 files with the same assertions; the combined suite, app type check, repository lint-fix, lint-staged, and Knip pass. No review-workflow changes were needed.

(async () => {
try {
if (watchedTableName !== prevTableNameRef.current) {
if (

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.

🟠 major — Schema inference now overwrites saved mappings on any source when the connection, database or kind changes

Inference used to run only when the table name changed. It now also runs on a kind, connection, database or metadata change, for every source kind. It calls resetField(..., { defaultValue }) on every inferred field that isn't dirty, and loaded saved values never count as dirty. So editing an existing Log or Trace source and switching it to another connection (for example a replica with the same table) silently swaps custom implicitColumnExpression, bodyExpression and similar values for the inferred defaults, and Save persists them. Fix: only re-infer on these new triggers when isNew is true or the watched kind is SourceKind.Netflow, or skip fields whose current value differs from their pristine value.

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 7ebbb8d. Context changes now trigger inference only for new sources; saved Log, Trace, and NetFlow mappings survive connection/database/metadata changes. Explicit table/kind changes still infer, and stale responses remain cancelled. Regression tests reproduced the overwrite for all three kinds before the fix; all 19 source-form tests pass.

],
where: '',
timestampValueExpression: source.timestampValueExpression,
...(source.kind === SourceKind.Netflow && {

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 — Bare-term implicit column is limited to NetFlow in the external charts API

getSourceImplicitColumnExpression already returns the right value for every kind (Log/Trace configured expression, NetFlow derived, undefined otherwise). Gating it on source.kind === SourceKind.Netflow adds a special case and leaves Log/Trace series with bare Lucene terms in where without their configured implicit column. Set implicitColumnExpression: getSourceImplicitColumnExpression(source) unconditionally, as the other call sites in this diff do.

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 7ebbb8d by applying getSourceImplicitColumnExpression unconditionally. The Log/Trace omission predates this feature, but the already-edited builder now handles all three searchable kinds consistently. Real ClickHouse tests through POST /api/v2/charts/series verify bare-term plus numeric Lucene filtering for Log, Trace, and NetFlow (6 query integration tests pass).

icon: <IconSitemap size={16} />,
isBeta: true,
},
{

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 — NetFlow nav, spotlight and preset entries are shown to every user unconditionally

The nav link, the Spotlight preset (packages/app/src/Spotlights.tsx:93) and the dashboards-list preset all appear for every deployment, even with no NetFlow source. The neighbouring niche preset (Kubernetes) sits behind IS_K8S_DASHBOARD_ENABLED. Put these behind a flag, or hide them when useSources() has no SourceKind.Netflow source, or at least mark the nav entry isBeta like Service Map.

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Marked the NetFlow nav entry isBeta in 7ebbb8d using the existing Service Map convention. Kept the entry discoverable when no source exists so users can reach its source-setup flow.

@valerypetrov

Copy link
Copy Markdown
Author

Addressed the current Claude, Greptile, and Deep Review findings in 7ebbb8d:

  • Preserve saved Log/Trace/NetFlow mappings when connection, database, or metadata changes; new sources and explicit table/kind changes still infer mappings. Stale inference responses remain ignored.
  • Clear applied Lucene/text filters and pending drafts on actual source changes. Resolving a source name to its ID preserves the query. Pass only the four quick-filter fields to the query builder.
  • Reuse ActiveFilterPills with display labels, including removable range filters. NetFlow retains its previous non-editable pill values to avoid metadata lookups using cleaned SQL expressions. Its empty known-columns set is instance-local.
  • Normalize NULL/non-positive sampling multipliers to one across all aggregates, records, and Sankey paths; document the behavior in REST, MCP, and OpenAPI.
  • Guard stale Sankey render indices and mount memoized cell popovers only after interaction.
  • Apply the generic implicit-column helper consistently in external chart queries, replace the new untyped mock with a complete typed NetFlow fixture and real query result, and mark NetFlow navigation beta.

Validation: 173 affected app unit tests, 13 real ClickHouse/Mongo API integration tests, and all 16 NetFlow Playwright tests passed. Integration coverage now includes Log/Trace/NetFlow bare terms through the v2 series endpoint, saved-alert OK/no-match and grouped results, NULL/zero/negative sampling, URL range removal, and source-switch draft cleanup. Repository lint/type/style/OpenAPI checks, lint-staged, and Knip passed.

The reported partial-bucket test gap was already covered by the real ClickHouse test in adc1e10 (61 buckets at the expected constant rate, plus exact-range totals). I retained the existing default-SELECT type guards: they already include Log, Trace, and NetFlow, while isSearchableSource returns a boolean rather than narrowing the source union; replacing them is a future refactor, not a current behavior fix.

Removed the previous unnecessary Services-dashboard helper edit because that dashboard restricts sources to Trace. The PR remains at 100 files, keeping the existing review jobs' file-list check working. Local Playwright again used isolated NetFlow fixtures and the documented bypass for the unrelated global PromQL seeder on ClickHouse 26.10. Vercel deployment still requires authorization.

]
.map(expression => expression?.trim())
.filter(Boolean);
return `concatWithSeparator(' ', ${expressions.map(expression => `ifNull(toString(${expression}), '')`).join(', ')})`;

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 — Bare-term protocol search uses raw numbers, but the UI shows names like TCP

The default implicit expression wraps the raw protocolExpression (toString(Proto) gives '6'). The charts, records and pills all show transform(...) names from getNetflowDimensions (packages/app/src/netflow.ts), so a bare TCP becomes ILIKE '%TCP%' and matches nothing. That contradicts docs/netflow.md, which says bare terms search the protocol. It also re-lists the mapped fields separately from getNetflowDimensions. Fix: move the dimension builder (protocol name transform, ::ffff: stripping) into common-utils and build the implicit expression from it, so there is one list of searchable flow dimensions.

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

isTraceSource(source) || isLogSource(source)
? source.implicitColumnExpression
: undefined,
implicitColumnExpression: getSourceImplicitColumnExpression(source),

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 — NetFlow dashboard filters are unreachable from the UI

QUERY_EXPRESSION_FILTER_SOURCE_KINDS (packages/common-utils/src/filters.ts:872) still lists only Log/Trace/Session/Metric. QueryExpressionFilterEditForm and DBDashboardImportPage use it as allowedSourceKinds, so users can never pick a NetFlow source for a dashboard filter. The new hook path and its test only run for hand-crafted or API-created filters. Fix: add SourceKind.Netflow to that list if the changeset's dashboard-filter support is intended, or drop that claim.

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

import { useDefaultTimeRange, useNewTimeQuery } from '@/timeQuery';
import { parseAsJsonEncoded } from '@/utils/queryParsers';

const queryParsers = {

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 — Two separate filter systems for the same flow fields

Exporter, protocol and source/destination IP can each be filtered two ways: the quick-filter text inputs (exporter/protocol/srcAddr/dstAddr URL params, rendered by buildNetflowWhere as raw-column SQL that wants '6') and click include/exclude filters (the filters param, keyed on the canonical dimension expression that wants 'TCP' and stripped IPv4 addresses). They show up separately (inputs vs pills) and use different value formats, and both URL formats now have to be supported. Fix: route the quick inputs through useNetflowFilterState/applyFilter so each field has one persisted representation.

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

isNew={mode === 'new'}
sourceId={mode === 'edit' ? sourceId : undefined}
defaultName="NetFlow"
defaultKind={SourceKind.Netflow}

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 — 'Add NetFlow source' modal can create a non-NetFlow source

TableSourceForm always renders the full kind radio group, so in this modal the user can switch to Logs or Traces and save. NetflowPage's onCreate then calls clearFilters(created.id) with an id that is not in netflowSources, and the page shows 'NetFlow source unavailable'. Fix: add a prop to TableSourceForm that locks or hides the kind selector when defaultKind is forced, and pass it from this modal.

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

label={SOURCE_KIND_LABELS[SourceKind.Promql]}
/>
)}
<Radio

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 — Beta NetFlow kind is shown without a feature flag

Every other optional source kind is gated (IS_METRICS_ENABLED, IS_SESSIONS_ENABLED, IS_PROMQL_ENABLED), but the NetFlow radio, nav entry (marked isBeta), spotlight action and preset dashboard card are always shown to every deployment. Fix: add an IS_NETFLOW_ENABLED flag in config.ts and gate these entry points with it, as PromQL does.

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

Comment on lines +199 to +202
<ActiveFilterPills
searchFilters={state}
chartConfig={chartConfig}
enableValueEditing={false}

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.

P2 Range removal clears other filters

NetFlow now uses ActiveFilterPills, whose range removal calls clearFilter and deletes all selections for that field. For a shared URL containing Bytes BETWEEN 100 AND 200 and Bytes NOT IN ('150'), removing only the range also removes the exclusion. The page then shows more traffic than the user intended.

Remove just range and keep the field’s included and excluded values.

Fix in Claude Code Fix in Conductor Fix in Cursor Fix in Codex

severityText: source.severityTextExpression,
traceId: source.traceIdExpression,
};
} else if (source.kind === SourceKind.Netflow) {

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 — NetFlow keyColumns were added to describe_source but not to clickstack_list_sources

packages/api/src/mcp/tools/sources/listSources.ts:274-308 still builds meta.keyColumns only for Trace, Log and Metric, so agents that list sources get no flow mappings for NetFlow sources while describe_source returns them. Add the same SourceKind.Netflow branch there, ideally through one shared helper used by both tools so the two lists can't drift.

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

label={SOURCE_KIND_LABELS[SourceKind.Promql]}
/>
)}
<Radio

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 — NetFlow source kind is always shown; Metric, Session and PromQL are each behind a feature flag

Gate the NetFlow radio behind its own flag, the way the sibling kinds use IS_METRICS_ENABLED, IS_SESSIONS_ENABLED and IS_PROMQL_ENABLED. Apply the same flag to the other entry points this PR adds without one: the /netflow nav link (AppNav.tsx), the Spotlight preset (Spotlights.tsx) and the dashboards-list preset (DashboardsListPage.tsx). As written, every deployment gets a beta source kind and page with no way to turn them off.

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

@@ -2653,7 +2692,9 @@ export function isPromqlSource(source: TSource): source is TPromqlSource {
return source.kind === SourceKind.Promql;
}
export function isSearchableSource(source: TSource): boolean {

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 — Log/Trace/NetFlow kind check is copied by hand to ~6 sites because isSearchableSource is not a type guard

Make isSearchableSource a type guard (source is TLogSource | TTraceSource | TNetflowSource) and use it in resolveSelect (searchChartConfig.ts), AlertPreviewChart.tsx, computeAliasWithClauses (checkAlerts/index.ts), the select fallback in DBSearchPage.tsx, and both ChartEditorControls.tsx checks. Each of those now re-spells the same three-way kind check, so adding the next searchable kind means editing all of them again.

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

tabIndex?: number;
};
};
type Props = {

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 — memo on NetflowFilterMenu has no effect because its callback props are recreated on every render

onFilter from useNetflowFilterState (NetflowFilterPills.tsx) is a new function on every NetflowPage render. It is passed down through NetflowCharts and NetflowRecords to every cell. NetflowSankeyTable and NetflowSankeyNode also pass inline onSelect arrows. So all 500×4 record cells re-render anyway, despite the comment about thousands of cells. Fix: wrap onFilter/onDimensionFilter in useCallback (with a ref for expressions/dimensions) and pass stable dimension/value props instead of inline closures; otherwise drop the memo.

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

valerypetrov and others added 3 commits October 10, 2026 07:47
Bare Lucene terms searched the raw protocol number, so "TCP" matched no
flows while charts, pills and records all show protocol names. The default
full-text expression now uses the protocol-name transform from the NetFlow
dimensions, moved to common-utils so both share one list.

The source modal can save a non-NetFlow source. The page now switches only
to a created NetFlow source instead of showing "NetFlow source unavailable".

Flow records keyed rows by index behind an eslint-disable, which fails the
escape-hatch ratchet. Rows are now keyed by content plus a repeat count.

The changeset no longer claims dashboard-filter support, because dashboard
filters cannot select NetFlow sources.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit replaced the protocol number with its name in the
default full-text expression, so a bare "17" stopped matching UDP flows.
Search both the number and the name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Charts, records and the Sankey show protocols by name, but the Protocol
filter only matched the raw number, so typing "TCP" returned no flows.
Known names now map to their protocol number before filtering.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@valerypetrov
valerypetrov force-pushed the agent/netflow-monitoring branch from 219da28 to 40b3978 Compare October 10, 2026 14:47
Comment on lines +105 to +116
const emitFilters = (next: FilterState) =>
onChange(
filtersToQuery(
Object.fromEntries(
Object.entries(next).map(([key, selection]) => [
expressions.get(key) ??
clickedExpressionsRef.current.get(key) ??
key,
selection,
]),
),
),

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.

P1 Clicks drop existing filter conditions

emitFilters drops conditions that parseQuery cannot turn into pills. A shared NetFlow URL with Bytes > 100 initially applies that filter, but clicking Include on an exporter replaces it with only the exporter filter. The page then shows traffic outside the requested search.

Keep untouched conditions alongside the updated pill filters so clicks and removals do not silently widen the search.

Knowledge Base Used: Roll Back Lucene filter handling

Fix in Claude Code Fix in Conductor Fix in Cursor Fix in Codex

Comment thread packages/app/src/source.ts Resolved
valerypetrov and others added 2 commits October 13, 2026 14:37
… mappings

Clicking Include/Exclude rebuilt the search from filter pills only and dropped conditions pills cannot represent (e.g. `Bytes > 100`). Switching to a table without optional columns kept the previous table's mapping because inference omitted absent columns; they are now returned empty.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
[
['exporter', 'Exporter', 'All exporters'],
['protocol', 'Protocol', '6 (TCP), 17 (UDP)'],
['srcAddr', 'Source IP', 'IPv4 or IPv6 address'],

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 — Two independent filter systems for the same NetFlow fields, with different matching rules

The exporter/protocol/srcAddr/dstAddr text inputs write their own URL params, and buildNetflowWhere turns them into toIPv6OrNull(...) = ... / toString(...) = ... predicates. The Include/Exclude clicks write to filters, which match with IN on the replaceRegexpOne/ifNull(toString()) dimension expression. So you can set source IP X in the input and click Include on source IP Y. The two conditions are ANDed, the result is empty, and the input value never shows up as a pill. Fix: have the quick inputs write to the same useNetflowFilterState FilterState (one URL param and one predicate builder), or remove the inputs now that click filters exist.

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

whereLanguage,
filters: [
...extraFilters,
{ type: 'sql' as const, condition: buildNetflowWhere(source, filters) },

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 — Every NetFlow query carries an empty SQL filter, which forces the text-index metadata lookup

buildNetflowWhere returns '' when no quick filter is set, but { type: 'sql', condition: '' } is still added to every config. renderChartConfig.ts:1326 sets hasSqlPredicate when any filter has type === 'sql', so all ~10 NetFlow queries per page load run buildTextIndexInfoLookup metadata calls for nothing. Fix: only add the filter when the condition is non-empty, e.g. ...(cond ? [{ type: 'sql', condition: cond }] : []).

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

? { timestampValueExpression: 'TimeReceived' }
: {}),
defaultTableSelectExpression: selected.join(', ') || '*',
// A mapping for a column the table lacks is returned empty so that switching tables clears it.

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 — Switching a saved NetFlow source to another table keeps stale full-text and timestamp expressions

The comment says missing mappings come back empty so a table switch clears them. But implicitColumnExpression is not in NETFLOW_COLUMN_EXPRESSIONS, and timestampValueExpression is only returned when TimeReceived (or a primary-key timestamp) exists. Edit a saved source with implicitColumnExpression: 'SearchText' and point it at a table without that column: the old value survives, and every bare Lucene term then fails. Fix: also return implicitColumnExpression: '', and an empty timestamp when none is detected, so a table change resets them like the other mappings.

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

label={SOURCE_KIND_LABELS[SourceKind.Promql]}
/>
)}
<Radio

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 — NetFlow source kind is shipped ungated, unlike the other optional kinds

Metric, Session and PromQL radios are behind IS_METRICS_ENABLED/IS_SESSIONS_ENABLED/IS_PROMQL_ENABLED. The beta NetFlow kind is always shown here, and so are its AppNav link, preset dashboard entry and Spotlight action. Fix: add an IS_NETFLOW_ENABLED flag (as IS_PROMQL_ENABLED does, including the setupDefaults.ts skip) and gate the radio, nav link, preset and spotlight on 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

| { label: string; onSelect?: (excluded: boolean) => void }
);

export default memo(function NetflowFilterMenu(props: Props) {

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 — memo on NetflowFilterMenu never prevents a re-render

Every caller passes a new callback on each render. useNetflowFilterState returns new onFilter/onDimensionFilter closures every render, NetflowCharts passes an inline renderGroupLabel, and NetflowSankeyTable/NetflowSankeyNode build onSelect inline. The shallow prop comparison therefore always fails across the hundreds of cells in the flow table. Fix: either drop memo, or wrap the handlers in useCallback in useNetflowFilterState and pass (field, value) instead of building closures per cell.

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

valerypetrov and others added 2 commits October 13, 2026 14:48
Conflicts: the time chart keeps the auto-sized y-axis and measured plot
width (main's fixed yAxisWidth is dropped with it), the MCP source schema
takes main's field descriptions plus the NetFlow wording, and the row side
panel takes main's TSource.

Follow-ups for main: netflow stays in the save_source kinds when PromQL
is off, the NetFlow MCP fields get descriptions, usePageTitle moved to
@/brand, LineData has groupValues, and the time display setting is now a
time zone.

This branch has not been deployed

No deployments
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.

1 participant