Add table filter integration: types, getFilter mappings, DataTable on…#86
Conversation
|
@github-actions[bot] is attempting to deploy a commit to the Databuddy Team on Vercel. A member of the Team first needs to authorize it. |
|
Caution Review failedThe pull request is closed. WalkthroughAdds end-to-end row-to-filter plumbing and UI: exposes addFilter on tab props, adds onAddFilter/getFilter to DataTable and table tabs, introduces dynamic filter atoms and toolbar/layout for managing filters and refresh, updates session atoms and list to atom-driven pagination, and adjusts query builders and geo normalization. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant DataTable
participant TabConfig
participant WebsitePage
participant FiltersAtom
User->>DataTable: Click row
alt row has sub-rows
DataTable->>DataTable: Toggle expand
else onAddFilter provided
DataTable->>TabConfig: getFilter(row)
TabConfig-->>DataTable: { field, value }
DataTable->>WebsitePage: onAddFilter(field, value, tableTitle)
WebsitePage->>FiltersAtom: addFilter({ field, operator: "eq", value })
else fallback
DataTable->>DataTable: onRowClick fallback
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Possibly related PRs
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. 📜 Recent review detailsConfiguration used: CodeRabbit UI 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (5)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
thanks @kubiks-agent for cooking! |
There was a problem hiding this comment.
Actionable comments posted: 11
🔭 Outside diff range comments (4)
apps/dashboard/components/analytics/data-table.tsx (2)
173-194: Update FullScreenTable onAddFilter prop typing to match DataTablePropsEnsure parity between the inline and full-screen flows.
- onAddFilter, + onAddFilter, ... - onAddFilter?: (field: string, value: string, tableTitle?: string) => void; + onAddFilter?: (field: string, value: string | number, tableTitle?: string) => void;
1006-1016: Cursor affordance may mislead when onAddFilter is provided but no getFilter/fallback existsThe row shows cursor-pointer whenever onAddFilter is present, even if neither a getFilter is defined nor a reasonable fallback is available. This is minor UI friction.
Consider tightening the condition to only show pointer when:
- hasSubRows, or
- onRowClick, or
- onAddFilter and (active tab has getFilter OR row.original.name exists)
apps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsx (2)
411-418: Critical: dayjs.tz()/dayjs.utc() used before plugins are extended (runtime bug).
filterFutureEventscallsdayjs().tz(...)anddayjs.utc(...)(Lines 413, 416) butdayjs.extend(utc)anddayjs.extend(timezone)are only called much later (Lines 780-781). This will causetz/utcto be undefined at call-time and can throw.Fix by extending the plugins right after importing them, before any use, and remove the late extensions.
Apply the following diffs:
- Move dayjs.extend calls near imports:
import dayjs from 'dayjs'; import timezone from 'dayjs/plugin/timezone'; import utc from 'dayjs/plugin/utc'; +dayjs.extend(utc); +dayjs.extend(timezone);
- Remove late extensions:
- dayjs.extend(utc); - dayjs.extend(timezone);Also applies to: 780-783
1183-1200: Fix: percentage is undefined in sub-row; avoid array index in keys.
valueItem.percentageis not set inprocessedCustomEventsData, so the UI rendersundefined%.- Keys use the array index; guidelines recommend not using indexes.
Apply this diff to compute percentage on the fly and remove index usage from keys:
- {propertyValues.map( - (valueItem: PropertyValue, valueIndex: number) => ( + {propertyValues.map( + (valueItem: PropertyValue) => ( <div className="flex items-center justify-between border-border/10 border-b px-3 py-2 last:border-b-0 hover:bg-muted/20" - key={`${propertyKey}-${valueItem.value}-${valueIndex}`} + key={`${propertyKey}-${valueItem.value}`} > <span className="truncate font-mono text-foreground text-sm" title={valueItem.value} > {valueItem.value} </span> <div className="flex items-center gap-2"> <span className="font-medium text-foreground text-sm"> {formatNumber(valueItem.count)} </span> <div className="min-w-[2.5rem] rounded bg-muted px-2 py-0.5 text-center font-medium text-muted-foreground text-xs"> - {valueItem.percentage}% + {Math.round((valueItem.count * 100) / Math.max(1, propertyTotal))}% </div> </div> </div> ) )}Optional follow-up: Populate
percentageandpercentage_within_eventinprocessedCustomEventsDatato avoid recomputing in render and to align withPropertyValuetype.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
apps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsx(12 hunks)apps/dashboard/app/(main)/websites/[id]/_components/utils/types.ts(1 hunks)apps/dashboard/app/(main)/websites/[id]/page.tsx(2 hunks)apps/dashboard/components/analytics/data-table.tsx(9 hunks)apps/dashboard/lib/table-tabs.tsx(3 hunks)packages/shared/src/lists/filters.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts,jsx,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't use global eval().
Don't use var.
Don't use console.
Don't use debugger.
Don't assign directly to document.cookie.
Don't use duplicate case labels.
Don't use duplicate class members.
Don't use duplicate function parameter names.
Don't use empty block statements and static blocks.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Use for...of statements instead of Array.forEach.
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names that aren't base 10 or use underscore separators.
Remove redundant terms from logical expressions.
Use while loops instead of for loops when you don't need initializer and update expressions.
Don't reassign const variables.
Don't use constant expressions in conditions.
Don't use Math.min and Math.max to clamp values when the result is constant.
Don't return a value from a constructor.
Don't use empty character classes in regular expression literals.
Don't use empty destructuring patterns.
Don't call global object properties as functions.
Don't declare functions and vars that are accessible outside their block.
Don't use variables and function parameters before they're declared.
Don't use 8 and 9 escape sequences in string literals.
Don't use literal numbers that lose precision.
Don't use duplicate conditions in if-else-if chains.
Don't use two keys with the same name inside objects...
Files:
apps/dashboard/app/(main)/websites/[id]/_components/utils/types.tspackages/shared/src/lists/filters.tsapps/dashboard/lib/table-tabs.tsxapps/dashboard/app/(main)/websites/[id]/page.tsxapps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsxapps/dashboard/components/analytics/data-table.tsx
**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use TypeScript namespaces.
Don't use the TypeScript directive @ts-ignore.
Don't use any type.
Don't use implicit any type on variable declarations.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't declare empty interfaces.
Use export type for types.
Use import type for types.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
**/*.{ts,tsx}: Ensure TypeScript type-safety; avoidanyunless absolutely necessary
Create proper interfaces/types for APIs, responses, and components; place them in shared types folders rather than in the same file
Files:
apps/dashboard/app/(main)/websites/[id]/_components/utils/types.tspackages/shared/src/lists/filters.tsapps/dashboard/lib/table-tabs.tsxapps/dashboard/app/(main)/websites/[id]/page.tsxapps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsxapps/dashboard/components/analytics/data-table.tsx
**/*.{js,ts}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts}: Make sure to use the "use strict" directive in script files.
Don't have redundant "use strict".
Make sure to use the "use strict" directive in script files.
Files:
apps/dashboard/app/(main)/websites/[id]/_components/utils/types.tspackages/shared/src/lists/filters.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)
**/*.{js,jsx,ts,tsx}: Usebun <file>instead ofnode <file>orts-node <file>
Do not use dotenv; Bun automatically loads .env files
Do not useexpress; useBun.serve()for HTTP servers
Do not usebetter-sqlite3; usebun:sqlitefor SQLite
Do not useioredis; useBun.redisfor Redis
Do not usepgorpostgres.js; useBun.sqlfor Postgres
Do not usews; use built-inWebSocket
PreferBun.fileovernode:fs's readFile/writeFile
UseBun.$instead of execa for running shell commands
Files:
apps/dashboard/app/(main)/websites/[id]/_components/utils/types.tspackages/shared/src/lists/filters.tsapps/dashboard/lib/table-tabs.tsxapps/dashboard/app/(main)/websites/[id]/page.tsxapps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsxapps/dashboard/components/analytics/data-table.tsx
**/*.{html,ts,css}
📄 CodeRabbit Inference Engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)
Use
bun build <file.html|file.ts|file.css>instead ofwebpackoresbuild
Files:
apps/dashboard/app/(main)/websites/[id]/_components/utils/types.tspackages/shared/src/lists/filters.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit Inference Engine (.cursor/rules/01-MUST-DO.mdc)
**/*.{ts,tsx,js,jsx}: Use console methods appropriately (e.g., console.error, console.time, console.table, console.json)
Use Dayjs instead of date-fns
Use TanStack Query for hooks; do not use SWR
When adding debugging, use JSON.stringify() for structured output
Files:
apps/dashboard/app/(main)/websites/[id]/_components/utils/types.tspackages/shared/src/lists/filters.tsapps/dashboard/lib/table-tabs.tsxapps/dashboard/app/(main)/websites/[id]/page.tsxapps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsxapps/dashboard/components/analytics/data-table.tsx
**/*.{tsx,jsx,ts,js}
📄 CodeRabbit Inference Engine (.cursor/rules/01-MUST-DO.mdc)
When using Phosphor React icons, append 'Icon' to component names (e.g., CaretIcon, not Caret)
Files:
apps/dashboard/app/(main)/websites/[id]/_components/utils/types.tspackages/shared/src/lists/filters.tsapps/dashboard/lib/table-tabs.tsxapps/dashboard/app/(main)/websites/[id]/page.tsxapps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsxapps/dashboard/components/analytics/data-table.tsx
**/*.{jsx,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't pass children as props.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't forget key props in iterators and collection literals.
Don't destructure props inside JSX components in Solid projects.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign to React component props.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use dangerous JSX props.
Don't use Array index in keys.
Don't insert comments as text nodes.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Watch out for possible "wrong" semicolons inside JSX elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Don't use distracting elements like or .
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible ...Files:
apps/dashboard/lib/table-tabs.tsxapps/dashboard/app/(main)/websites/[id]/page.tsxapps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsxapps/dashboard/components/analytics/data-table.tsx**/*.{tsx,jsx}
📄 CodeRabbit Inference Engine (.cursor/rules/01-MUST-DO.mdc)
**/*.{tsx,jsx}: In Tailwind CSS usage, useroundedonly; do not userounded-mdorrounded-xl
Use Phosphor icons, not Lucide; default to weight="duotone", use fill for arrows, and for plus icons do not set weight
In view components, decouple state management, data transformations, and API interactions from the React lifecycle
Simplify data flow in components to avoid prop drilling and callback chains
Prioritize modularity and testability in all React components
Use React Error Boundaries appropriately
Avoid useEffect in React unless it is truly criticalFiles:
apps/dashboard/lib/table-tabs.tsxapps/dashboard/app/(main)/websites/[id]/page.tsxapps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsxapps/dashboard/components/analytics/data-table.tsx🧬 Code Graph Analysis (1)
apps/dashboard/app/(main)/websites/[id]/_components/utils/types.ts (1)
packages/shared/src/types/api.ts (1)
DynamicQueryFilter(19-34)🔇 Additional comments (9)
packages/shared/src/lists/filters.ts (1)
9-10: LGTM: Added filter options are consistent with existing schemaReferrer and Page Path align with common filter fields and the existing filterOptions shape.
apps/dashboard/app/(main)/websites/[id]/_components/utils/types.ts (1)
36-36: LGTM: addFilter is correctly introduced on FullTabPropsSignature matches DynamicQueryFilter and fits the page-level addFilter usage.
apps/dashboard/lib/table-tabs.tsx (1)
122-123: LGTM: getFilter propagation is correctuseTableTabs correctly exposes getFilter per tab.
apps/dashboard/app/(main)/websites/[id]/page.tsx (2)
209-210: LGTM: addFilter is properly passed to tab propsThis wires the tab components to trigger page-level filtering.
244-244: LGTM: Dependency list includes addFilterIncluding addFilter prevents stale closures in renderTabContent.
apps/dashboard/components/analytics/data-table.tsx (1)
1229-1230: LGTM: onAddFilter is forwarded to the full-screen modalEnsures consistent behavior across inline and full-screen views.
apps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsx (3)
147-148: Prop addition: ensure FullTabProps type is updated for addFilter.Confirm
FullTabPropsincludes a type-safeaddFiltersignature matching the constructed filter shape:{ field: string; operator: 'eq' | ...; value: string }. Mismatched types will break the wiring downstream.
911-921: LGTM: onAddFilter wiring is correct and minimal.Callback is memoized, uses the provided
addFilter, and standardizes the operator toeq. Good foundation.
1106-1106: LGTM: DataTable onAddFilter connections across all tables.Passing
onAddFilterinto all DataTable instances is consistent with the tab-levelgetFiltermappers and enables row-to-filter UX.Also applies to: 1115-1115, 1134-1134, 1221-1221, 1253-1253, 1277-1277
| getFilter: (row: any) => { | ||
| // For referrers, use the referrer field and get the actual referrer URL | ||
| return { | ||
| field: 'referrer', | ||
| value: row.referrer | ||
| }; | ||
| }, | ||
| }, |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Avoid any in getFilter mappers; align to concrete row types per tab.
Multiple getFilter lambdas use any. Per coding guidelines, avoid any in TS. Use concrete types already present in this file to improve type-safety and prevent runtime mistakes.
Apply these diffs:
- getFilter: (row: any) => {
+ getFilter: (row: ReferrerSourceCellData) => {
// For referrers, use the referrer field and get the actual referrer URL
return {
field: 'referrer',
value: row.referrer
};
},- getFilter: (row: any) => ({
+ getFilter: (row: { name: string }) => ({
field: 'utm_source',
value: row.name
}),- getFilter: (row: any) => ({
+ getFilter: (row: { name: string }) => ({
field: 'utm_medium',
value: row.name
}),- getFilter: (row: any) => ({
+ getFilter: (row: { name: string }) => ({
field: 'utm_campaign',
value: row.name
}),- getFilter: (row: any) => ({
+ getFilter: (row: PageData) => ({
field: 'path',
value: row.name
}),- getFilter: (row: any) => ({
+ getFilter: (row: PageData) => ({
field: 'path',
value: row.name
}),- getFilter: (row: any) => ({
+ getFilter: (row: PageData) => ({
field: 'path',
value: row.name
}),- getFilter: (row: any) => {
+ getFilter: (row: { name: string }) => {
// Map display device names to filter values
const deviceDisplayToFilterMap: Record<string, string> = {
'laptop': 'mobile',
'tablet': 'tablet',
'desktop': 'desktop',
};
return {
field: 'device_type',
value: deviceDisplayToFilterMap[row.name] || row.name
};
}- getFilter: (row: any) => ({
+ getFilter: (row: TechnologyData) => ({
field: 'browser_name',
value: row.name
}),- getFilter: (row: any) => ({
+ getFilter: (row: TechnologyData) => ({
field: 'os_name',
value: row.name
}),Also confirm the filter field names (utm_source, utm_medium, utm_campaign, path, browser_name, os_name) match the backend API’s expected filter keys.
Also applies to: 331-335, 341-345, 351-355, 367-371, 380-384, 393-397, 1230-1241, 1262-1265, 1286-1289
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== getFilter occurrences (context) ==="
rg -n --hidden --no-ignore-vcs -S -C3 "getFilter" || true
echo
echo "=== Print overview-tab.tsx (if present) ==="
TARGET='apps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsx'
if [ -f "$TARGET" ]; then
sed -n '1,2000p' "$TARGET" || true
else
echo "File not found at: $TARGET"
fi
echo
echo "=== Search for suggested row types in repo ==="
rg -n --hidden --no-ignore-vcs -S -C3 "ReferrerSourceCellData|PageData|TechnologyData" || true
echo
echo "=== Search for backend filter key usages in repo ==="
rg -n --hidden --no-ignore-vcs -S -C3 "utm_source|utm_medium|utm_campaign|browser_name|os_name|path" || trueLength of output: 299150
Use concrete row types for getFilter in overview-tab.tsx (fix required)
Replace all occurrences of getFilter that use any with the concrete row types already present/imported in this file to restore type-safety. I confirmed the file defines/imports ReferrerSourceCellData, PageData and TechnologyData and the backend uses the same filter keys (utm_source/utm_medium/utm_campaign/path/browser_name/os_name/referrer/device_type).
Files/locations to update
- apps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsx
- referrers: ~318-325
- utm_sources: ~331-335
- utm_mediums: ~341-345
- utm_campaigns: ~351-355
- top_pages: ~367-371
- entry_pages: ~377-383
- exit_pages: ~390-396
- devices tab getFilter: ~1230-1241
- browsers tab getFilter: ~1262-1265
- operating_systems tab getFilter: ~1286-1289
Apply these diffs:
- getFilter: (row: any) => {
+ getFilter: (row: ReferrerSourceCellData) => {
// For referrers, use the referrer field and get the actual referrer URL
return {
field: 'referrer',
value: row.referrer
};
},- getFilter: (row: any) => ({
+ getFilter: (row: { name: string }) => ({
field: 'utm_source',
value: row.name
}),- getFilter: (row: any) => ({
+ getFilter: (row: { name: string }) => ({
field: 'utm_medium',
value: row.name
}),- getFilter: (row: any) => ({
+ getFilter: (row: { name: string }) => ({
field: 'utm_campaign',
value: row.name
}),- getFilter: (row: any) => ({
+ getFilter: (row: PageData) => ({
field: 'path',
value: row.name
}),(Apply the same PageData change for entry_pages and exit_pages occurrences.)
- getFilter: (row: any) => {
+ getFilter: (row: { name: string }) => {
// Map display device names to filter values
const deviceDisplayToFilterMap: Record<string, string> = {
'laptop': 'mobile',
'tablet': 'tablet',
'desktop': 'desktop',
};
return {
field: 'device_type',
value: deviceDisplayToFilterMap[row.name] || row.name
};
}- getFilter: (row: any) => ({
+ getFilter: (row: TechnologyData) => ({
field: 'browser_name',
value: row.name
}),- getFilter: (row: any) => ({
+ getFilter: (row: TechnologyData) => ({
field: 'os_name',
value: row.name
}),Notes
- Types referenced above are already defined/imported in the file (ReferrerSourceCellData import; PageData/TechnologyData interfaces).
- Backend filter keys (utm_source, utm_medium, utm_campaign, path, browser_name, os_name, referrer, device_type) are used elsewhere in the repo (packages/shared and rpc code), so the chosen field names align with the API.
📝 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.
| getFilter: (row: any) => { | |
| // For referrers, use the referrer field and get the actual referrer URL | |
| return { | |
| field: 'referrer', | |
| value: row.referrer | |
| }; | |
| }, | |
| }, | |
| getFilter: (row: ReferrerSourceCellData) => { | |
| // For referrers, use the referrer field and get the actual referrer URL | |
| return { | |
| field: 'referrer', | |
| value: row.referrer | |
| }; | |
| }, |
🤖 Prompt for AI Agents
In apps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsx
around the indicated lines (referrers ~318-325, utm_sources ~331-335,
utm_mediums ~341-345, utm_campaigns ~351-355, top_pages ~367-371, entry_pages
~377-383, exit_pages ~390-396, devices ~1230-1241, browsers ~1262-1265,
operating_systems ~1286-1289) the getFilter functions currently type their row
parameter as any; replace those any usages with the concrete types already
imported/defined in the file: use ReferrerSourceCellData for referrers, PageData
for top_pages/entry_pages/exit_pages, and TechnologyData for
devices/browsers/operating_systems/utm_* where appropriate; ensure each returned
object uses the backend filter key (referrer, path, utm_source, utm_medium,
utm_campaign, device_type, browser_name, os_name) and the row property access
matches the chosen type so TypeScript infers correct fields and restores
type-safety.
| getFilter: (row: any) => ({ | ||
| field: 'path', | ||
| value: row.name | ||
| }), | ||
| }, |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Verify page path filter value: using decoded path may not match backend expectation.
You decode the path for display, then pass row.name (decoded) to filters. If the backend expects the encoded path, filtering may fail.
If backend expects encoded values, change to pass the original (encoded) value alongside a safe decode for display, or re-encode before filtering:
- getFilter: (row: PageData) => ({
+ getFilter: (row: PageData) => ({
field: 'path',
- value: row.name
+ value: encodeURI(row.name)
}),Alternatively, keep both raw and display fields when mapping the data for pages, e.g., { name: decodeURIComponent(page.name), raw: page.name }, and use row.raw for filters.
Also applies to: 380-384, 393-397
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "----- Displaying overview-tab.tsx (first 400 lines) -----"
sed -n '1,400p' "apps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsx" || true
echo
echo "----- Searching for getFilter occurrences -----"
rg -n -C3 "getFilter" --hidden --no-ignore || true
echo
echo "----- Searching for direct uses of row.name / row.raw -----"
rg -n -C3 "row\.name|row\.raw" --hidden --no-ignore || true
echo
echo "----- Searching for encode/decode usage in repo -----"
rg -n -C3 "decodeURIComponent|decodeURI|encodeURI|encodeURIComponent" --hidden --no-ignore || true
echo
echo "----- Searching for mapping that sets name: or raw: -----"
rg -n -C3 "\bname\s*:\s*|raw\s*:\s*" --hidden --no-ignore || true
echo
echo "----- Searching for PageData type or interface -----"
rg -n -C3 "type\s+PageData|interface\s+PageData|PageData\b" --hidden --no-ignore || trueLength of output: 286434
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "----- Search for 'path' used in server/query code -----"
rg -n --hidden --no-ignore "path" packages apps --glob '!**/node_modules/**' -S -g 'packages/**/src/**' -g 'apps/**/src/**' -g 'apps/**/api/**' || true
echo
echo "----- Search for 'top_pages' and where it's produced -----"
rg -n --hidden --no-ignore "top_pages" || true
echo
echo "----- Show apps/api/src/query/utils.ts around applyUrlNormalization -----"
sed -n '1,320p' apps/api/src/query/utils.ts || true
echo
echo "----- Search for server-side filter handling (look for 'filters' param usage) -----"
rg -n --hidden --no-ignore "filters" packages apps --glob '!**/node_modules/**' -S || true
echo
echo "----- Search for code that maps/normalizes page names or paths -----"
rg -n --hidden --no-ignore "applyUrlNormalization|normalize|urlNormalization|url_normalization|path_normalization|decodeURIComponent" || true
Length of output: 78219
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- apps/api/src/query/simple-builder.ts ----"
sed -n '1,320p' apps/api/src/query/simple-builder.ts || true
echo
echo "---- apps/api/src/query/builders/pages.ts ----"
sed -n '1,320p' apps/api/src/query/builders/pages.ts || true
echo
echo "---- packages/rpc/src/lib/analytics-utils.ts (top 320 lines) ----"
sed -n '1,320p' packages/rpc/src/lib/analytics-utils.ts || true
echo
echo "---- apps/dashboard/hooks/use-dynamic-query.ts ----"
sed -n '1,400p' apps/dashboard/hooks/use-dynamic-query.ts || true
echo
echo "---- apps/dashboard/stores/jotai/filterAtoms.ts ----"
sed -n '1,320p' apps/dashboard/stores/jotai/filterAtoms.ts || trueLength of output: 37889
Fix path filter: use the original/encoded path when adding filters
Decoded page names are only for display — the API/DB expects the normalized/encoded path. You're decoding page.name for UI then passing the decoded value to filters, which can cause filter misses. Preserve the original value and use it for filters (or re-encode as a fallback).
Files to change
- apps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsx
- top_pages mapping & getFilter (≈ lines 359–371)
- entry_pages mapping & getFilter (≈ lines 372–384)
- exit_pages mapping & getFilter (≈ lines 385–397)
Suggested change (apply same pattern to entry_pages and exit_pages):
- data: (analytics.top_pages || []).map((page: PageData) => ({
- ...page,
- name: decodeURIComponent(page.name),
- })),
+ data: (analytics.top_pages || []).map((page: PageData) => ({
+ ...page,
+ name: decodeURIComponent(page.name), // decoded for display
+ raw: page.name, // preserve original (encoded/normalized) value for filtering
+ })),
- getFilter: (row: any) => ({
- field: 'path',
- value: row.name
- }),
+ getFilter: (row: any) => ({
+ field: 'path',
+ value: row.raw ?? encodeURIComponent(row.name), // prefer original; fallback to re-encoding
+ }),This will ensure filter values match what the backend queries against.
📝 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.
| getFilter: (row: any) => ({ | |
| field: 'path', | |
| value: row.name | |
| }), | |
| }, | |
| data: (analytics.top_pages || []).map((page: PageData) => ({ | |
| ...page, | |
| name: decodeURIComponent(page.name), // decoded for display | |
| raw: page.name, // preserve original (encoded/normalized) value for filtering | |
| })), | |
| getFilter: (row: any) => ({ | |
| field: 'path', | |
| value: row.raw ?? encodeURIComponent(row.name), // prefer original; fallback to re-encoding | |
| }), | |
| }, |
🤖 Prompt for AI Agents
In apps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsx
around lines 359–397 (top_pages mapping & getFilter ≈ 359–371, entry_pages ≈
372–384, exit_pages ≈ 385–397), the getFilter currently uses the decoded display
name (row.name) which breaks backend filtering; change the mappings so the UI
still displays the decoded name but the getFilter uses the original/encoded path
value (e.g. row.path or row.originalPath) and, if that field is not available,
produce an encoded fallback using encodeURI/encodeURIComponent on the original
raw path before returning the filter object — apply the same pattern to
top_pages, entry_pages and exit_pages getFilter implementations so filter.value
always matches the backend-normalized path.
| getFilter: (row: any) => { | ||
| // Map display device names to filter values | ||
| const deviceDisplayToFilterMap: Record<string, string> = { | ||
| 'laptop': 'mobile', | ||
| 'tablet': 'tablet', | ||
| 'desktop': 'desktop', | ||
| }; | ||
| return { | ||
| field: 'device_type', | ||
| value: deviceDisplayToFilterMap[row.name] || row.name | ||
| }; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Incorrect device mapping: 'laptop' → 'mobile'.
Device mapping appears wrong: 'laptop' should not map to 'mobile'. If your source names are typically 'mobile' | 'tablet' | 'desktop', consider normalizing and mapping those directly. Also, normalize the key to lowercase for robustness.
Apply this diff:
- getFilter: (row: { name: string }) => {
- // Map display device names to filter values
- const deviceDisplayToFilterMap: Record<string, string> = {
- 'laptop': 'mobile',
- 'tablet': 'tablet',
- 'desktop': 'desktop',
- };
- return {
- field: 'device_type',
- value: deviceDisplayToFilterMap[row.name] || row.name
- };
- }
+ getFilter: (row: { name: string }) => {
+ // Map display device names to filter values
+ const deviceDisplayToFilterMap: Record<string, string> = {
+ mobile: 'mobile',
+ tablet: 'tablet',
+ desktop: 'desktop',
+ };
+ const key = String(row.name).toLowerCase();
+ return {
+ field: 'device_type',
+ value: deviceDisplayToFilterMap[key] ?? key,
+ };
+ }If 'laptop' does appear in your dataset and should be treated as 'desktop', add 'laptop': 'desktop'.
📝 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.
| getFilter: (row: any) => { | |
| // Map display device names to filter values | |
| const deviceDisplayToFilterMap: Record<string, string> = { | |
| 'laptop': 'mobile', | |
| 'tablet': 'tablet', | |
| 'desktop': 'desktop', | |
| }; | |
| return { | |
| field: 'device_type', | |
| value: deviceDisplayToFilterMap[row.name] || row.name | |
| }; | |
| } | |
| getFilter: (row: { name: string }) => { | |
| // Map display device names to filter values | |
| const deviceDisplayToFilterMap: Record<string, string> = { | |
| mobile: 'mobile', | |
| tablet: 'tablet', | |
| desktop: 'desktop', | |
| }; | |
| const key = String(row.name).toLowerCase(); | |
| return { | |
| field: 'device_type', | |
| value: deviceDisplayToFilterMap[key] ?? key, | |
| }; | |
| } |
| getFilter?: (row: TData) => { field: string; value: string }; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Broaden the getFilter return type to support numeric filters
Align with DynamicQueryFilter.value and avoid over-constraining.
- getFilter?: (row: TData) => { field: string; value: string };
+ getFilter?: (row: TData) => { field: string; value: string | number };📝 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.
| getFilter?: (row: TData) => { field: string; value: string }; | |
| } | |
| getFilter?: (row: TData) => { field: string; value: string | number }; | |
| } |
🤖 Prompt for AI Agents
In apps/dashboard/components/analytics/data-table.tsx around lines 45 to 46, the
getFilter return type currently forces value to a string which over-constrains
callers; change the return type to match DynamicQueryFilter.value (e.g. value:
string | number or better: value: DynamicQueryFilter['value']) and import/alias
DynamicQueryFilter if needed so numeric filters are supported and typings remain
consistent.
| onAddFilter?: (field: string, value: string, tableTitle?: string) => void; | ||
| minHeight?: string | number; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Broaden onAddFilter to accept string | number values
Keeps the callback compatible with all filter value types and matches onRowClick’s value typing.
- onAddFilter?: (field: string, value: string, tableTitle?: string) => void;
+ onAddFilter?: (field: string, value: string | number, tableTitle?: string) => void;📝 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.
| onAddFilter?: (field: string, value: string, tableTitle?: string) => void; | |
| minHeight?: string | number; | |
| onAddFilter?: (field: string, value: string | number, tableTitle?: string) => void; | |
| minHeight?: string | number; |
🤖 Prompt for AI Agents
In apps/dashboard/components/analytics/data-table.tsx around lines 59-60, the
onAddFilter callback currently types the value parameter as string only; change
its signature to accept string | number (onAddFilter?: (field: string, value:
string | number, tableTitle?: string) => void) so it matches onRowClick’s value
typing and supports numeric filter values, then update any local calls/usages of
onAddFilter to pass numbers without casting and adjust callers to handle number
values as needed.
| if (hasSubRows) { | ||
| toggleRowExpansion(row.id); | ||
| } else if (onAddFilter && row.original.name) { | ||
| // Determine the appropriate field and value based on table context | ||
| const activeTabConfig = tabs?.find(tab => tab.id === activeTab); | ||
| const filterFunc = activeTabConfig?.getFilter; | ||
| if (!filterFunc) { | ||
| return; | ||
| } | ||
|
|
||
| const { field, value } = filterFunc(row.original); | ||
| onAddFilter(field, value, title); | ||
| } else if (onRowClick) { | ||
| onRowClick('name', row.original.name); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Remove unnecessary guard and add robust fallback for filtering
Filtering currently requires row.original.name to be truthy and silently no-ops if getFilter is missing. This causes missed filters on valid rows and inconsistent UX.
- } else if (onAddFilter && row.original.name) {
- // Determine the appropriate field and value based on table context
- const activeTabConfig = tabs?.find(tab => tab.id === activeTab);
- const filterFunc = activeTabConfig?.getFilter;
- if (!filterFunc) {
- return;
- }
-
- const { field, value } = filterFunc(row.original);
- onAddFilter(field, value, title);
+ } else if (onAddFilter) {
+ const activeTabConfig = tabs?.find((tab) => tab.id === activeTab);
+ const filter = activeTabConfig?.getFilter?.(row.original);
+ const field = filter?.field ?? 'name';
+ const value = filter?.value ?? row.original.name;
+ if (field != null && value != null) {
+ onAddFilter(field, value as string | number, title);
+ }
} else if (onRowClick) {
- onRowClick('name', row.original.name);
+ onRowClick('name', row.original.name);
}📝 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.
| if (hasSubRows) { | |
| toggleRowExpansion(row.id); | |
| } else if (onAddFilter && row.original.name) { | |
| // Determine the appropriate field and value based on table context | |
| const activeTabConfig = tabs?.find(tab => tab.id === activeTab); | |
| const filterFunc = activeTabConfig?.getFilter; | |
| if (!filterFunc) { | |
| return; | |
| } | |
| const { field, value } = filterFunc(row.original); | |
| onAddFilter(field, value, title); | |
| } else if (onRowClick) { | |
| onRowClick('name', row.original.name); | |
| } | |
| if (hasSubRows) { | |
| toggleRowExpansion(row.id); | |
| } else if (onAddFilter) { | |
| const activeTabConfig = tabs?.find((tab) => tab.id === activeTab); | |
| const filter = activeTabConfig?.getFilter?.(row.original); | |
| const field = filter?.field ?? 'name'; | |
| const value = filter?.value ?? row.original.name; | |
| if (field != null && value != null) { | |
| onAddFilter(field, value as string | number, title); | |
| } | |
| } else if (onRowClick) { | |
| onRowClick('name', row.original.name); | |
| } |
🤖 Prompt for AI Agents
In apps/dashboard/components/analytics/data-table.tsx around lines 1018 to 1032,
remove the unnecessary guard that requires row.original.name and instead ensure
filtering always attempts a sensible fallback: keep the hasSubRows branch, then
for the filter branch remove the row.original.name check, fetch activeTabConfig
= tabs?.find(tab => tab.id === activeTab) and const filterFunc =
activeTabConfig?.getFilter, and if filterFunc is missing use a default function
that returns { field: 'name', value: row.original.name ?? row.original.id ?? ''
}; call onAddFilter(field, value, title) only when value is non-empty to avoid
no-ops and preserve UX consistency.
| getFilter?: (row: T) => { field: string; value: string }; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Broaden getFilter value type to support numeric values
DynamicQueryFilter.value allows string | number | array. Constraining to string is unnecessarily narrow and may block valid filters (e.g., numeric codes). Broaden the return type for consistency.
- getFilter?: (row: T) => { field: string; value: string };
+ getFilter?: (row: T) => { field: string; value: string | number };📝 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.
| getFilter?: (row: T) => { field: string; value: string }; | |
| } | |
| getFilter?: (row: T) => { field: string; value: string | number }; | |
| } |
🤖 Prompt for AI Agents
In apps/dashboard/lib/table-tabs.tsx around lines 22-23, the getFilter return
type is too narrow (value: string); broaden it to match DynamicQueryFilter.value
by changing the value type to accept string | number | (string | number)[] or,
better, reference the existing type (e.g., DynamicQueryFilter['value']) so
filters can accept numeric and array values; update the type annotation
accordingly and adjust any imports if needed.
| getFilter?: (row: T) => { field: string; value: string }; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Apply the same value type widening here for consistency
Keep TabConfig and SimpleTabConfig aligned on the getFilter signature.
- getFilter?: (row: T) => { field: string; value: string };
+ getFilter?: (row: T) => { field: string; value: string | number };📝 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.
| getFilter?: (row: T) => { field: string; value: string }; | |
| } | |
| getFilter?: (row: T) => { field: string; value: string | number }; | |
| } |
🤖 Prompt for AI Agents
In apps/dashboard/lib/table-tabs.tsx around lines 107 to 108, the getFilter
return type is currently { field: string; value: string } and needs the same
value-type widening as the corresponding TabConfig definition; change the
signature to return { field: string; value: string | number | boolean } (or the
exact union used in TabConfig) so SimpleTabConfig and TabConfig stay aligned.
| // Table title to filter field mapping - use exact database column names | ||
| export const tableFilterMapping: Record<string, string> = { | ||
| 'Traffic Sources': 'referrer', | ||
| 'Pages': 'path', | ||
| 'Devices': 'device_type', | ||
| 'Browsers': 'browser_name', | ||
| 'Operating Systems': 'os_name', | ||
| }; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Avoid coupling mappings to user-facing titles; prefer stable identifiers
Mapping by exact table titles is brittle (breaks with copy changes or i18n). Prefer stable IDs (e.g., enum-like string literals or a central TabId) over display labels, or at least centralize the labels to a single source of truth.
Run this script to verify all call sites depend on these exact labels and assess i18n risk:
🏁 Script executed:
#!/bin/bash
# Find usages of table titles that are used as keys here
rg -n -C2 -P "(Traffic Sources|Pages|Devices|Browsers|Operating Systems)" --type=ts --type=tsx --type=js --type=jsxLength of output: 145
🏁 Script executed:
#!/bin/bash
echo "== show file =="
if [ -f packages/shared/src/lists/filters.ts ]; then
sed -n '1,200p' packages/shared/src/lists/filters.ts
else
echo "File not found: packages/shared/src/lists/filters.ts"
fi
echo
echo "== search for symbol tableFilterMapping =="
rg -n --hidden -S "tableFilterMapping" || true
echo
echo "== search for exact labels (case-sensitive) =="
for s in "Traffic Sources" "Pages" "Devices" "Browsers" "Operating Systems"; do
echo "---- $s ----"
rg -n -C2 -F "$s" || echo "No matches for: $s"
done
echo
echo "== search for labels (case-insensitive) =="
for s in "Traffic Sources" "Pages" "Devices" "Browsers" "Operating Systems"; do
echo "---- $s (ci) ----"
rg -n -C2 -i -F "$s" || echo "No matches for (ci): $s"
done
echo
echo "== search for mapped DB column names =="
for s in "referrer" "path" "device_type" "browser_name" "os_name"; do
echo "---- $s ----"
rg -n -C2 -F "$s" || echo "No matches for: $s"
doneLength of output: 280595
Use stable tab IDs (not user-facing titles) for tableFilterMapping
Verified findings:
- tableFilterMapping is declared only at packages/shared/src/lists/filters.ts and I found no other references to that symbol in the repo.
- The display titles ("Traffic Sources", "Pages", "Devices", "Browsers", "Operating Systems") are used widely in the UI and API constants (examples: apps/dashboard/.../overview-tab.tsx, performance-tab.tsx; apps/api/src/query/constants.ts).
Recommended change (small refactor):
- Replace user-facing title keys with stable IDs (enum-like/tab IDs) or centralize labels in one place. If tableFilterMapping is not used, remove or rename it to avoid confusion.
Current (lines 13-20):
// Table title to filter field mapping - use exact database column names
export const tableFilterMapping: Record<string, string> = {
'Traffic Sources': 'referrer',
'Pages': 'path',
'Devices': 'device_type',
'Browsers': 'browser_name',
'Operating Systems': 'os_name',
};Suggested replacement:
// Map stable tab IDs to filter field names
export const tableFilterMapping: Record<string, string> = {
traffic: 'referrer',
pages: 'path',
devices: 'device_type',
browsers: 'browser_name',
operating_systems: 'os_name',
};Places to update or review:
- packages/shared/src/lists/filters.ts (apply refactor or remove)
- apps/api/src/query/constants.ts (uses stable ids like
traffic,pages,devices) - apps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsx and performance-tab.tsx (UI titles)
- Any Add Filter / onAddFilter handlers — ensure they use tab IDs (or a centralized label -> id mapping) instead of raw display strings
Action: please update the mapping to use stable IDs (or centralize display labels) and adjust any consumers to use those IDs; if mapping is unused, remove/rename it to prevent accidental coupling.
🤖 Prompt for AI Agents
In packages/shared/src/lists/filters.ts around lines 13 to 20, the mapping
currently uses user-facing table titles which are unstable; replace those keys
with stable tab IDs (e.g. traffic, pages, devices, browsers, operating_systems)
mapping to the same DB column names, then update all consumers to use the IDs
(check apps/api/src/query/constants.ts and the dashboard overview/performance
tab components and any Add Filter handlers) or remove/rename the mapping if it's
unused to avoid accidental coupling.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…d-table-filtering-website-overview-2025-08-15
|
sick, great feature, a little bit incompatible with the filter rewrite I'm doing, but very timely, gonna write them together now |
There was a problem hiding this comment.
Actionable comments posted: 17
🔭 Outside diff range comments (2)
apps/api/src/query/utils.ts (1)
191-205: Geo normalization: avoid writing empty code/name; return early if resolution failsIf
getCountryCode(currentName)returns an empty string (e.g., unknown input), we end up setting emptycountry_code/country_name. Short-circuit to preserve original row when resolution fails.Apply this diff:
function applyGeoNormalization(data: DataRow[]): DataRow[] { return data.map((row) => { - const currentName = getString(row.name) || getString(row.country); + const currentName = getString(row.name) || getString(row.country); if (!currentName) { return row; } const code = getCountryCode(currentName); + if (!code) { + return row; + } const name = getCountryName(code); return { ...row, country_code: code, country_name: name, } as DataRow; }); }apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-event-timeline.tsx (1)
52-55: Avoid array index in React keysFallbacking to
eventIndexviolates the guideline and can cause unstable renders. Prefer a deterministic composite key whenevent_idis absent.Apply this diff:
- key={event.event_id || eventIndex} + key={event.event_id || `${event.time}-${event.event_name}`}If
timegranularity may repeat, include additional stable fields (e.g.,path).
♻️ Duplicate comments (2)
packages/shared/src/lists/filters.ts (2)
13-20: Avoid mapping by user-facing titles; use stable tab IDs (duplicate of earlier feedback)Keying
tableFilterMappingby display labels is brittle (copy changes/i18n will break it). Prefer stable identifiers (e.g., tab ids). This was raised previously and still applies.Apply this diff (example using stable ids):
-// Table title to filter field mapping - use exact database column names -export const tableFilterMapping: Record<string, string> = { - 'Traffic Sources': 'referrer', - 'Pages': 'path', - 'Devices': 'device_type', - 'Browsers': 'browser_name', - 'Operating Systems': 'os_name', -}; +// Map stable tab IDs to filter field names +export const tableFilterMapping: Record<string, string> = { + traffic: 'referrer', + pages: 'path', + devices: 'device_type', + browsers: 'browser_name', + operating_systems: 'os_name', +};If there are existing consumers expecting titles, update them to use ids or remove this mapping if unused.
22-27: Replace local device map in overview-tab with the shared deviceDisplayToFilterMapFound a duplicate local map in the overview tab that still maps 'laptop' → 'mobile', diverging from the shared map. Remove the local map and import/use the shared one to keep a single source of truth.
Files to change:
- packages/shared/src/lists/filters.ts — exported deviceDisplayToFilterMap ('laptop': 'laptop', 'tablet': 'tablet', 'desktop': 'desktop') (around lines 23–26).
- apps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsx — local deviceDisplayToFilterMap (around lines 1232–1239) currently mapping 'laptop' → 'mobile' — remove this and import the shared map.
Suggested change (minimal diff):
-// Map display device names to filter values -const deviceDisplayToFilterMap: Record<string, string> = { - 'laptop': 'mobile', - 'tablet': 'tablet', - 'desktop': 'desktop', -}; +// reuse shared device->filter map +import { deviceDisplayToFilterMap } from 'packages/shared/src/lists/filters';Keep usage as: value: deviceDisplayToFilterMap[row.name] || row.name
Also scan the codebase for any other local device->filter mappings and replace them with the shared export to avoid future divergence.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (16)
apps/api/src/query/builders/sessions.ts(2 hunks)apps/api/src/query/utils.ts(1 hunks)apps/dashboard/app/(main)/websites/[id]/_components/analytics-toolbar.tsx(1 hunks)apps/dashboard/app/(main)/websites/[id]/layout.tsx(1 hunks)apps/dashboard/app/(main)/websites/[id]/page.tsx(5 hunks)apps/dashboard/app/(main)/websites/[id]/sessions/_components/index.ts(0 hunks)apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-event-timeline.tsx(1 hunks)apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-row.tsx(1 hunks)apps/dashboard/app/(main)/websites/[id]/sessions/_components/sessions-list.tsx(1 hunks)apps/dashboard/app/(main)/websites/[id]/sessions/page.tsx(2 hunks)apps/dashboard/app/(main)/websites/page.tsx(0 hunks)apps/dashboard/hooks/use-dynamic-query.ts(0 hunks)apps/dashboard/stores/jotai/filterAtoms.ts(3 hunks)apps/dashboard/stores/jotai/sessionAtoms.ts(1 hunks)packages/shared/src/lists/filters.ts(1 hunks)packages/shared/src/types/analytics.ts(3 hunks)
💤 Files with no reviewable changes (3)
- apps/dashboard/hooks/use-dynamic-query.ts
- apps/dashboard/app/(main)/websites/[id]/sessions/_components/index.ts
- apps/dashboard/app/(main)/websites/page.tsx
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{jsx,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't pass children as props.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't forget key props in iterators and collection literals.
Don't destructure props inside JSX components in Solid projects.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign to React component props.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use dangerous JSX props.
Don't use Array index in keys.
Don't insert comments as text nodes.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Watch out for possible "wrong" semicolons inside JSX elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Don't use distracting elements like or .
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible ...Files:
apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-row.tsxapps/dashboard/app/(main)/websites/[id]/sessions/_components/session-event-timeline.tsxapps/dashboard/app/(main)/websites/[id]/_components/analytics-toolbar.tsxapps/dashboard/app/(main)/websites/[id]/layout.tsxapps/dashboard/app/(main)/websites/[id]/sessions/_components/sessions-list.tsxapps/dashboard/app/(main)/websites/[id]/sessions/page.tsxapps/dashboard/app/(main)/websites/[id]/page.tsx**/*.{js,ts,jsx,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts,jsx,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't use global eval().
Don't use var.
Don't use console.
Don't use debugger.
Don't assign directly to document.cookie.
Don't use duplicate case labels.
Don't use duplicate class members.
Don't use duplicate function parameter names.
Don't use empty block statements and static blocks.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Use for...of statements instead of Array.forEach.
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names that aren't base 10 or use underscore separators.
Remove redundant terms from logical expressions.
Use while loops instead of for loops when you don't need initializer and update expressions.
Don't reassign const variables.
Don't use constant expressions in conditions.
Don't use Math.min and Math.max to clamp values when the result is constant.
Don't return a value from a constructor.
Don't use empty character classes in regular expression literals.
Don't use empty destructuring patterns.
Don't call global object properties as functions.
Don't declare functions and vars that are accessible outside their block.
Don't use variables and function parameters before they're declared.
Don't use 8 and 9 escape sequences in string literals.
Don't use literal numbers that lose precision.
Don't use duplicate conditions in if-else-if chains.
Don't use two keys with the same name inside objects...Files:
apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-row.tsxapps/dashboard/app/(main)/websites/[id]/sessions/_components/session-event-timeline.tsxpackages/shared/src/types/analytics.tsapps/api/src/query/utils.tsapps/dashboard/app/(main)/websites/[id]/_components/analytics-toolbar.tsxapps/api/src/query/builders/sessions.tsapps/dashboard/stores/jotai/filterAtoms.tsapps/dashboard/app/(main)/websites/[id]/layout.tsxpackages/shared/src/lists/filters.tsapps/dashboard/app/(main)/websites/[id]/sessions/_components/sessions-list.tsxapps/dashboard/app/(main)/websites/[id]/sessions/page.tsxapps/dashboard/stores/jotai/sessionAtoms.tsapps/dashboard/app/(main)/websites/[id]/page.tsx**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use TypeScript namespaces.
Don't use the TypeScript directive @ts-ignore.
Don't use any type.
Don't use implicit any type on variable declarations.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't declare empty interfaces.
Use export type for types.
Use import type for types.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
**/*.{ts,tsx}: Ensure TypeScript type-safety; avoidanyunless absolutely necessary
Create proper interfaces/types for APIs, responses, and components; place them in shared types folders rather than in the same fileFiles:
apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-row.tsxapps/dashboard/app/(main)/websites/[id]/sessions/_components/session-event-timeline.tsxpackages/shared/src/types/analytics.tsapps/api/src/query/utils.tsapps/dashboard/app/(main)/websites/[id]/_components/analytics-toolbar.tsxapps/api/src/query/builders/sessions.tsapps/dashboard/stores/jotai/filterAtoms.tsapps/dashboard/app/(main)/websites/[id]/layout.tsxpackages/shared/src/lists/filters.tsapps/dashboard/app/(main)/websites/[id]/sessions/_components/sessions-list.tsxapps/dashboard/app/(main)/websites/[id]/sessions/page.tsxapps/dashboard/stores/jotai/sessionAtoms.tsapps/dashboard/app/(main)/websites/[id]/page.tsx**/*.{js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)
**/*.{js,jsx,ts,tsx}: Usebun <file>instead ofnode <file>orts-node <file>
Do not use dotenv; Bun automatically loads .env files
Do not useexpress; useBun.serve()for HTTP servers
Do not usebetter-sqlite3; usebun:sqlitefor SQLite
Do not useioredis; useBun.redisfor Redis
Do not usepgorpostgres.js; useBun.sqlfor Postgres
Do not usews; use built-inWebSocket
PreferBun.fileovernode:fs's readFile/writeFile
UseBun.$instead of execa for running shell commandsFiles:
apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-row.tsxapps/dashboard/app/(main)/websites/[id]/sessions/_components/session-event-timeline.tsxpackages/shared/src/types/analytics.tsapps/api/src/query/utils.tsapps/dashboard/app/(main)/websites/[id]/_components/analytics-toolbar.tsxapps/api/src/query/builders/sessions.tsapps/dashboard/stores/jotai/filterAtoms.tsapps/dashboard/app/(main)/websites/[id]/layout.tsxpackages/shared/src/lists/filters.tsapps/dashboard/app/(main)/websites/[id]/sessions/_components/sessions-list.tsxapps/dashboard/app/(main)/websites/[id]/sessions/page.tsxapps/dashboard/stores/jotai/sessionAtoms.tsapps/dashboard/app/(main)/websites/[id]/page.tsx**/*.{tsx,jsx}
📄 CodeRabbit Inference Engine (.cursor/rules/01-MUST-DO.mdc)
**/*.{tsx,jsx}: In Tailwind CSS usage, useroundedonly; do not userounded-mdorrounded-xl
Use Phosphor icons, not Lucide; default to weight="duotone", use fill for arrows, and for plus icons do not set weight
In view components, decouple state management, data transformations, and API interactions from the React lifecycle
Simplify data flow in components to avoid prop drilling and callback chains
Prioritize modularity and testability in all React components
Use React Error Boundaries appropriately
Avoid useEffect in React unless it is truly criticalFiles:
apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-row.tsxapps/dashboard/app/(main)/websites/[id]/sessions/_components/session-event-timeline.tsxapps/dashboard/app/(main)/websites/[id]/_components/analytics-toolbar.tsxapps/dashboard/app/(main)/websites/[id]/layout.tsxapps/dashboard/app/(main)/websites/[id]/sessions/_components/sessions-list.tsxapps/dashboard/app/(main)/websites/[id]/sessions/page.tsxapps/dashboard/app/(main)/websites/[id]/page.tsx**/*.{ts,tsx,js,jsx}
📄 CodeRabbit Inference Engine (.cursor/rules/01-MUST-DO.mdc)
**/*.{ts,tsx,js,jsx}: Use console methods appropriately (e.g., console.error, console.time, console.table, console.json)
Use Dayjs instead of date-fns
Use TanStack Query for hooks; do not use SWR
When adding debugging, use JSON.stringify() for structured outputFiles:
apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-row.tsxapps/dashboard/app/(main)/websites/[id]/sessions/_components/session-event-timeline.tsxpackages/shared/src/types/analytics.tsapps/api/src/query/utils.tsapps/dashboard/app/(main)/websites/[id]/_components/analytics-toolbar.tsxapps/api/src/query/builders/sessions.tsapps/dashboard/stores/jotai/filterAtoms.tsapps/dashboard/app/(main)/websites/[id]/layout.tsxpackages/shared/src/lists/filters.tsapps/dashboard/app/(main)/websites/[id]/sessions/_components/sessions-list.tsxapps/dashboard/app/(main)/websites/[id]/sessions/page.tsxapps/dashboard/stores/jotai/sessionAtoms.tsapps/dashboard/app/(main)/websites/[id]/page.tsx**/*.{tsx,jsx,ts,js}
📄 CodeRabbit Inference Engine (.cursor/rules/01-MUST-DO.mdc)
When using Phosphor React icons, append 'Icon' to component names (e.g., CaretIcon, not Caret)
Files:
apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-row.tsxapps/dashboard/app/(main)/websites/[id]/sessions/_components/session-event-timeline.tsxpackages/shared/src/types/analytics.tsapps/api/src/query/utils.tsapps/dashboard/app/(main)/websites/[id]/_components/analytics-toolbar.tsxapps/api/src/query/builders/sessions.tsapps/dashboard/stores/jotai/filterAtoms.tsapps/dashboard/app/(main)/websites/[id]/layout.tsxpackages/shared/src/lists/filters.tsapps/dashboard/app/(main)/websites/[id]/sessions/_components/sessions-list.tsxapps/dashboard/app/(main)/websites/[id]/sessions/page.tsxapps/dashboard/stores/jotai/sessionAtoms.tsapps/dashboard/app/(main)/websites/[id]/page.tsx**/*.{js,ts}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts}: Make sure to use the "use strict" directive in script files.
Don't have redundant "use strict".
Make sure to use the "use strict" directive in script files.Files:
packages/shared/src/types/analytics.tsapps/api/src/query/utils.tsapps/api/src/query/builders/sessions.tsapps/dashboard/stores/jotai/filterAtoms.tspackages/shared/src/lists/filters.tsapps/dashboard/stores/jotai/sessionAtoms.ts**/*.{html,ts,css}
📄 CodeRabbit Inference Engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)
Use
bun build <file.html|file.ts|file.css>instead ofwebpackoresbuildFiles:
packages/shared/src/types/analytics.tsapps/api/src/query/utils.tsapps/api/src/query/builders/sessions.tsapps/dashboard/stores/jotai/filterAtoms.tspackages/shared/src/lists/filters.tsapps/dashboard/stores/jotai/sessionAtoms.ts🧠 Learnings (1)
📚 Learning: 2025-08-11T10:37:28.346Z
Learnt from: CR PR: databuddy-analytics/Databuddy#0 File: .cursor/rules/01-MUST-DO.mdc:0-0 Timestamp: 2025-08-11T10:37:28.346Z Learning: Applies to **/*.{ts,tsx,js,jsx} : Use TanStack Query for hooks; do not use SWRApplied to files:
apps/dashboard/app/(main)/websites/[id]/page.tsx🧬 Code Graph Analysis (4)
apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-row.tsx (3)
apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-utils.tsx (1)
getCountryFlag(58-72)apps/dashboard/app/(main)/websites/[id]/profiles/_components/profile-utils.tsx (1)
getCountryFlag(51-65)packages/db/src/drizzle/schema.ts (1)
session(105-141)apps/dashboard/app/(main)/websites/[id]/layout.tsx (3)
apps/dashboard/hooks/use-tracking-setup.ts (1)
useTrackingSetup(3-26)apps/dashboard/stores/jotai/filterAtoms.ts (2)
isAnalyticsRefreshingAtom(267-267)dynamicQueryFiltersAtom(274-274)apps/dashboard/app/(main)/websites/[id]/_components/analytics-toolbar.tsx (1)
AnalyticsToolbar(26-225)apps/dashboard/app/(main)/websites/[id]/sessions/_components/sessions-list.tsx (4)
apps/dashboard/stores/jotai/filterAtoms.ts (3)
formattedDateRangeAtom(25-35)timeGranularityAtom(40-40)dynamicQueryFiltersAtom(274-274)apps/dashboard/stores/jotai/sessionAtoms.ts (2)
expandedSessionIdAtom(17-17)getSessionPageAtom(23-38)apps/dashboard/hooks/use-dynamic-query.ts (1)
useDynamicQuery(178-248)apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-row.tsx (1)
SessionRow(333-333)apps/dashboard/app/(main)/websites/[id]/page.tsx (1)
apps/dashboard/stores/jotai/filterAtoms.ts (5)
timeGranularityAtom(40-40)formattedDateRangeAtom(25-35)timezoneAtom(64-66)isAnalyticsRefreshingAtom(267-267)dynamicQueryFiltersAtom(274-274)🔇 Additional comments (12)
packages/shared/src/types/analytics.ts (1)
42-43: Good: stricter typing for event propertiesSwitching to
Record<string, unknown>is a solid step to avoidanyleakage and encourages proper value formatting.packages/shared/src/lists/filters.ts (1)
9-11: New filter options look goodAdding Referrer and Page Path aligns with the new row-to-filter workflow.
apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-row.tsx (1)
126-132: LGTM: prefer country_code for flag rendering, with country fallbackThis ensures correct ISO code is used for flagcdn URLs while preserving robustness.
apps/dashboard/app/(main)/websites/[id]/sessions/_components/session-event-timeline.tsx (1)
19-20: Good: safer typing for propertiesChanging to
Record<string, unknown>improves type safety and pairs well withformatPropertyValue.apps/dashboard/stores/jotai/filterAtoms.ts (1)
217-224: LGTM! Good defensive programming practiceUsing
Object.hasOwn()instead ofhasOwnPropertyis the modern approach and prevents issues with objects that override or lack thehasOwnPropertymethod.apps/api/src/query/builders/sessions.ts (1)
183-183: WHERE clause construction — verifiedbuildWhereClause (apps/api/src/query/utils.ts) returns
AND (${safeClauses.join(' AND ')})when conditions are present, so thecombinedWhereClause.replace('AND ', '')in apps/api/src/query/builders/sessions.ts (line ~183) will correctly strip the leadingANDwith the current implementation. No change required.
- apps/api/src/query/builders/sessions.ts — line ~183:
WHERE ${combinedWhereClause.replace('AND ', '')}- apps/api/src/query/utils.ts —
export function buildWhereClause(...) { ... return \AND (${safeClauses.join(' AND ')})`; }`apps/dashboard/app/(main)/websites/[id]/sessions/page.tsx (2)
7-20: LGTM! Clean module import refactoringThe direct import from
./components/sessions-listis cleaner than using a barrel export, especially since the index barrel was removed. The dynamic import configuration remains unchanged, preserving the loading and SSR behavior.
26-26: No change needed — parent layout includes responsive paddingVerified that the websites layout wraps pages with responsive padding, so removing local
p-3 sm:p-4 lg:p-6is safe.
- apps/dashboard/app/(main)/websites/[id]/sessions/page.tsx — uses:
- apps/dashboard/app/(main)/websites/[id]/layout.tsx — provides:
(wraps {children})apps/dashboard/app/(main)/websites/[id]/page.tsx (1)
167-171: Update dependency array after fixing addFilterOnce the
addFilterfunction is properly defined, ensure it's correctly memoized withuseCallbackto prevent unnecessary re-renders of tab content.The dependency array includes
addFilterbut it's not defined. After implementing the function withuseCallback, the dependency array will be correct.apps/dashboard/stores/jotai/sessionAtoms.ts (1)
23-38: LGTM! Well-structured pagination atom with proper reset handling.The implementation correctly handles both reading the page for a specific website and resetting the pagination state. The dependency on
filterDependencyAtomensures automatic reset when filters change.apps/dashboard/app/(main)/websites/[id]/_components/analytics-toolbar.tsx (1)
1-17: LGTM! Clean imports and proper separation of concerns.The imports are well-organized, and the component correctly uses client-side rendering with the 'use client' directive.
apps/dashboard/app/(main)/websites/[id]/sessions/_components/sessions-list.tsx (1)
145-166: IntersectionObserver cleanup could be more robust.The observer cleanup uses a captured ref value which might be stale.
Use the ref directly in cleanup:
useEffect(() => { const observer = new IntersectionObserver( (entries) => { const entry = entries[0]; if (entry?.isIntersecting && hasNextPage && !isLoading) { setPage(page + 1); } }, { threshold: 0.1 } ); - const currentRef = loadMoreRef.current; - if (currentRef) { - observer.observe(currentRef); + if (loadMoreRef.current) { + observer.observe(loadMoreRef.current); } return () => { - if (currentRef) { - observer.unobserve(currentRef); - } + if (loadMoreRef.current) { + observer.unobserve(loadMoreRef.current); + } + observer.disconnect(); }; }, [hasNextPage, isLoading, setPage, page]);Likely an incorrect or invalid review comment.
| plugins: { | ||
| normalizeGeo: true, | ||
| }, |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Document the normalizeGeo plugin behavior
The normalizeGeo plugin is added but its behavior and impact on the query results are not documented. Consider adding a comment explaining what this plugin does.
+ // normalizeGeo plugin enriches geo data with normalized country names and codes
plugins: {
normalizeGeo: true,
},📝 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.
| plugins: { | |
| normalizeGeo: true, | |
| }, | |
| // normalizeGeo plugin enriches geo data with normalized country names and codes | |
| plugins: { | |
| normalizeGeo: true, | |
| }, |
🤖 Prompt for AI Agents
In apps/api/src/query/builders/sessions.ts around lines 196 to 198, the
normalizeGeo plugin is enabled but undocumented; add a concise comment
immediately above this plugin config that states exactly what normalizeGeo does
(e.g., which geo fields it transforms, whether it converts to GeoJSON or ensures
{lat,lng} vs [lon,lat], any rounding/precision applied, how it affects
filtering/sorting/index usage, and any possible side effects or required
upstream formats), and include an example input → output snippet and note if it
mutates the original object or returns a normalized copy.
| const handleQuickRangeSelect = useCallback( | ||
| (range: (typeof quickRanges)[0]) => { | ||
| const now = new Date(); | ||
| const start = range.hours | ||
| ? dayjs(now).subtract(range.hours, 'hour').toDate() | ||
| : dayjs(now) | ||
| .subtract(range.days || 7, 'day') | ||
| .toDate(); | ||
| setDateRangeAction({ startDate: start, endDate: now }); | ||
| }, | ||
| [setDateRangeAction] | ||
| ); |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Simplify date calculation logic to avoid duplication.
The date calculation logic for quick ranges is duplicated between the handler and the render loop.
Extract the date calculation into a shared utility:
+const calculateQuickRangeDate = (range: typeof quickRanges[0]) => {
+ const now = new Date();
+ const start = range.hours
+ ? dayjs(now).subtract(range.hours, 'hour').toDate()
+ : dayjs(now).subtract(range.days || 7, 'day').toDate();
+ return { start, end: now };
+};
const handleQuickRangeSelect = useCallback(
(range: (typeof quickRanges)[0]) => {
- const now = new Date();
- const start = range.hours
- ? dayjs(now).subtract(range.hours, 'hour').toDate()
- : dayjs(now)
- .subtract(range.days || 7, 'day')
- .toDate();
- setDateRangeAction({ startDate: start, endDate: now });
+ const { start, end } = calculateQuickRangeDate(range);
+ setDateRangeAction({ startDate: start, endDate: end });
},
[setDateRangeAction]
);Then use it in the render loop (line 116-130) as well.
📝 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.
| const handleQuickRangeSelect = useCallback( | |
| (range: (typeof quickRanges)[0]) => { | |
| const now = new Date(); | |
| const start = range.hours | |
| ? dayjs(now).subtract(range.hours, 'hour').toDate() | |
| : dayjs(now) | |
| .subtract(range.days || 7, 'day') | |
| .toDate(); | |
| setDateRangeAction({ startDate: start, endDate: now }); | |
| }, | |
| [setDateRangeAction] | |
| ); | |
| const calculateQuickRangeDate = (range: typeof quickRanges[0]) => { | |
| const now = new Date(); | |
| const start = range.hours | |
| ? dayjs(now).subtract(range.hours, 'hour').toDate() | |
| : dayjs(now).subtract(range.days || 7, 'day').toDate(); | |
| return { start, end: now }; | |
| }; | |
| const handleQuickRangeSelect = useCallback( | |
| (range: (typeof quickRanges)[0]) => { | |
| const { start, end } = calculateQuickRangeDate(range); | |
| setDateRangeAction({ startDate: start, endDate: end }); | |
| }, | |
| [setDateRangeAction] | |
| ); |
| {selectedFilters.map((filter, index) => { | ||
| const fieldLabel = filterOptions.find( | ||
| (o) => o.value === filter.field | ||
| )?.label; | ||
| const operatorLabel = operatorOptions.find( | ||
| (o) => getOperatorShorthand(o.value) === filter.operator | ||
| )?.label; | ||
| const valueLabel = Array.isArray(filter.value) | ||
| ? filter.value.join(', ') | ||
| : filter.value; | ||
|
|
||
| return ( | ||
| <div | ||
| className="flex items-center gap-0 rounded border bg-background py-1 pr-2 pl-3 shadow-sm" | ||
| key={`filter-${index}-${filter.field}-${filter.operator}`} | ||
| > | ||
| <div className="flex items-center gap-1"> | ||
| <span className="font-medium text-foreground text-sm"> | ||
| {fieldLabel} | ||
| </span> | ||
| <span className="text-muted-foreground/70 text-sm"> | ||
| {operatorLabel} | ||
| </span> | ||
| <span className="font-medium text-foreground text-sm"> | ||
| {valueLabel} | ||
| </span> | ||
| </div> | ||
| <button | ||
| aria-label={`Remove filter ${fieldLabel} ${operatorLabel} ${valueLabel}`} | ||
| className="flex h-6 w-6 items-center justify-center rounded hover:bg-muted/50" | ||
| onClick={() => removeFilter(index)} | ||
| type="button" | ||
| > | ||
| <XIcon aria-hidden="true" className="h-3 w-3" /> | ||
| </button> | ||
| </div> | ||
| ); | ||
| })} |
There was a problem hiding this comment.
Add null safety for filter value display.
The filter chip rendering assumes filter.value is always defined and can be stringified. This could cause runtime errors if the value is null or undefined.
Apply this fix to handle null/undefined values:
const valueLabel = Array.isArray(filter.value)
? filter.value.join(', ')
- : filter.value;
+ : filter.value?.toString() ?? '';📝 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.
| {selectedFilters.map((filter, index) => { | |
| const fieldLabel = filterOptions.find( | |
| (o) => o.value === filter.field | |
| )?.label; | |
| const operatorLabel = operatorOptions.find( | |
| (o) => getOperatorShorthand(o.value) === filter.operator | |
| )?.label; | |
| const valueLabel = Array.isArray(filter.value) | |
| ? filter.value.join(', ') | |
| : filter.value; | |
| return ( | |
| <div | |
| className="flex items-center gap-0 rounded border bg-background py-1 pr-2 pl-3 shadow-sm" | |
| key={`filter-${index}-${filter.field}-${filter.operator}`} | |
| > | |
| <div className="flex items-center gap-1"> | |
| <span className="font-medium text-foreground text-sm"> | |
| {fieldLabel} | |
| </span> | |
| <span className="text-muted-foreground/70 text-sm"> | |
| {operatorLabel} | |
| </span> | |
| <span className="font-medium text-foreground text-sm"> | |
| {valueLabel} | |
| </span> | |
| </div> | |
| <button | |
| aria-label={`Remove filter ${fieldLabel} ${operatorLabel} ${valueLabel}`} | |
| className="flex h-6 w-6 items-center justify-center rounded hover:bg-muted/50" | |
| onClick={() => removeFilter(index)} | |
| type="button" | |
| > | |
| <XIcon aria-hidden="true" className="h-3 w-3" /> | |
| </button> | |
| </div> | |
| ); | |
| })} | |
| {selectedFilters.map((filter, index) => { | |
| const fieldLabel = filterOptions.find( | |
| (o) => o.value === filter.field | |
| )?.label; | |
| const operatorLabel = operatorOptions.find( | |
| (o) => getOperatorShorthand(o.value) === filter.operator | |
| )?.label; | |
| const valueLabel = Array.isArray(filter.value) | |
| ? filter.value.join(', ') | |
| : filter.value?.toString() ?? ''; | |
| return ( | |
| <div | |
| className="flex items-center gap-0 rounded border bg-background py-1 pr-2 pl-3 shadow-sm" | |
| key={`filter-${index}-${filter.field}-${filter.operator}`} | |
| > | |
| <div className="flex items-center gap-1"> | |
| <span className="font-medium text-foreground text-sm"> | |
| {fieldLabel} | |
| </span> | |
| <span className="text-muted-foreground/70 text-sm"> | |
| {operatorLabel} | |
| </span> | |
| <span className="font-medium text-foreground text-sm"> | |
| {valueLabel} | |
| </span> | |
| </div> | |
| <button | |
| aria-label={`Remove filter ${fieldLabel} ${operatorLabel} ${valueLabel}`} | |
| className="flex h-6 w-6 items-center justify-center rounded hover:bg-muted/50" | |
| onClick={() => removeFilter(index)} | |
| type="button" | |
| > | |
| <XIcon aria-hidden="true" className="h-3 w-3" /> | |
| </button> | |
| </div> | |
| ); | |
| })} |
| <Button onClick={() => onFiltersChange([])} variant="outline"> | ||
| Clear all filters | ||
| </Button> |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Consider confirming before clearing all filters.
Clearing all filters is a destructive action that users might trigger accidentally.
Consider adding a confirmation or using a more descriptive button label:
-<Button onClick={() => onFiltersChange([])} variant="outline">
- Clear all filters
+<Button
+ onClick={() => {
+ if (window.confirm('Are you sure you want to clear all filters?')) {
+ onFiltersChange([]);
+ }
+ }}
+ variant="outline"
+>
+ Clear all filters
</Button>📝 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.
| <Button onClick={() => onFiltersChange([])} variant="outline"> | |
| Clear all filters | |
| </Button> | |
| <Button | |
| onClick={() => { | |
| if (window.confirm('Are you sure you want to clear all filters?')) { | |
| onFiltersChange([]); | |
| } | |
| }} | |
| variant="outline" | |
| > | |
| Clear all filters | |
| </Button> |
🤖 Prompt for AI Agents
In apps/dashboard/app/(main)/websites/[id]/_components/analytics-toolbar.tsx
around lines 217-219, the "Clear all filters" button performs a destructive
action without confirmation; add a confirmation step (modal or native confirm)
before calling onFiltersChange([]) so the user must explicitly confirm before
filters are cleared. Implement the confirm dialog to show a clear message and
Cancel/Confirm actions, call onFiltersChange([]) only when the user confirms,
and optionally disable the button when there are no active filters or change the
label to make the action more explicit.
| const handleRefresh = async () => { | ||
| setIsRefreshing(true); | ||
| try { | ||
| await Promise.all([ | ||
| queryClient.invalidateQueries({ queryKey: ['websites', id] }), | ||
| queryClient.invalidateQueries({ | ||
| queryKey: ['websites', 'isTrackingSetup', id], | ||
| }), | ||
| queryClient.invalidateQueries({ queryKey: ['dynamic-query', id] }), | ||
| queryClient.invalidateQueries({ | ||
| queryKey: ['batch-dynamic-query', id], | ||
| }), | ||
| ]); | ||
| toast.success('Data refreshed'); | ||
| } catch { | ||
| toast.error('Failed to refresh data'); | ||
| } finally { | ||
| setIsRefreshing(false); | ||
| } | ||
| }; |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Add error handling for individual query invalidations
The current implementation catches all errors together. Consider handling individual query invalidation failures to provide more granular error feedback.
const handleRefresh = async () => {
setIsRefreshing(true);
+ const errors: string[] = [];
try {
- await Promise.all([
- queryClient.invalidateQueries({ queryKey: ['websites', id] }),
- queryClient.invalidateQueries({
- queryKey: ['websites', 'isTrackingSetup', id],
- }),
- queryClient.invalidateQueries({ queryKey: ['dynamic-query', id] }),
- queryClient.invalidateQueries({
- queryKey: ['batch-dynamic-query', id],
- }),
- ]);
+ const invalidations = [
+ { key: ['websites', id], name: 'website data' },
+ { key: ['websites', 'isTrackingSetup', id], name: 'tracking setup' },
+ { key: ['dynamic-query', id], name: 'dynamic queries' },
+ { key: ['batch-dynamic-query', id], name: 'batch queries' },
+ ];
+
+ const results = await Promise.allSettled(
+ invalidations.map(({ key }) =>
+ queryClient.invalidateQueries({ queryKey: key })
+ )
+ );
+
+ results.forEach((result, index) => {
+ if (result.status === 'rejected') {
+ errors.push(invalidations[index].name);
+ }
+ });
+
+ if (errors.length > 0) {
+ toast.warning(`Partially refreshed. Failed: ${errors.join(', ')}`);
+ } else {
+ toast.success('Data refreshed');
+ }
- toast.success('Data refreshed');
} catch {
toast.error('Failed to refresh data');
} finally {
setIsRefreshing(false);
}
};📝 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.
| const handleRefresh = async () => { | |
| setIsRefreshing(true); | |
| try { | |
| await Promise.all([ | |
| queryClient.invalidateQueries({ queryKey: ['websites', id] }), | |
| queryClient.invalidateQueries({ | |
| queryKey: ['websites', 'isTrackingSetup', id], | |
| }), | |
| queryClient.invalidateQueries({ queryKey: ['dynamic-query', id] }), | |
| queryClient.invalidateQueries({ | |
| queryKey: ['batch-dynamic-query', id], | |
| }), | |
| ]); | |
| toast.success('Data refreshed'); | |
| } catch { | |
| toast.error('Failed to refresh data'); | |
| } finally { | |
| setIsRefreshing(false); | |
| } | |
| }; | |
| const handleRefresh = async () => { | |
| setIsRefreshing(true); | |
| const errors: string[] = []; | |
| try { | |
| const invalidations = [ | |
| { key: ['websites', id], name: 'website data' }, | |
| { key: ['websites', 'isTrackingSetup', id], name: 'tracking setup' }, | |
| { key: ['dynamic-query', id], name: 'dynamic queries' }, | |
| { key: ['batch-dynamic-query', id], name: 'batch queries' }, | |
| ]; | |
| const results = await Promise.allSettled( | |
| invalidations.map(({ key }) => | |
| queryClient.invalidateQueries({ queryKey: key }) | |
| ) | |
| ); | |
| results.forEach((result, index) => { | |
| if (result.status === 'rejected') { | |
| errors.push(invalidations[index].name); | |
| } | |
| }); | |
| if (errors.length > 0) { | |
| toast.warning(`Partially refreshed. Failed: ${errors.join(', ')}`); | |
| } else { | |
| toast.success('Data refreshed'); | |
| } | |
| } catch { | |
| toast.error('Failed to refresh data'); | |
| } finally { | |
| setIsRefreshing(false); | |
| } | |
| }; |
🤖 Prompt for AI Agents
In apps/dashboard/app/(main)/websites/[id]/layout.tsx around lines 27 to 46, the
current handleRefresh wraps all query invalidations in a single try/catch which
masks which invalidation failed; change the logic to call Promise.allSettled on
the array of queryClient.invalidateQueries promises, inspect the results to
detect per-query failures, log or collect the specific errors (including which
queryKey failed), show a success toast if all settled as fulfilled or an error
toast summarizing which specific invalidations failed, and keep setIsRefreshing
toggling in finally; this gives granular feedback while preserving the original
flow.
|
|
||
| if (!allSessions.length) { | ||
| // Empty state | ||
| if (!(allSessions.length || isLoading)) { |
There was a problem hiding this comment.
Fix logical operator precedence issue.
The condition !(allSessions.length || isLoading) is confusing and might not work as intended.
Apply this fix for clearer logic:
-if (!(allSessions.length || isLoading)) {
+if (!allSessions.length && !isLoading) {📝 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.
| if (!(allSessions.length || isLoading)) { | |
| if (!allSessions.length && !isLoading) { |
🤖 Prompt for AI Agents
In
apps/dashboard/app/(main)/websites/[id]/sessions/_components/sessions-list.tsx
around line 219, the condition using !(allSessions.length || isLoading) is
misleading; replace it with an explicit check such as allSessions.length === 0
&& !isLoading (or !allSessions.length && !isLoading) so the branch only runs
when there are no sessions and loading is false, improving clarity and
correctness.
| // --- Dynamic Query Filters (for shared package compatibility) --- | ||
| /** | ||
| * Atom for storing DynamicQueryFilter[] from @databuddy/shared | ||
| * This is used for the analytics toolbar and shared across all website pages | ||
| */ | ||
| export const dynamicQueryFiltersAtom = atom<DynamicQueryFilter[]>([]); |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Consider implementing filter persistence
The dynamicQueryFiltersAtom is initialized as an empty array. Consider whether filters should persist across page navigations or browser sessions. You might want to add localStorage persistence using Jotai's atomWithStorage to improve user experience.
If you want to persist filters across sessions, you could refactor like this:
+import { atomWithStorage } from 'jotai/utils';
+
// --- Dynamic Query Filters (for shared package compatibility) ---
/**
* Atom for storing DynamicQueryFilter[] from @databuddy/shared
* This is used for the analytics toolbar and shared across all website pages
*/
-export const dynamicQueryFiltersAtom = atom<DynamicQueryFilter[]>([]);
+export const dynamicQueryFiltersAtom = atomWithStorage<DynamicQueryFilter[]>(
+ 'analytics-dynamic-filters',
+ []
+);🤖 Prompt for AI Agents
In apps/dashboard/stores/jotai/filterAtoms.ts around lines 269 to 274,
dynamicQueryFiltersAtom is created as a plain atom([]) which loses filters
across navigations/sessions; replace it with a persistent atom using Jotai's
atomWithStorage (import from jotai/utils) with a clear storage key (e.g.,
"dashboard:dynamicQueryFilters"), a default of [] and proper typing, and ensure
safe access in SSR by guarding localStorage (or using a no-op fallback) and
optionally adding a migration/validation step to handle malformed or
older-format data so the atom always yields a valid DynamicQueryFilter[].
| // Create a dependency atom that changes when filters/date range change | ||
| const filterDependencyAtom = atom((get) => ({ | ||
| dateRange: get(formattedDateRangeAtom), | ||
| granularity: get(timeGranularityAtom), | ||
| filters: get(dynamicQueryFiltersAtom), | ||
| })); |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Consider the performance implications of filterDependencyAtom.
The filterDependencyAtom creates a new object reference on every read, which will trigger dependent atoms to re-evaluate whenever any parent state is accessed. While this ensures proper reset behavior, it might cause unnecessary re-renders.
Consider using a more selective dependency tracking approach:
-// Create a dependency atom that changes when filters/date range change
-const filterDependencyAtom = atom((get) => ({
- dateRange: get(formattedDateRangeAtom),
- granularity: get(timeGranularityAtom),
- filters: get(dynamicQueryFiltersAtom),
-}));
+// Create a dependency atom that changes when filters/date range change
+const filterDependencyAtom = atom((get) => {
+ const dateRange = get(formattedDateRangeAtom);
+ const granularity = get(timeGranularityAtom);
+ const filters = get(dynamicQueryFiltersAtom);
+ // Create a stable key for comparison
+ return `${dateRange.startDate}-${dateRange.endDate}-${granularity}-${JSON.stringify(filters)}`;
+});Committable suggestion skipped: line range outside the PR's diff.
| // Auto-reset atoms when filters change | ||
| export const autoResetSessionStateAtom = atom( | ||
| (get) => get(filterDependencyAtom), | ||
| (_get, set) => { | ||
| // Reset all session state when filters change | ||
| set(expandedSessionIdAtom, RESET); | ||
| set(sessionPageAtom, RESET); | ||
| } | ||
| ); |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Consider adding error handling for the reset operation.
While the reset logic is correct, there's no error handling if the reset operations fail.
Consider wrapping the reset operations in a try-catch:
export const autoResetSessionStateAtom = atom(
(get) => get(filterDependencyAtom),
(_get, set) => {
- // Reset all session state when filters change
- set(expandedSessionIdAtom, RESET);
- set(sessionPageAtom, RESET);
+ try {
+ // Reset all session state when filters change
+ set(expandedSessionIdAtom, RESET);
+ set(sessionPageAtom, RESET);
+ } catch (error) {
+ console.error('Failed to reset session state:', error);
+ }
}
);📝 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.
| // Auto-reset atoms when filters change | |
| export const autoResetSessionStateAtom = atom( | |
| (get) => get(filterDependencyAtom), | |
| (_get, set) => { | |
| // Reset all session state when filters change | |
| set(expandedSessionIdAtom, RESET); | |
| set(sessionPageAtom, RESET); | |
| } | |
| ); | |
| // Auto-reset atoms when filters change | |
| export const autoResetSessionStateAtom = atom( | |
| (get) => get(filterDependencyAtom), | |
| (_get, set) => { | |
| try { | |
| // Reset all session state when filters change | |
| set(expandedSessionIdAtom, RESET); | |
| set(sessionPageAtom, RESET); | |
| } catch (error) { | |
| console.error('Failed to reset session state:', error); | |
| } | |
| } | |
| ); |
🤖 Prompt for AI Agents
In apps/dashboard/stores/jotai/sessionAtoms.ts around lines 40 to 48, the
autoResetSessionStateAtom performs two set(…, RESET) calls with no error
handling; wrap the two reset set calls inside a try-catch so any thrown errors
are caught, log a clear contextual error including the caught error (e.g.,
console.error or the app logger) and the atom names, and optionally decide
whether to rethrow or swallow the error (prefer logging and not rethrowing to
avoid breaking filter changes). Ensure both resets are attempted inside the try
block so a failure of the first does not prevent attempting the second.
| country_code: string; | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Adding required country_code may break existing producers; verify all API outputs populate it
Making country_code required on BaseSessionData tightens the contract. Ensure all API/query builders that return session/profile data now include country_code (the PR adds normalizeGeo, but confirm coverage across endpoints).
Run this script to locate producers and confirm country_code presence in API mappers/builders:
🏁 Script executed:
#!/bin/bash
# Show places where session/profile payloads are shaped
rg -n -C3 -P "(SessionData|ProfileData|BaseSessionData)\b" --type=ts --type=tsx
echo
echo "== Search for country_code in API/query builders and transformers =="
rg -n -C3 -S "country_code"
echo
echo "== Check sessions builders using normalizeGeo plugin =="
rg -n -C3 -S "normalizeGeo|applyGeoNormalization|plugins" apps/api/src
echo
echo "== Grep for profile shaping (ensure country_code is present) =="
rg -n -C3 -i "profile.*country" appsLength of output: 21232
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== packages/shared/src/types/analytics.ts =="
sed -n '1,240p' packages/shared/src/types/analytics.ts || true
echo
echo "== apps/api/src/query/utils.ts (around DataRow & applyGeoNormalization) =="
sed -n '1,320p' apps/api/src/query/utils.ts || true
echo
echo "== apps/api/src/query/builders/sessions.ts (plugin usage & mapping) =="
sed -n '1,360p' apps/api/src/query/builders/sessions.ts || true
echo
echo "== apps/api/src/query/builders/traffic.ts (plugin usage & mapping) =="
sed -n '1,360p' apps/api/src/query/builders/traffic.ts || true
echo
echo "== apps/api/src/query/builders/performance.ts =="
sed -n '1,220p' apps/api/src/query/builders/performance.ts || true
echo
echo "== apps/api/src/query/builders/geo.ts =="
sed -n '1,220p' apps/api/src/query/builders/geo.ts || true
echo
echo "== Search for other places shaping session/profile objects =="
rg -n -C2 "country_code" || true
rg -n -C2 "session_id:|country: getCountryCode|profile.*country" || trueLength of output: 58011
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Type references (SessionData / ProfileData / BaseSessionData) =="
rg -n --hidden -S '\b(SessionData|ProfileData|BaseSessionData)\b' || true
echo
echo "== Object literals containing session_id (candidates for producers) =="
rg -n --hidden -S 'session_id\s*:' -g '!**/node_modules/**' -C2 || true
echo
echo "== Places assigning country_code in object literals =="
rg -n --hidden -S 'country_code\s*:' -g '!**/node_modules/**' -C2 || true
echo
echo "== getCountryCode / getCountryName usages =="
rg -n --hidden -S 'getCountryCode\(|getCountryName\(' || true
echo
echo "== apps/dashboard/hooks/use-dynamic-query.ts (mapping around profiles/sessions) =="
sed -n '660,840p' apps/dashboard/hooks/use-dynamic-query.ts || true
echo
echo "== apps/api/src - search for returned JSON shaping responses =="
rg -n --hidden -S 'return\s+{\s*.*session_id|res\.json\(|res\.status\(' apps/api/src -g '!**/node_modules/**' -C2 || true
echo
echo "== session_list builder and applyPlugins link check =="
sed -n '1,260p' apps/api/src/query/builders/sessions.ts || true
sed -n '1,260p' apps/api/src/query/utils.ts || trueLength of output: 36035
Make producers emit country_code — BaseSessionData now requires it
BaseSessionData now requires country_code. I checked the codebase: query plugins (applyGeoNormalization in apps/api/src/query/utils.ts) will populate country_code for DataRow outputs and several builders enable normalizeGeo (session_list, performance_by_country, geo.country), but frontend mappers still omit a dedicated country_code property and will fail TypeScript checks.
Files to fix (minimal changes needed):
- apps/dashboard/hooks/use-dynamic-query.ts
- transformSessionsData: add country_code: countryCode to the returned session objects.
- transformProfilesData: add country_code to the top-level ProfileData and to each sessionData object (use getCountryCode(...) — you currently set country to the code but not country_code).
- apps/api/src/query/utils.ts
- applyGeoNormalization already sets country_code on DataRow — confirm the query execution path calls applyPlugins(...) before responses are serialized so API responses include country_code.
- Verify other producers that construct session/profile objects (search results showed other mappings and builders) and add country_code where they construct objects directly.
Suggested minimal patch examples:
- In apps/dashboard/hooks/use-dynamic-query.ts, replace
country: countryCode,
country_name: countryName,
with
country: countryCode,
country_code: countryCode,
country_name: countryName,
(and the same for profile/session objects inside transformProfilesData).
🤖 Prompt for AI Agents
packages/shared/src/types/analytics.ts (around lines 15-16) and related
frontend/back-end mappers: BaseSessionData now requires country_code but several
producers omit it; update producers to emit country_code. In
apps/dashboard/hooks/use-dynamic-query.ts update transformSessionsData to
include country_code: countryCode on returned session objects, and update
transformProfilesData to include country_code on the top-level ProfileData and
on each sessionData object (use getCountryCode(...) the same way you set
country). In apps/api/src/query/utils.ts confirm applyGeoNormalization runs
before response serialization so DataRow already contains country_code; if not,
ensure applyPlugins(...) / applyGeoNormalization is invoked prior to building
API responses. Search other builders/mappers that construct session/profile
objects and add country_code where objects are created to satisfy the
BaseSessionData type.
…AddFilter wiring
Pull Request
Description
Please include a summary of the change and which issue is fixed. Also include relevant motivation and context.
Checklist
Summary by CodeRabbit
New Features
Style