Skip to content

aggregated traffic events#693

Open
pascal-fischer wants to merge 4 commits into
mainfrom
feature/aggregated-traffic-events
Open

aggregated traffic events#693
pascal-fischer wants to merge 4 commits into
mainfrom
feature/aggregated-traffic-events

Conversation

@pascal-fischer

@pascal-fischer pascal-fischer commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Issue ticket number and link

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

E2E tests

Optional: override the image tags used by the Playwright e2e workflow.
Defaults to main when omitted.

management-cloud-tag: main
reverse-proxy-tag: main

Summary by CodeRabbit

  • New Features

    • Traffic events now show more accurate aggregate summaries, including totals and time windows when available.
    • Expanded event rows are displayed more selectively based on whether an event is aggregated and has enough detail.
  • Bug Fixes

    • Improved filter state handling so empty selections no longer trigger unnecessary updates.
    • Hidden unknown events from expanded traffic event details.
    • Refined visual indicators and expand/collapse behavior for aggregated traffic events.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces optional aggregation fields on TrafficEvent with a new getTrafficEventCounts helper, then updates the traffic events table, description, detail row, and text cell components to render aggregated event data. Minor unrelated tweaks include a filter default constant, a state-update guard, and exporting PeerContext.

Changes

Aggregated Traffic Events

Layer / File(s) Summary
Aggregation data contract and helper
src/cloud/traffic-events/interfaces/TrafficEvent.ts
Aggregation/window fields on TrafficEvent become optional; new exported getTrafficEventCounts derives starts, ends, drops, total, and isAggregated.
Expanded row logic in TrafficEventsTable
src/cloud/traffic-events/TrafficEventsTable.tsx
renderExpandedRow uses getTrafficEventCounts to expand aggregated rows only when a policy exists, and non-aggregated rows when there are 2+ events.
Aggregated message and window rendering
src/cloud/traffic-events/table/TrafficEventDescription.tsx
Adds memoized eventWindow, an aggregatedMessage() sentence builder for starts/ends/drops, and renders a date-range timestamp for aggregated events.
Text cell expandability and detail row filtering
src/cloud/traffic-events/table/TrafficEventsTextCell.tsx, src/cloud/traffic-events/table/TrafficEventsDetailRow.tsx
Text cell derives isExpandable/isAggregatedBlocked from getTrafficEventCounts to adjust dot color and caret; detail row excludes UNKNOWN events from otherEvents.
Filter default and context export tweaks
src/cloud/traffic-events/TrafficEventsFilter.tsx, src/contexts/PeersProvider.tsx
Filter uses a typed EMPTY_FILTERS constant and guards setActiveFilterIds against redundant updates; PeerContext is now exported.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TrafficEventsTable
  participant TrafficEvent as "getTrafficEventCounts"
  participant TrafficEventsTextCell
  participant TrafficEventDescription

  TrafficEventsTable->>TrafficEvent: getTrafficEventCounts(event)
  TrafficEvent-->>TrafficEventsTable: isAggregated, drops, starts, ends
  TrafficEventsTable->>TrafficEventsTable: decide renderExpandedRow (policy.id check)
  TrafficEventsTextCell->>TrafficEvent: getTrafficEventCounts(event)
  TrafficEvent-->>TrafficEventsTextCell: isAggregated, drops
  TrafficEventsTextCell->>TrafficEventDescription: render(showCaret=isExpandable)
  TrafficEventDescription->>TrafficEvent: getTrafficEventCounts(event)
  TrafficEvent-->>TrafficEventDescription: starts, ends, drops
  TrafficEventDescription->>TrafficEventDescription: aggregatedMessage() / eventWindow render
Loading

Suggested reviewers: lixmal

Poem

A rabbit hopped through fields of code so neat,
Counting starts and ends and drops with tiny feet 🐇
Aggregated events now tell their tale,
With windows shown from head to tail.
Hop, hop, hooray — the dashboard's complete!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description follows the template but leaves the issue ticket section blank and omits the required explanation for why documentation is not needed. Add the issue ticket number/link and briefly explain why documentation is not needed to fully satisfy the template.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and matches the main theme of the change set.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/aggregated-traffic-events

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/cloud/traffic-events/TrafficEventsFilter.tsx

Oops! Something went wrong! :(

ESLint: 9.39.3

TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'configs' -> object with constructor 'Object'
| property 'flat' -> object with constructor 'Object'
| ...
| property 'plugins' -> object with constructor 'Object'
--- property 'react' closes the circle
Referenced from: /.eslintrc.json
at JSON.stringify ()
at /node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2255:45
at Array.map ()
at ConfigValidator.formatErrors (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2246:23)
at ConfigValidator.validateConfigSchema (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2277:84)
at ConfigArrayFactory._normalizeConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3123:19)
at ConfigArrayFactory._loadConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3088:21)
at ConfigArrayFactory._loadExtendedShareableConfig (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3393:21)
at ConfigArrayFactory._loadExtends (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3261:25)
at ConfigArrayFactory._normalizeObjectConfigDataBody (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3199:25)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)

src/cloud/traffic-events/TrafficEventsTable.tsx

Oops! Something went wrong! :(

ESLint: 9.39.3

TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'configs' -> object with constructor 'Object'
| property 'flat' -> object with constructor 'Object'
| ...
| property 'plugins' -> object with constructor 'Object'
--- property 'react' closes the circle
Referenced from: /.eslintrc.json
at JSON.stringify ()
at /node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2255:45
at Array.map ()
at ConfigValidator.formatErrors (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2246:23)
at ConfigValidator.validateConfigSchema (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2277:84)
at ConfigArrayFactory._normalizeConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3123:19)
at ConfigArrayFactory._loadConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3088:21)
at ConfigArrayFactory._loadExtendedShareableConfig (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3393:21)
at ConfigArrayFactory._loadExtends (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3261:25)
at ConfigArrayFactory._normalizeObjectConfigDataBody (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3199:25)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)

src/cloud/traffic-events/interfaces/TrafficEvent.ts

Oops! Something went wrong! :(

ESLint: 9.39.3

TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'configs' -> object with constructor 'Object'
| property 'flat' -> object with constructor 'Object'
| ...
| property 'plugins' -> object with constructor 'Object'
--- property 'react' closes the circle
Referenced from: /.eslintrc.json
at JSON.stringify ()
at /node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2255:45
at Array.map ()
at ConfigValidator.formatErrors (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2246:23)
at ConfigValidator.validateConfigSchema (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2277:84)
at ConfigArrayFactory._normalizeConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3123:19)
at ConfigArrayFactory._loadConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3088:21)
at ConfigArrayFactory._loadExtendedShareableConfig (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3393:21)
at ConfigArrayFactory._loadExtends (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3261:25)
at ConfigArrayFactory._normalizeObjectConfigDataBody (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3199:25)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)

  • 4 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pascal-fischer pascal-fischer marked this pull request as ready for review July 1, 2026 14:07

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cloud/traffic-events/table/TrafficEventDescription.tsx`:
- Around line 104-158: The blocked-connections text in aggregatedMessage() does
not respect info.isInbound, so the actor/phrasing is wrong for inbound traffic.
Update the drops sentence in TrafficEventDescription.tsx to mirror the
direction-aware logic used by the initiated clause and the other blocked-message
branches in this component, using info.sourceName/info.destinationName and the
correct “trying to connect” wording based on traffic direction.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2d8ce9a0-9eef-4835-aaae-f9864085ef74

📥 Commits

Reviewing files that changed from the base of the PR and between d7fd242 and 0e5ed25.

📒 Files selected for processing (7)
  • src/cloud/traffic-events/TrafficEventsFilter.tsx
  • src/cloud/traffic-events/TrafficEventsTable.tsx
  • src/cloud/traffic-events/interfaces/TrafficEvent.ts
  • src/cloud/traffic-events/table/TrafficEventDescription.tsx
  • src/cloud/traffic-events/table/TrafficEventsDetailRow.tsx
  • src/cloud/traffic-events/table/TrafficEventsTextCell.tsx
  • src/contexts/PeersProvider.tsx

Comment on lines +104 to +158
const aggregatedMessage = () => {
const { starts, ends, drops } = getTrafficEventCounts(event);
const connections = (n: number) =>
`${n.toLocaleString()} ${n === 1 ? "connection" : "connections"}`;

const joinClauses = (clauses: string[]) =>
clauses.map((clause, index) => (
<React.Fragment key={clause}>
{index > 0 && (index === clauses.length - 1 ? " and " : ", ")}
{clause}
</React.Fragment>
));

const startVerb = info.isInbound ? "accepted" : "started";
const initiated = [
starts > 0 && `${startVerb} ${connections(starts)}`,
ends > 0 && `ended ${connections(ends)}`,
].filter(Boolean) as string[];

const sentences: React.ReactNode[] = [];
if (initiated.length > 0) {
sentences.push(
info.isInbound ? (
<>
{info.destinationName} {joinClauses(initiated)} from{" "}
{info.sourceName}
</>
) : (
<>
{info.sourceName} {joinClauses(initiated)} to {info.destinationName}
</>
),
);
}
if (drops > 0) {
sentences.push(
<>
{info.sourceName} got blocked {drops.toLocaleString()}{" "}
{drops === 1 ? "time" : "times"} trying to connect to{" "}
{info.destinationName}
</>,
);
}

return (
<>
{sentences.map((sentence, index) => (
<React.Fragment key={index}>
{index > 0 && ". "}
{sentence}
</React.Fragment>
))}
</>
);
};

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Blocked-connections sentence in aggregatedMessage() ignores traffic direction.

The initiated clause correctly swaps phrasing/actor based on info.isInbound (lines 117, 126-136), but the drops sentence always uses {sourceName} got blocked ... trying to connect to {destinationName} regardless of direction (lines 138-146). This is inconsistent with both the initiated-connections handling above it and the non-aggregated blocked messages elsewhere in this file (lines 251-271, 308-322), which do vary phrasing/actor by direction. For inbound aggregated blocked traffic this reads misleadingly, attributing the "connecting" action to the wrong party.

🩹 Proposed fix
     if (drops > 0) {
       sentences.push(
-        <>
-          {info.sourceName} got blocked {drops.toLocaleString()}{" "}
-          {drops === 1 ? "time" : "times"} trying to connect to{" "}
-          {info.destinationName}
-        </>,
+        info.isInbound ? (
+          <>
+            {info.destinationName} blocked {drops.toLocaleString()}{" "}
+            {drops === 1 ? "connection" : "connections"} from{" "}
+            {info.sourceName}
+          </>
+        ) : (
+          <>
+            {info.sourceName} got blocked {drops.toLocaleString()}{" "}
+            {drops === 1 ? "time" : "times"} trying to connect to{" "}
+            {info.destinationName}
+          </>
+        ),
       );
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const aggregatedMessage = () => {
const { starts, ends, drops } = getTrafficEventCounts(event);
const connections = (n: number) =>
`${n.toLocaleString()} ${n === 1 ? "connection" : "connections"}`;
const joinClauses = (clauses: string[]) =>
clauses.map((clause, index) => (
<React.Fragment key={clause}>
{index > 0 && (index === clauses.length - 1 ? " and " : ", ")}
{clause}
</React.Fragment>
));
const startVerb = info.isInbound ? "accepted" : "started";
const initiated = [
starts > 0 && `${startVerb} ${connections(starts)}`,
ends > 0 && `ended ${connections(ends)}`,
].filter(Boolean) as string[];
const sentences: React.ReactNode[] = [];
if (initiated.length > 0) {
sentences.push(
info.isInbound ? (
<>
{info.destinationName} {joinClauses(initiated)} from{" "}
{info.sourceName}
</>
) : (
<>
{info.sourceName} {joinClauses(initiated)} to {info.destinationName}
</>
),
);
}
if (drops > 0) {
sentences.push(
<>
{info.sourceName} got blocked {drops.toLocaleString()}{" "}
{drops === 1 ? "time" : "times"} trying to connect to{" "}
{info.destinationName}
</>,
);
}
return (
<>
{sentences.map((sentence, index) => (
<React.Fragment key={index}>
{index > 0 && ". "}
{sentence}
</React.Fragment>
))}
</>
);
};
const aggregatedMessage = () => {
const { starts, ends, drops } = getTrafficEventCounts(event);
const connections = (n: number) =>
`${n.toLocaleString()} ${n === 1 ? "connection" : "connections"}`;
const joinClauses = (clauses: string[]) =>
clauses.map((clause, index) => (
<React.Fragment key={clause}>
{index > 0 && (index === clauses.length - 1 ? " and " : ", ")}
{clause}
</React.Fragment>
));
const startVerb = info.isInbound ? "accepted" : "started";
const initiated = [
starts > 0 && `${startVerb} ${connections(starts)}`,
ends > 0 && `ended ${connections(ends)}`,
].filter(Boolean) as string[];
const sentences: React.ReactNode[] = [];
if (initiated.length > 0) {
sentences.push(
info.isInbound ? (
<>
{info.destinationName} {joinClauses(initiated)} from{" "}
{info.sourceName}
</>
) : (
<>
{info.sourceName} {joinClauses(initiated)} to {info.destinationName}
</>
),
);
}
if (drops > 0) {
sentences.push(
info.isInbound ? (
<>
{info.destinationName} blocked {drops.toLocaleString()}{" "}
{drops === 1 ? "connection" : "connections"} from{" "}
{info.sourceName}
</>
) : (
<>
{info.sourceName} got blocked {drops.toLocaleString()}{" "}
{drops === 1 ? "time" : "times"} trying to connect to{" "}
{info.destinationName}
</>
),
);
}
return (
<>
{sentences.map((sentence, index) => (
<React.Fragment key={index}>
{index > 0 && ". "}
{sentence}
</React.Fragment>
))}
</>
);
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cloud/traffic-events/table/TrafficEventDescription.tsx` around lines 104
- 158, The blocked-connections text in aggregatedMessage() does not respect
info.isInbound, so the actor/phrasing is wrong for inbound traffic. Update the
drops sentence in TrafficEventDescription.tsx to mirror the direction-aware
logic used by the initiated clause and the other blocked-message branches in
this component, using info.sourceName/info.destinationName and the correct
“trying to connect” wording based on traffic direction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants