Skip to content

Conversation

nishant-sharechat
Copy link
Member

@nishant-sharechat nishant-sharechat commented Aug 11, 2025

Summary by CodeRabbit

  • New Features

    • Per-source-type search filters, per-key "Load more" for facets, connection included in searches; admin-gated UI via new useAuthEmails hook; new connection/time helper exports.
  • Chores

    • Single multi-stage Docker image serving API+frontend with startup checks and healthcheck; new Jenkins pipeline for gated build/push.
  • Tweaks

    • 15-minute fixed default time range (live-tail off), deferred select clearing, default line-wrapping enabled, Enter hides autocomplete, removed "Add to Filters" action, raised metadata/query limits, configurable pagination page size and per-query ClickHouse settings.

Copy link

coderabbitai bot commented Aug 11, 2025

Walkthrough

Adds a multi-stage Dockerfile and Jenkins Kubernetes pipeline; updates container entry script with preflight checks; raises ClickHouse/metadata limits and defaults; and implements frontend changes for connection/time/auth wiring, per-source facet lazy-loading, configurable pagination pageSize, and multiple UI tweaks.

Changes

Cohort / File(s) Summary of Changes
Containerization & Runtime
Dockerfile, docker/hyperdx/entry.prod.sh
New multi-stage Dockerfile (Node 22.16.0 Alpine) building workspaces into one runtime image; non-root user; installs concurrently; exposes 8000/8080; HEALTHCHECK; ENTRYPOINT -> entry.prod.sh. entry.prod.sh adds banner, preflight checks for built artifacts, and starts API, Next.js app, and alert task using built distributions.
CI/CD Pipeline
Jenkinsfile
New Declarative Pipeline running in a Pod with privileged dind and builder containers; mounts emptyDir and Jenkins SA secret; exposes Docker at tcp://localhost:2375; passes GITHUB_TOKEN as build-arg; stages: build (armory build) and conditional push (armory push) for main, staging, feature/*; deployment ID derived from commit.
Frontend — Filters & Facets
packages/app/src/components/DBSearchPageFilters.tsx
Adds serviceMapOverride + getKeysForSourceType; component accepts sourceType?, sourceName?, connectionName?; introduces extraFacets, per-key load-more state and API (loadMoreFilterValuesForKey), auto-trigger in FilterGroup, merges extra values into shown facets, preserves pinning/filtering, and adds console debug logs. FilterGroupProps extended for load-more state.
Frontend — Page Wiring, Time & Connection
packages/app/src/DBSearchPage.tsx, packages/app/src/hooks/useAutoCompleteOptions.tsx, packages/app/src/hooks/useMetadata.tsx
DBSearchPage: fixed 15-minute default time range, isLive default false, adds useConnections, derives/propagates connection in form/state/URL and SearchConfigSchema. Autocomplete uses 15-min default and infers timestamp column. Metadata hook: sets ClickHouse max_rows_to_read from config, increases keys slice to 50 and per-key limit to 1000, reduces staleTime to 30s.
Frontend — Team, Auth & Config
packages/app/src/TeamPage.tsx, packages/app/src/config.ts, packages/app/src/hooks/useAuthEmails.tsx
Adds config exports HDX_AUTH_EMAILS, HDX_METADATA_MAX_ROWS_TO_READ, HDX_DEFAULT_PAGE_SIZE. New useAuthEmails hook parses HDX_AUTH_EMAILS and exposes hasAccess and getAuthorizedEmails. TeamPage gates UI sections/actions based on authorized emails and current user.
Frontend — Selects & Source Labels
packages/app/src/components/SelectControlled.tsx, packages/app/src/components/SourceSelect.tsx
SelectControlled defers clearing when options are not yet available. SourceSelect uses useConnections to prefix labels with `connectionName
Common Utils & ClickHouse Defaults
packages/common-utils/src/metadata.ts, packages/common-utils/src/clickhouse.ts
DEFAULT_METADATA_MAX_ROWS_TO_READ increased (3e6 → 10e6). Metadata.getKeyValues default limit changed to 1000; cache key now derived from chartConfig (${cacheKey}.values). ClickHouse client defaults max_rows_to_read='10000000' and read_overflow_mode='break', merged with external settings.
Other Frontend Adjustments
packages/app/src/components/DBRowTable.tsx, packages/app/src/components/SearchTotalCountChart.tsx, packages/app/src/hooks/useChartConfig.tsx, packages/app/src/components/DBRowJsonViewer.tsx, packages/app/src/AutocompleteInput.tsx, packages/app/src/searchFilters.tsx, packages/app/src/components/ContextSidePanel.tsx
DBRowTable enables wrap-lines by default. SearchTotalCountChart passes HDX_METADATA_MAX_ROWS_TO_READ to its query. useChartConfig adds optional clickhouseSettings?: Record<string, any> and forwards it to queries. DBRowJsonViewer removes the runtime "Add to Filters" action (commented-out). AutocompleteInput closes suggestion dropdown on Enter. searchFilters now syncs empty parsed filters into local state. ContextSidePanel infers Pod context from k8s.pod.name and conditionally includes Pod option.
Paging & Pagination API
packages/app/src/hooks/useOffsetPaginatedQuery.tsx
useOffsetPaginatedQuery accepts new pageSize option (default HDX_DEFAULT_PAGE_SIZE), propagates pageSize via meta to queryFn, uses pageSize as per-page limit, stops pagination when fetched rows < pageSize, and includes clickhouse_settings: { max_rows_to_read: HDX_METADATA_MAX_ROWS_TO_READ } in queries.
Typing / Signature Changes
packages/app/src/components/DBSearchPageFilters.tsx, packages/common-utils/src/metadata.ts, packages/app/src/hooks/useChartConfig.tsx, packages/app/src/hooks/useOffsetPaginatedQuery.tsx
Public/API changes: DBSearchPageFilters accepts sourceType?, sourceName?, connectionName?; FilterGroupProps extended; Metadata.getKeyValues default limit updated to 1000; useQueriedChartConfig options now support clickhouseSettings; useOffsetPaginatedQuery adds optional pageSize parameter (default from config).

Sequence Diagram(s)

sequenceDiagram
  participant Container
  participant Entry as /etc/local/entry.sh
  participant API
  participant App
  Container->>Entry: start (ENTRYPOINT)
  Entry->>Entry: print banner, run preflight checks (api dist, app .next)
  alt preflight OK
    Entry->>API: node -r ./packages/api/dist/tracing.js ./packages/api/dist/index.js
    Entry->>App: cd ./packages/app && HOSTNAME='0.0.0.0' ... yarn start
  else preflight fail
    Entry-->>Container: exit 1
  end
  Note over Container: HEALTHCHECK: GET http://localhost:8000/health
Loading
sequenceDiagram
  participant Dev
  participant Jenkins
  participant Pod
  participant Dind
  participant Builder
  participant Registry
  Dev->>Jenkins: push / PR
  Jenkins->>Pod: create Pod (dind + builder)
  Pod->>Dind: start Docker daemon (2375)
  Builder->>Dind: connect via DOCKER_HOST
  Jenkins->>Builder: armory build (GITHUB_TOKEN)
  alt branch ∈ {main,staging,feature/*}
    Jenkins->>Builder: armory push
    Builder->>Registry: push image
  end
Loading
sequenceDiagram
  participant User
  participant UI as DBSearchPageFilters
  participant Meta as Metadata Service
  User->>UI: open / interact (with sourceType/connection)
  UI->>UI: getKeysForSourceType(sourceName,connectionName)
  UI->>Meta: useGetKeyValues(initialKeys up to 50)
  Meta-->>UI: initial facet values
  User->>UI: click "load more" for key
  UI->>Meta: fetch additional values for key (limit up to 1000)
  Meta-->>UI: extra values
  UI->>UI: merge into extraFacets -> update shown facets
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I hop through Alpine with yarn upon my paws,
Pods hum, healthchecks tap, and builders learn their laws.
Facets stretch on call, time windows find their tune,
Auth guards the meadow while connections light the moon.
Carrots, builds, and logs — a rabbit’s cozy rune 🐇🚀


📜 Recent review details

Configuration used: Repository: ShareChat/coderabbit/.coderabbit.yaml

Review profile: CHILL

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.

📥 Commits

Reviewing files that changed from the base of the PR and between abf1ad3 and 6d84559.

📒 Files selected for processing (1)
  • packages/app/src/hooks/useOffsetPaginatedQuery.tsx (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/app/src/hooks/useOffsetPaginatedQuery.tsx
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch staging

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)

489-521: Consider pagination for large datasets

The loadMoreFilterValuesForKey function fetches a fixed limit of 200 values without offset/pagination support. For keys with more than 200 unique values, users won't be able to access all values.

Consider implementing pagination by tracking the offset for each key:

 const [extraFacets, setExtraFacets] = useState<Record<string, string[]>>({});
+const [keyOffsets, setKeyOffsets] = useState<Record<string, number>>({});

 const loadMoreFilterValuesForKey = useCallback(
   async (key: string) => {
     setLoadMoreLoadingKeys(prev => new Set(prev).add(key));
     try {
       const metadata = getMetadata();
+      const currentOffset = keyOffsets[key] || keyLimit;
       const newKeyVals = await metadata.getKeyValues({
         chartConfig: {
           ...chartConfig,
           dateRange,
         },
         keys: [key],
         limit: 200,
+        offset: currentOffset,
         disableRowLimit: true,
       });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 345ff7e and 2c9c653.

📒 Files selected for processing (4)
  • Dockerfile (1 hunks)
  • Jenkinsfile (1 hunks)
  • docker/hyperdx/entry.prod.sh (1 hunks)
  • packages/app/src/components/DBSearchPageFilters.tsx (3 hunks)
🔇 Additional comments (7)
Jenkinsfile (2)

63-86: Pipeline stages look good

The build and push stages are appropriately configured with branch filtering for deployments.


55-61: Confirm Docker build usage and secret management
I didn’t find any docker build or docker.build invocations in your Jenkinsfile, nor an ARG GITHUB_TOKEN in your Dockerfile. Please verify that you’re not accidentally passing GITHUB_TOKEN into your image layers. Consider using BuildKit secrets or multi-stage builds to keep credentials out of final artifacts.

  • Search all sh or pipeline steps for any --build-arg buildarg_GITHUB_TOKEN=… usage.
  • Ensure your Dockerfile doesn’t declare or consume ARG GITHUB_TOKEN.
  • If you must pass secrets at build time, switch to BuildKit’s --secret mechanism or bind credentials via Jenkins plugins.
docker/hyperdx/entry.prod.sh (2)

15-24: Good addition of preflight checks

The preflight checks ensure required build artifacts exist before attempting to start services, providing clear error messages for debugging.


30-32: Service startup commands properly updated

The commands correctly reference the built distribution files and use appropriate startup methods for production deployment.

Dockerfile (3)

1-8: Well-structured multi-stage Dockerfile

Good use of multi-stage builds with Alpine base image for smaller footprint. The NODE_VERSION ARG provides flexibility.


55-60: Good security practice with non-root user

Running the container as a non-root user is a security best practice. The ownership is properly set with --chown.


20-22: Confirm skip-build implications

I ran a search across all package.json files and found no postinstall scripts defined, so yarn install --mode=skip-build won’t skip any project-level lifecycle hooks. However, note that --mode=skip-build also suppresses dependency install scripts and native module builds. Please verify:

  • None of your dependencies rely on postinstall, prepare, or other install-time scripts to function correctly.
  • Any required native modules or build steps are handled elsewhere (e.g., prebuilt binaries or CI jobs).

Comment on lines +47 to +49
ARG CODE_VERSION=2.1.1

ENV CODE_VERSION=2.1.1
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix CODE_VERSION environment variable

The ENV CODE_VERSION=2.1.1 hardcodes the value and overrides the ARG, making the build argument ineffective.

 ARG CODE_VERSION=2.1.1
 
-ENV CODE_VERSION=2.1.1
+ENV CODE_VERSION=${CODE_VERSION}
🤖 Prompt for AI Agents
In Dockerfile around lines 47 to 49, the ENV line hardcodes CODE_VERSION and
overrides the ARG; replace the hardcoded ENV with one that references the build
ARG (e.g. ENV CODE_VERSION=${CODE_VERSION}) or remove the ENV entirely if you
only need the ARG during build time, ensuring the ARG value is propagated
correctly to the environment when required.

Comment on lines +91 to +92
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure curl is available for health check

The HEALTHCHECK uses curl but it's not explicitly installed in the production stage. Alpine images may not include curl by default.

Add curl installation in the production stage:

 FROM node:${NODE_VERSION}-alpine AS production
 
 ARG CODE_VERSION=2.1.1
 
+# Install curl for healthcheck
+RUN apk add --no-cache curl
+
 ENV CODE_VERSION=${CODE_VERSION}
 ENV NODE_ENV production
📝 Committable suggestion

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

Suggested change
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
FROM node:${NODE_VERSION}-alpine AS production
ARG CODE_VERSION=2.1.1
# Install curl for healthcheck
RUN apk add --no-cache curl
ENV CODE_VERSION=${CODE_VERSION}
ENV NODE_ENV production
🤖 Prompt for AI Agents
In Dockerfile around lines 91-92, the HEALTHCHECK uses curl but the production
stage doesn't install it; add a package install for curl in that stage before
the HEALTHCHECK. If the base image is Alpine add a line like "RUN apk add
--no-cache curl", if it's Debian/Ubuntu add "RUN apt-get update && apt-get
install -y curl && rm -rf /var/lib/apt/lists/*", placing it in the production
stage so curl is available for the HEALTHCHECK command.

Comment on lines +9 to +45
containers:
- name: dind
image: sc-mum-armory.platform.internal/devops/dind:v2
securityContext:
privileged: true
env:
- name: DOCKER_HOST
value: tcp://localhost:2375
- name: DOCKER_TLS_CERTDIR
value: ""
volumeMounts:
- name: dind-storage
mountPath: /var/lib/docker
readinessProbe:
tcpSocket:
port: 2375
initialDelaySeconds: 30
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 2375
initialDelaySeconds: 30
periodSeconds: 20
- name: builder
image: sc-mum-armory.platform.internal/devops/builder-image-armory
command:
- sleep
- infinity
env:
- name: DOCKER_HOST
value: tcp://localhost:2375
- name: DOCKER_BUILDKIT
value: "0"
volumeMounts:
- name: jenkins-sa
mountPath: /root/.gcp/
volumes:
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add resource limits for containers

The Pod specification lacks resource limits and requests for both containers, which could lead to resource contention and pod instability.

Add resource limits to prevent unbounded resource consumption:

 spec:
   containers:
   - name: dind
     image: sc-mum-armory.platform.internal/devops/dind:v2
+    resources:
+      requests:
+        memory: "512Mi"
+        cpu: "500m"
+      limits:
+        memory: "2Gi"
+        cpu: "2000m"
     securityContext:
       privileged: true
   - name: builder
     image: sc-mum-armory.platform.internal/devops/builder-image-armory
+    resources:
+      requests:
+        memory: "1Gi"
+        cpu: "1000m"
+      limits:
+        memory: "4Gi"
+        cpu: "4000m"
     command:
     - sleep
📝 Committable suggestion

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

Suggested change
containers:
- name: dind
image: sc-mum-armory.platform.internal/devops/dind:v2
securityContext:
privileged: true
env:
- name: DOCKER_HOST
value: tcp://localhost:2375
- name: DOCKER_TLS_CERTDIR
value: ""
volumeMounts:
- name: dind-storage
mountPath: /var/lib/docker
readinessProbe:
tcpSocket:
port: 2375
initialDelaySeconds: 30
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 2375
initialDelaySeconds: 30
periodSeconds: 20
- name: builder
image: sc-mum-armory.platform.internal/devops/builder-image-armory
command:
- sleep
- infinity
env:
- name: DOCKER_HOST
value: tcp://localhost:2375
- name: DOCKER_BUILDKIT
value: "0"
volumeMounts:
- name: jenkins-sa
mountPath: /root/.gcp/
volumes:
containers:
- name: dind
image: sc-mum-armory.platform.internal/devops/dind:v2
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"
securityContext:
privileged: true
env:
- name: DOCKER_HOST
value: tcp://localhost:2375
- name: DOCKER_TLS_CERTDIR
value: ""
volumeMounts:
- name: dind-storage
mountPath: /var/lib/docker
readinessProbe:
tcpSocket:
port: 2375
initialDelaySeconds: 30
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 2375
initialDelaySeconds: 30
periodSeconds: 20
- name: builder
image: sc-mum-armory.platform.internal/devops/builder-image-armory
resources:
requests:
memory: "1Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "4000m"
command:
- sleep
- infinity
env:
- name: DOCKER_HOST
value: tcp://localhost:2375
- name: DOCKER_BUILDKIT
value: "0"
volumeMounts:
- name: jenkins-sa
mountPath: /root/.gcp/
volumes:
🤖 Prompt for AI Agents
In Jenkinsfile around lines 9 to 45, both the dind and builder container specs
are missing resource requests/limits; add a resources block under each container
with requests and limits for cpu and memory (and optionally ephemeral-storage)
to prevent unbounded consumption; set sensible values (e.g. requests smaller
than limits) appropriate for CI workloads and your cluster, validate units (m
for millicpu, Mi/Gi for memory), and adjust values after load testing or per
team policy.

Comment on lines +10 to +18
- name: dind
image: sc-mum-armory.platform.internal/devops/dind:v2
securityContext:
privileged: true
env:
- name: DOCKER_HOST
value: tcp://localhost:2375
- name: DOCKER_TLS_CERTDIR
value: ""
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Security concern: Privileged container without TLS

The dind container runs with privileged: true and DOCKER_TLS_CERTDIR: "" which disables TLS encryption for the Docker daemon. This exposes the Docker API without encryption.

Consider enabling TLS for the Docker daemon or document why this security trade-off is acceptable in your environment.

🤖 Prompt for AI Agents
In the Jenkinsfile, around lines 10-18, the dind container is running in
privileged mode without TLS encryption enabled for the Docker daemon. This poses
a security concern as it exposes the Docker API without encryption. To address
this issue, either enable TLS encryption for the Docker daemon or document the
rationale for accepting this security trade-off in your environment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🔭 Outside diff range comments (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)

420-433: Add TypeScript type for the sourceType prop.

The sourceType prop is added but its type definition is missing from the component's props interface.

Add the type definition:

 }: {
   analysisMode: 'results' | 'delta' | 'pattern';
   setAnalysisMode: (mode: 'results' | 'delta' | 'pattern') => void;
   isLive: boolean;
   chartConfig: ChartConfigWithDateRange;
   sourceId?: string;
+  sourceType?: string;
   showDelta: boolean;
   denoiseResults: boolean;
   setDenoiseResults: (denoiseResults: boolean) => void;
 } & FilterStateHook) => {
♻️ Duplicate comments (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)

459-459: Remove debug console statement.

Debug console.error statement should be removed before merging.

-    console.error('nishant is here === ', sourceType);
🧹 Nitpick comments (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)

526-526: Remove unused sourceType from dependency array.

The sourceType is not used within the loadMoreFilterValuesForKey callback but is included in its dependencies, which could trigger unnecessary re-renders.

-    [chartConfig, setExtraFacets, dateRange, sourceType],
+    [chartConfig, setExtraFacets, dateRange],
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c9c653 and 9a6ac0a.

📒 Files selected for processing (1)
  • packages/app/src/components/DBSearchPageFilters.tsx (6 hunks)
🔇 Additional comments (4)
packages/app/src/components/DBSearchPageFilters.tsx (4)

32-62: LGTM! Well-structured source type overrides.

The service map configuration and helper function provide a clean way to customize facet keys per source type while maintaining default behavior for unmapped types.


205-209: Good implementation of auto-loading behavior.

The effect correctly triggers lazy loading for filter values when the component mounts, ensuring data is available without user interaction.


487-488: Correct usage of source type overrides for key fetching.

The implementation properly applies the source-specific keys when fetching facet values.


531-579: Well-implemented facet merging and ordering logic.

The code properly handles merging of lazy-loaded facets, ensures all required keys are present, and maintains correct ordering with pinned fields prioritized.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)

456-466: Remove leftover debug log and clarify fallback behavior.

The console.error is a stray debug statement and should be removed. Also, relying on an empty array to trigger “default behavior” depends on hook semantics and may fetch nothing.

Apply this cleanup:

   const keysToFetch = useMemo(() => {
     // Override keys for specific source types
     if (sourceType && sourceType in serviceMapOverride) {
       return getKeysForSourceType(sourceType);
     }
-    console.error('nishant is here === ', sourceType);
     // For other source types, return empty array to use default behavior
     // This allows the system to fetch all available keys from metadata
     return [];
   }, [sourceType]);

Verify hook behavior when keys is []:

#!/bin/bash
# Locate and inspect useGetKeyValues to confirm how it treats keys: []
rg -n "function useGetKeyValues|const useGetKeyValues|export function useGetKeyValues|export const useGetKeyValues" -A 50
rg -n "keys:" -A 5 -B 5
🧹 Nitpick comments (5)
packages/app/src/components/DBSearchPageFilters.tsx (5)

55-61: getKeysForSourceType should be strongly typed to SourceKind to avoid string drift.

Using string comparisons here is error-prone. Prefer SourceKind.

// outside current diff range:
import { SourceKind } from '@hyperdx/common-utils/dist/types';

const getKeysForSourceType = (sourceType?: SourceKind) =>
  sourceType ? serviceMapOverride[sourceType] ?? [] : [];

487-490: Use the memoized keysToFetch to avoid duplicate logic and ensure consistency.

Currently you compute keys twice and rely on comments about default behavior. Use the same logic everywhere.

   const {
     data: facets,
     isLoading: isFacetsLoading,
     isFetching: isFacetsFetching,
   } = useGetKeyValues({
     chartConfigs: { ...chartConfig, dateRange },
     limit: keyLimit,
-    keys: getKeysForSourceType(sourceType),
+    keys: keysToFetch,
   });

494-529: Trim loadMoreFilterValuesForKey dependencies; sourceType isn’t used.

sourceType in deps causes unnecessary re-creations. Remove it unless you plan to vary behavior by sourceType.

-    [chartConfig, setExtraFacets, dateRange, sourceType],
+    [chartConfig, setExtraFacets, dateRange],

206-211: Auto “load more” on mount may fan-out requests for every facet.

This effect triggers network fetches for all groups as soon as they render. Consider gating behind user intent (e.g., only when expanded or when group is visible) to avoid N simultaneous requests on first paint.

Example:

-  useEffect(() => {
-    if (onLoadMore && !hasLoadedMore) {
-      onLoadMore(name);
-    }
-  }, [onLoadMore, hasLoadedMore, name]);
+  useEffect(() => {
+    if (onLoadMore && isExpanded && !hasLoadedMore) {
+      onLoadMore(name);
+    }
+  }, [onLoadMore, hasLoadedMore, name, isExpanded]);

If intentional, please confirm backend can handle this fan-out without rate limiting.


445-447: Remove unused variable numRows.

numRows is computed but unused; likely leftover.

-  const { data: countData } = useExplainQuery(chartConfig);
-  const numRows: number = countData?.[0]?.rows ?? 0;
+  const { data: countData } = useExplainQuery(chartConfig);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a6ac0a and f719fe2.

📒 Files selected for processing (2)
  • packages/app/src/DBSearchPage.tsx (1 hunks)
  • packages/app/src/components/DBSearchPageFilters.tsx (7 hunks)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔭 Outside diff range comments (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)

466-512: Drop console logs (no-console), simplify keysToFetch, and add missing dependency.

  • Remove debug console statements flagged by ESLint.
  • Prefer early return using getKeysForSourceType; fallback to data-derived keys only when no override exists.
  • Include isFieldPinned in deps so changes to pinning recompute keys reliably.
-   const keysToFetch = useMemo(() => {
-    console.log('🔍 keysToFetch - sourceType:', sourceType, 'available overrides:', Object.keys(serviceMapOverride));
-
-    // First check if we have a source type override
-    if (sourceType && sourceType in serviceMapOverride) {
-      const overrideKeys = serviceMapOverride[sourceType as keyof typeof serviceMapOverride];
-      console.log('✅ Using source type override for', sourceType, ':', overrideKeys);
-      return overrideKeys;
-    }
-
-    console.log('⚠️ No source type override found for', sourceType, '- falling back to data-based logic');
-
-    // If no source type override, fall back to data-based logic
+  const keysToFetch = useMemo(() => {
+    // Prefer source-type override; otherwise fall back to data-derived keys
+    const override = getKeysForSourceType(sourceType);
+    if (override.length > 0) return override;
+
+    // Fallback: derive from metadata
     if (!data) {
       return [];
     }
@@
-    return strings;
-  }, [sourceType, data, filterState, showMoreFields]);
+    return strings;
+  }, [sourceType, data, filterState, showMoreFields, isFieldPinned]);
🧹 Nitpick comments (4)
packages/app/src/components/DBSearchPageFilters.tsx (4)

211-216: Auto “load more” on mount can cause bursty background fetches. Confirm intent.

This effect will prefetch “more values” for every facet as soon as it renders. If facet count is large, this can generate high request volume.

Consider gating:

  • Only autoload for pinned fields or the first N visible facets.
  • Trigger when expanded/visible (IntersectionObserver), or debounce on first hover/click.

461-465: Remove commented dead code.

The old keysToFetch block is commented out and should be removed for clarity.

-  // const keysToFetch = useMemo(() => {
-  //   // Get keys for the source type - will return specific keys if available, or default keys otherwise
-  //   return getKeysForSourceType(sourceType);
-  // }, [sourceType]);

426-437: Type sourceType to SourceKind for compile-time safety.

This prevents key drift and ensures overrides apply correctly. You’re already importing from the same module.

-  sourceType?: string;
+  sourceType?: SourceKind;

Add import (outside current diff range):

import { ChartConfigWithDateRange, SourceKind } from '@hyperdx/common-utils/dist/types';

574-575: Tighten useCallback deps (avoid unnecessary identity churn).

sourceType and setExtraFacets aren’t used inside the callback; removing them reduces re-renders in children relying on onLoadMore identity.

-    [chartConfig, setExtraFacets, dateRange, sourceType],
+    [chartConfig, dateRange],
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f719fe2 and f9b9fd5.

📒 Files selected for processing (1)
  • packages/app/src/components/DBSearchPageFilters.tsx (8 hunks)
🧰 Additional context used
🪛 ESLint
packages/app/src/components/DBSearchPageFilters.tsx

[error] 32-32: Delete ··

(prettier/prettier)


[error] 33-33: Delete ··

(prettier/prettier)


[error] 34-34: Delete ··

(prettier/prettier)


[error] 35-35: Delete ··

(prettier/prettier)


[error] 36-36: Delete ··

(prettier/prettier)


[error] 37-37: Replace ······ with ····

(prettier/prettier)


[error] 38-38: Delete ··

(prettier/prettier)


[error] 39-39: Delete ··

(prettier/prettier)


[error] 40-40: Replace ···· with ··

(prettier/prettier)


[error] 41-41: Delete ··

(prettier/prettier)


[error] 42-42: Delete ··

(prettier/prettier)


[error] 43-43: Delete ··

(prettier/prettier)


[error] 44-44: Replace ······ with ····

(prettier/prettier)


[error] 45-45: Delete ··

(prettier/prettier)


[error] 46-46: Delete ··

(prettier/prettier)


[error] 47-51: Replace ····session:·[⏎······'ServiceName',⏎······'StatusCode',⏎······'SpanKind',⏎···· with ··session:·['ServiceName',·'StatusCode',·'SpanKind'

(prettier/prettier)


[error] 52-56: Replace ····metric:·[⏎······'ServiceName',⏎······'MetricName',⏎······'Unit',⏎···· with ··metric:·['ServiceName',·'MetricName',·'Unit'

(prettier/prettier)


[error] 57-57: Delete ··

(prettier/prettier)


[error] 466-466: Delete ·

(prettier/prettier)


[error] 467-467: Unexpected console statement.

(no-console)


[error] 467-467: Replace '🔍·keysToFetch·-·sourceType:',·sourceType,·'available·overrides:',·Object.keys(serviceMapOverride) with ⏎······'🔍·keysToFetch·-·sourceType:',⏎······sourceType,⏎······'available·overrides:',⏎······Object.keys(serviceMapOverride),⏎····

(prettier/prettier)


[error] 471-471: Insert ⏎·······

(prettier/prettier)


[error] 472-472: Unexpected console statement.

(no-console)


[error] 472-472: Replace '✅·Using·source·type·override·for',·sourceType,·':',·overrideKeys with ⏎········'✅·Using·source·type·override·for',⏎········sourceType,⏎········':',⏎········overrideKeys,⏎······

(prettier/prettier)


[error] 476-476: Unexpected console statement.

(no-console)


[error] 476-476: Replace '⚠️·No·source·type·override·found·for',·sourceType,·'-·falling·back·to·data-based·logic' with ⏎······'⚠️·No·source·type·override·found·for',⏎······sourceType,⏎······'-·falling·back·to·data-based·logic',⏎····

(prettier/prettier)

🔇 Additional comments (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)

59-66: Normalize sourceType to ensure override lookup succeeds.

Lowercasing sourceType before lookup fixes the mismatch and keeps the override logic centralized.

-const getKeysForSourceType = (sourceType?: string) => {
-  if (sourceType && sourceType in serviceMapOverride) {
-    return serviceMapOverride[sourceType as keyof typeof serviceMapOverride];
-  }
-  // For other source types, return empty array to use default behavior
-  return [];
-};
+const getKeysForSourceType = (sourceType?: string) => {
+  const key = sourceType?.toLowerCase().trim();
+  if (key && key in serviceMapOverride) {
+    return serviceMapOverride[key as keyof typeof serviceMapOverride];
+  }
+  // For other source types, return empty array to use default behavior
+  return [];
+};

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🔭 Outside diff range comments (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)

454-513: Remove all debug console statements and fix typo

Multiple debug console.error statements need to be removed before merging. Also, there's a typo: "erroric" should be "logic" on lines 469 and 471.

Remove all console statements:

   const keysToFetch = useMemo(() => {
-    console.error('🔍 keysToFetch function called with:');
-    console.error('  - sourceType:', sourceType);
-    console.error('  - data available:', !!data);
-    console.error('  - data length:', data?.length || 0);
-    console.error('  - serviceMapOverride keys:', Object.keys(serviceMapOverride));
-
     // First check if we have a source type override
     if (sourceType && sourceType in serviceMapOverride) {
       const overrideKeys =
         serviceMapOverride[sourceType as keyof typeof serviceMapOverride];
-      console.error('✅ Using source type override for', sourceType, ':', overrideKeys);
       return overrideKeys;
     }

-    console.error('⚠️ No source type override found for', sourceType, '- falling back to data-based erroric');
-
-    // If no source type override, fall back to data-based erroric
+    // If no source type override, fall back to data-based logic
     if (!data) {
-      console.error('❌ No data available, returning empty array');
       return [];
     }
-    console.error('📊 Processing data for keysToFetch:');
-    console.error('  - Raw data sample:', data.slice(0, 3));
-    console.error('  - showMoreFields:', showMoreFields);
-    console.error('  - filterState keys:', Object.keys(filterState));

     const strings = data
       // ... rest of the logic
       
-    console.error('🎯 Final keysToFetch result:', strings);
-    console.error('  - Total keys found:', strings.length);
-    console.error('  - Keys:', strings);
-
     return strings;
♻️ Duplicate comments (2)
packages/app/src/components/DBSearchPageFilters.tsx (2)

534-538: Use keysToFetch for consistency

The useGetKeyValues hook should use the computed keysToFetch variable which includes fallback logic, rather than calling getKeysForSourceType directly which may return an empty array.

   const {
     data: facets,
     isLoading: isFacetsLoading,
     isFetching: isFacetsFetching,
   } = useGetKeyValues({
     chartConfigs: { ...chartConfig, dateRange },
     limit: keyLimit,
-    keys: getKeysForSourceType(sourceType),
+    keys: keysToFetch,
   });

32-58: Critical: Source type overrides won't apply due to case mismatch

The serviceMapOverride uses lowercase keys ('log', 'trace'), but DBSearchPage passes capitalized SourceKind values ('Log', 'Trace'). This case-sensitive mismatch means overrides will never be applied.

Apply this fix to normalize the sourceType before lookup:

 const getKeysForSourceType = (sourceType?: string) => {
-  if (sourceType && sourceType in serviceMapOverride) {
-    return serviceMapOverride[sourceType as keyof typeof serviceMapOverride];
+  if (sourceType) {
+    const normalizedType = sourceType.toLowerCase();
+    if (normalizedType in serviceMapOverride) {
+      return serviceMapOverride[normalizedType as keyof typeof serviceMapOverride];
+    }
   }
   // For other source types, return empty array to use default behavior
   return [];
 };

Alternatively, update the keys to match SourceKind values:

 const serviceMapOverride = {
-  log: [
+  Log: [
     'SeverityText',
     // ...
   ],
-  trace: [
+  Trace: [
     'ServiceName',
     // ...
   ],
   // ...
 };
🧹 Nitpick comments (2)
packages/app/src/components/DBSearchPageFilters.tsx (2)

545-577: Consider using proper logger instead of console.error

The error handling uses console.error which should be replaced with a proper logging mechanism.

       } catch (error) {
-        console.error('failed to fetch more keys', error);
+        // Use your app's logger here
+        // logger.error('Failed to fetch more filter values', { key, error });
       } finally {

459-465: Fix formatting issues flagged by ESLint

Run Prettier to fix the formatting issues in the console.error statements (though these should be removed anyway as per the previous comment).

npx prettier --write packages/app/src/components/DBSearchPageFilters.tsx
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f9b9fd5 and 802a4db.

📒 Files selected for processing (1)
  • packages/app/src/components/DBSearchPageFilters.tsx (10 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)
packages/app/src/searchFilters.tsx (2)
  • FilterStateHook (220-220)
  • usePinnedFilters (278-352)
🪛 ESLint
packages/app/src/components/DBSearchPageFilters.tsx

[error] 459-459: Replace '··-·serviceMapOverride·keys:',·Object.keys(serviceMapOverride) with ⏎······'··-·serviceMapOverride·keys:',⏎······Object.keys(serviceMapOverride),⏎····

(prettier/prettier)


[error] 465-465: Replace '✅·Using·source·type·override·for',·sourceType,·':',·overrideKeys with ⏎········'✅·Using·source·type·override·for',⏎········sourceType,⏎········':',⏎········overrideKeys,⏎······

(prettier/prettier)


[error] 469-469: Replace '⚠️·No·source·type·override·found·for',·sourceType,·'-·falling·back·to·data-based·erroric' with ⏎······'⚠️·No·source·type·override·found·for',⏎······sourceType,⏎······'-·falling·back·to·data-based·erroric',⏎····

(prettier/prettier)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
packages/app/src/components/DBSearchPageFilters.tsx (2)

455-525: Remove extensive debug logging and fix ESLint error.

The implementation logic for keysToFetch is correct - it prioritizes source type overrides and falls back to data-driven logic. However, there are several issues that need to be addressed:

  1. Extensive console.error statements should be removed before production
  2. ESLint formatting error on line 491
  3. Potential performance impact from excessive logging

Apply this diff to remove debug logging:

   const keysToFetch = useMemo(() => {
-    console.error('🔍 keysToFetch function called with:');
-    console.error('  - sourceType:', sourceType);
-    console.error('  - data available:', !!data);
-    console.error('  - data length:', data?.length || 0);
-    console.error(
-      '  - serviceMapOverride keys:',
-      Object.keys(serviceMapOverride),
-    );
-
     // First check if we have a source type override
     if (sourceType && sourceType in serviceMapOverride) {
       const overrideKeys =
         serviceMapOverride[sourceType as keyof typeof serviceMapOverride];
-      console.error(
-        '✅ Using source type override for',
-        sourceType,
-        ':',
-        overrideKeys,
-      );
       return overrideKeys;
     }
 
-    console.error(
-      '⚠️ No source type override found for',
-      sourceType,
-      '- falling back to data-based logic',
-    );
-
     // If no source type override, fall back to data-based logic
     if (!data) {
-      console.error('❌ No data available, returning empty array');
       return [];
     }
-    console.error('📊 Processing data for keysToFetch:');
-    console.error('  - Raw data sample:', data.slice(0, 3));
-    console.error('  - showMoreFields:', showMoreFields);
-    console.error('  - filterState keys:', Object.keys(filterState));
 
     const strings = data
       .sort((a, b) => {
         // First show low cardinality fields
         const isLowCardinality = (type: string) =>
           type.includes('LowCardinality');
         return isLowCardinality(a.type) && !isLowCardinality(b.type) ? -1 : 1;
       })
       .filter(
         field => field.jsType && ['string'].includes(field.jsType),
         // todo: add number type with sliders :D
       )
       .map(({ path, type }) => {
         return { type, path: mergePath(path) };
       })
       .filter(
         field =>
           showMoreFields ||
           field.type.includes('LowCardinality') || // query only low cardinality fields by default
           Object.keys(filterState).includes(field.path) || // keep selected fields
           isFieldPinned(field.path), // keep pinned fields
       )
       .map(({ path }) => path)
       .filter(
         path =>
           !['body', 'timestamp', '_hdx_body'].includes(path.toLowerCase()),
       );
 
-    console.error('🎯 Final keysToFetch result:', strings);
-    console.error('  - Total keys found:', strings.length);
-    console.error('  - Keys:', strings);
-
     return strings;
   }, [sourceType, data, filterState, showMoreFields]);

549-549: Inconsistent key fetching approach.

This line uses getKeysForSourceType(sourceType) directly, which can return an empty array and diverge from the computed keysToFetch that includes fallback logic. This inconsistency was flagged in previous reviews.

-    keys: getKeysForSourceType(sourceType),
+    keys: keysToFetch,
🧹 Nitpick comments (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)

449-452: Remove commented code before merging.

This commented-out code should be removed to keep the codebase clean.

-  // const keysToFetch = useMemo(() => {
-  //   // Get keys for the source type - will return specific keys if available, or default keys otherwise
-  //   return getKeysForSourceType(sourceType);
-  // }, [sourceType]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 352d9c8 and 37d36df.

📒 Files selected for processing (1)
  • packages/app/src/components/DBSearchPageFilters.tsx (10 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)
packages/app/src/searchFilters.tsx (2)
  • FilterStateHook (220-220)
  • usePinnedFilters (278-352)
🪛 ESLint
packages/app/src/components/DBSearchPageFilters.tsx

[error] 491-491: Replace '··-·filterState·keys:',·Object.keys(DBSearchPageFiltersComponent) with ⏎······'··-·filterState·keys:',⏎······Object.keys(DBSearchPageFiltersComponent),⏎····

(prettier/prettier)

🔇 Additional comments (6)
packages/app/src/components/DBSearchPageFilters.tsx (6)

94-96: LGTM! Well-documented empty function.

The emptyFn utility with clear documentation explaining why it's intentionally empty is good practice for avoiding double-firing of checkbox events.


205-209: Auto-trigger load more functionality looks correct.

The useEffect automatically triggers onLoadMore when the function exists and the data hasn't been loaded yet, which provides good UX for lazy loading.


420-420: sourceType prop integration is correct.

The component now properly accepts sourceType as an optional string prop, addressing the previous issue where sourceType was undefined.

Also applies to: 430-430


552-589: Lazy loading implementation is well-structured.

The extraFacets state management and loadMoreFilterValuesForKey function provide a solid implementation for lazy loading additional facet values. The error handling and loading state management are appropriate.


591-641: Enhanced shownFacets logic handles multiple data sources correctly.

The implementation properly merges facets from multiple sources (API response, extra facets, filter state, and keysToFetch) and handles the reordering to prioritize pinned fields. The logic ensures all expected keys are represented even with empty values.


32-58: Formatting issues resolved

I ran npx prettier --write packages/app/src/components/DBSearchPageFilters.tsx and the file was formatted successfully with no remaining ESLint or formatting errors.

Note: the CLI emitted warnings about deprecated Prettier options (jsxBracketSameLine and { fluid: false }) in your Prettier configuration. You may want to update your config to remove or replace these outdated settings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (4)
packages/app/src/components/DBSearchPageFilters.tsx (4)

32-58: Potential key mismatch between override map and sourceType values.

Based on past review comments, there were issues with sourceType values not matching the override keys. The current implementation uses lowercase keys (log, trace), but if sourceType comes from SourceKind enum values (typically capitalized like Log, Trace), the lookups will still fail.

Verify that the sourceType values passed to this component actually match these lowercase keys, or update the keys to match the actual SourceKind values.


488-562: Remove debug logging and approve the core logic.

The keysToFetch logic correctly prioritizes source-type overrides before falling back to data-based key selection. However, extensive debug logging from lines 489-560 must be removed.

Remove all console.error statements within this useMemo block while keeping the core logic intact.


586-587: Use keysToFetch for consistency instead of calling getKeysForSourceType directly.

As noted in previous reviews, using getKeysForSourceType(sourceType) directly can return an empty array and diverge from the computed keysToFetch which includes fallback logic.

-    keys: getKeysForSourceType(sourceType),
+    keys: keysToFetch,

435-482: Remove all debug console statements.

Extensive debug logging remains throughout the component despite past review comments requesting their removal. This debug output should not be in production code.

Remove all console.error statements from lines 435-482 and throughout the file (also present at lines ~489-560).

🧹 Nitpick comments (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)

594-626: Remove unused dependency from useCallback.

The loadMoreFilterValuesForKey function implementation is solid with proper error handling and state management. However, sourceType is included in the dependency array but not used within the function.

-    [chartConfig, setExtraFacets, dateRange, sourceType],
+    [chartConfig, setExtraFacets, dateRange],
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37d36df and 940eef7.

📒 Files selected for processing (1)
  • packages/app/src/components/DBSearchPageFilters.tsx (9 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/app/src/components/DBSearchPageFilters.tsx (3)
packages/app/src/searchFilters.tsx (2)
  • FilterStateHook (220-220)
  • usePinnedFilters (278-352)
packages/app/src/hooks/useExplainQuery.tsx (1)
  • useExplainQuery (8-36)
packages/app/src/hooks/useMetadata.tsx (1)
  • useAllFields (82-126)
🪛 ESLint
packages/app/src/components/DBSearchPageFilters.tsx

[error] 525-528: Replace ⏎······'··-·filterState·keys:',⏎······Object.keys(filterState),⏎···· with '··-·filterState·keys:',·Object.keys(filterState)

(prettier/prettier)

🔇 Additional comments (2)
packages/app/src/components/DBSearchPageFilters.tsx (2)

205-209: Good addition for auto-triggering lazy load on mount.

The useEffect correctly auto-triggers onLoadMore when the component mounts if data hasn't been loaded yet. This enhances the user experience by proactively loading facet values.


628-678: Excellent integration of lazy-loaded facet values.

The shownFacets logic properly merges extra facets with regular facets, ensures all keysToFetch keys are represented, and maintains the existing sorting behavior with pinned fields prioritized.

The implementation handles all edge cases correctly and provides a seamless user experience for the lazy-loading functionality.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)

488-559: Remove debug console statements from keysToFetch computation

The keysToFetch logic is correct, but contains extensive debug logging that should be removed.

   const keysToFetch = useMemo(() => {
-    console.error('🔍 keysToFetch function called with:');
-    console.error('  - sourceType:', sourceType);
-    console.error('  - data available:', !!data);
-    console.error('  - data length:', data?.length || 0);
-    console.error(
-      '  - serviceMapOverride keys:',
-      Object.keys(serviceMapOverride),
-    );

     // First check if we have a source type override
     if (sourceType && sourceType in serviceMapOverride) {
       const overrideKeys =
         serviceMapOverride[sourceType as keyof typeof serviceMapOverride];
-      console.error(
-        '✅ Using source type override for',
-        sourceType,
-        ':',
-        overrideKeys,
-      );
       return overrideKeys;
     }

-    console.error(
-      '⚠️ No source type override found for',
-      sourceType,
-      '- falling back to data-based logic',
-    );

     // If no source type override, fall back to data-based logic
     if (!data) {
-      console.error('❌ No data available, returning empty array');
       return [];
     }
-    console.error('📊 Processing data for keysToFetch:');
-    console.error('  - Raw data sample:', data.slice(0, 3));
-    console.error('  - showMoreFields:', showMoreFields);
-    console.error('  - filterState keys:', Object.keys(filterState));

     // ... rest of the logic

-    console.error('🎯 Final keysToFetch result:', strings);
-    console.error('  - Total keys found:', strings.length);
-    console.error('  - Keys:', strings);

     return strings;
   }, [sourceType, data, filterState, showMoreFields]);
♻️ Duplicate comments (3)
packages/app/src/components/DBSearchPageFilters.tsx (3)

32-58: Fix case mismatch in serviceMapOverride keys

The override map uses lowercase keys ("log", "trace"), but DBSearchPage passes SourceKind values which are capitalized (e.g., "Log", "Trace"). This causes the override lookup to fail.

Apply this fix to align keys with SourceKind values:

 const serviceMapOverride = {
-  log: [
+  Log: [
     'SeverityText',
     'ServiceName',
     "ResourceAttributes['k8s.cluster.name']",
     "ResourceAttributes['k8s.namespace.name']",
   ],
-  trace: [
+  Trace: [
     'ServiceName',
     'StatusCode',
     "ResourceAttributes['k8s.node.name']",
     "ResourceAttributes['k8s.owner.name']",
     'SpanKind',
   ],
   session: ['ServiceName', 'StatusCode', 'SpanKind'],
   metric: ['ServiceName', 'MetricName', 'Unit'],
 };

Also fix the ESLint formatting issues by running:

npx prettier --write packages/app/src/components/DBSearchPageFilters.tsx

583-584: Use keysToFetch instead of calling getKeysForSourceType directly

The useGetKeyValues hook should use the computed keysToFetch value which includes fallback logic, rather than calling getKeysForSourceType directly.

   const {
     data: facets,
     isLoading: isFacetsLoading,
     isFetching: isFacetsFetching,
   } = useGetKeyValues({
     chartConfigs: { ...chartConfig, dateRange },
     limit: keyLimit,
-    keys: getKeysForSourceType(sourceType),
+    keys: keysToFetch,
   });

436-481: Remove all debug console statements before merging

These extensive debug statements should not be in production code. They use console.error inappropriately and will clutter the console.

Remove all console statements:

-  // Log all component props and variables
-  console.error('🚀 DBSearchPageFiltersComponent - ALL PROPS AND VARIABLES:');
-  console.error('📋 Main Props:');
-  console.error('  - analysisMode:', analysisMode);
-  console.error('  - isLive:', isLive);
-  console.error('  - sourceId:', sourceId);
-  console.error('  - sourceType:', sourceType);
-  console.error('  - showDelta:', showDelta);
-  console.error('  - denoiseResults:', denoiseResults);
-  console.error('  - chartConfig:', chartConfig);
-  console.error('📊 FilterStateHook Props:');
-  console.error('  - filters (filterState):', filterState);
-  console.error('  - clearAllFilters function:', typeof clearAllFilters);
-  console.error('  - clearFilter function:', typeof clearFilter);
-  console.error('  - setFilterValue function:', typeof setFilterValue);
-  console.error('🔧 Hook Results:');

   const { toggleFilterPin, toggleFieldPin, isFilterPinned, isFieldPinned } =
     usePinnedFilters(sourceId ?? null);

-  console.error('  - toggleFilterPin function:', typeof toggleFilterPin);
-  console.error('  - toggleFieldPin function:', typeof toggleFieldPin);
-  console.error('  - isFilterPinned function:', typeof isFilterPinned);
-  console.error('  - isFieldPinned function:', typeof isFieldPinned);

   const { width, startResize } = useResizable(16, 'left');
-  console.error('  - width:', width);
-  console.error('  - startResize function:', typeof startResize);

   // ... (remove other console statements)
🧹 Nitpick comments (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)

622-622: Remove unnecessary sourceType from dependency array

The loadMoreFilterValuesForKey callback doesn't use sourceType, so it shouldn't be in the dependency array.

-    [chartConfig, setExtraFacets, dateRange, sourceType],
+    [chartConfig, setExtraFacets, dateRange],
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 940eef7 and e2db365.

📒 Files selected for processing (1)
  • packages/app/src/components/DBSearchPageFilters.tsx (9 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/app/src/components/DBSearchPageFilters.tsx (3)
packages/app/src/searchFilters.tsx (2)
  • FilterStateHook (220-220)
  • usePinnedFilters (278-352)
packages/app/src/hooks/useExplainQuery.tsx (1)
  • useExplainQuery (8-36)
packages/app/src/hooks/useMetadata.tsx (1)
  • useAllFields (82-126)


return strings;
}, [data, filterState, showMoreFields]);
}, [sourceType, data, filterState, showMoreFields]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add missing isFieldPinned to dependency array

The keysToFetch useMemo uses isFieldPinned but doesn't include it in the dependency array.

-  }, [sourceType, data, filterState, showMoreFields]);
+  }, [sourceType, data, filterState, showMoreFields, isFieldPinned]);
📝 Committable suggestion

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

Suggested change
}, [sourceType, data, filterState, showMoreFields]);
}, [sourceType, data, filterState, showMoreFields, isFieldPinned]);
🤖 Prompt for AI Agents
In packages/app/src/components/DBSearchPageFilters.tsx around line 559, the
useMemo that computes keysToFetch references isFieldPinned but doesn't list it
in the dependency array; update the dependency array to include isFieldPinned
(or if isFieldPinned is a function prop, ensure it's stable by wrapping it with
useCallback and then include it) so the memo recalculates when isFieldPinned
changes.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
packages/common-utils/src/metadata.ts (1)

459-461: Bug: cache key omits the limit parameter, causing incorrect cache hits

Requests with different limit values will share the same cache entry, returning stale/incorrect results.

-    return this.cache.getOrFetch(
-      `${chartConfig.from.databaseName}.${chartConfig.from.tableName}.${keys.join(',')}.${chartConfig.dateRange.toString()}.${disableRowLimit}.values`,
+    return this.cache.getOrFetch(
+      `${chartConfig.from.databaseName}.${chartConfig.from.tableName}.${keys.join(',')}.${chartConfig.dateRange.toString()}.${disableRowLimit}.${limit}.values`,
packages/app/src/hooks/useMetadata.tsx (1)

174-179: Cache correctness: include limit and the actual sliced keys in the React Query key

Currently the queryKey excludes limit and uses the unsliced keys, while the request uses keys.slice(0, 50). This can lead to cache collisions and unnecessary cache fragmentation.

-    queryKey: [
-      'useMetadata.useGetKeyValues',
-      ...chartConfigsArr.map(cc => ({ ...cc })),
-      ...keys,
-      disableRowLimit,
-    ],
+    queryKey: [
+      'useMetadata.useGetKeyValues',
+      ...chartConfigsArr.map(cc => ({ ...cc })),
+      { keys: keys.slice(0, 50), limit, disableRowLimit },
+    ],
packages/app/src/components/SourceSelect.tsx (1)

45-46: Missing dependency in useMemo leads to stale labels

connections is used inside the memo but not listed as a dependency; labels won’t update when connections load/change.

-    [data, onCreate, allowedSourceKinds, hasLocalDefaultSources],
+    [data, onCreate, allowedSourceKinds, hasLocalDefaultSources, connections],
packages/app/src/TeamPage.tsx (1)

595-613: Align “Delete Pending Invite” action with the same access gate used elsewhere.

Currently this is gated only by hasAdminAccess (true). Use the same canAdmin check you use for member removal to avoid exposing destructive actions to unauthorized users.

-                      {hasAdminAccess && (
+                      {canAdmin && (
packages/app/src/components/DBSearchPageFilters.tsx (1)

420-431: Prop should use SourceKind instead of string to prevent mismatches.

Tighten typing to align with map and callers.

-  sourceType,
+  sourceType,
@@
-  sourceType?: string;
+  sourceType?: SourceKind;
packages/app/src/DBSearchPage.tsx (1)

1095-1103: Bug: Initial time search passes a single ISO string, which parseTimeQuery likely can’t parse.

onSearch(newTimeRange.toISOString()) won’t match the expected “start - end” format (or a known token). This likely results in no-op or incorrect initial range.

Use onTimeRangeSelect with the fixed 15-minute range and the display string:

-      if (!searchParams.has('from') && !searchParams.has('to')) {
-        let newTimeRange = new Date(defaultTimeRange[0]);
-        onSearch(newTimeRange.toISOString());
-      }
+      if (!searchParams.has('from') && !searchParams.has('to')) {
+        onTimeRangeSelect(defaultTimeRange[0], defaultTimeRange[1], initialTimeDisplay);
+      }

Also, consider making initialTimeDisplay sensitive to UTC if desired.

♻️ Duplicate comments (2)
packages/app/src/components/DBSearchPageFilters.tsx (2)

483-487: Use the unified keysToFetch in useGetKeyValues to avoid divergence.

Avoid recomputing. This also helps when you later change fallback logic.

   const keysToFetch = useMemo(() => {
     // Keep keys static relative to source type, independent of current selections
     return getKeysForSourceType(sourceType);
   }, [sourceType]);
@@
   } = useGetKeyValues({
     chartConfigs: { ...chartConfig, dateRange },
     limit: keyLimit,
-    keys: getKeysForSourceType(sourceType),
+    keys: keysToFetch,
   });

Also applies to: 508-511


32-49: Override keys won’t match SourceKind; current map uses lowercase strings.

sourceType is passed as inputSourceObj?.kind (SourceKind). With lowercase keys (“log”, “trace”), overrides will never match, so no overrides apply.

Apply this diff to align with the enum and ensure matches:

-import { ChartConfigWithDateRange } from '@hyperdx/common-utils/dist/types';
+import { ChartConfigWithDateRange, SourceKind } from '@hyperdx/common-utils/dist/types';
@@
-// Override keys for specific source types
-const serviceMapOverride = {
-  log: [
+// Override keys for specific source types
+const serviceMapOverride: Partial<Record<SourceKind, string[]>> = {
+  [SourceKind.Log]: [
     'SeverityText',
     'ServiceName',
     "ResourceAttributes['k8s.cluster.name']",
     "ResourceAttributes['k8s.namespace.name']",
   ],
-  trace: [
+  [SourceKind.Trace]: [
     'ServiceName',
     'StatusCode',
     "ResourceAttributes['k8s.node.name']",
     "ResourceAttributes['k8s.owner.name']",
     'SpanKind',
   ],
-  session: ['ServiceName', 'StatusCode', 'SpanKind'],
-  metric: ['ServiceName', 'MetricName', 'Unit'],
+  // Add other kinds when applicable, e.g.
+  // [SourceKind.Metric]: ['ServiceName', 'MetricName', 'Unit'],
 };
🧹 Nitpick comments (18)
packages/app/src/components/SelectControlled.tsx (1)

18-24: Tighten the selected predicate to avoid false positives

The else branch returns true when an item lacks a value field, which can incorrectly treat the first such item as “selected”. Make the check strict.

-  const selected = props.data?.find(d =>
-    typeof d === 'string'
-      ? d === field.value
-      : 'value' in d
-        ? d.value === field.value
-        : true,
-  );
+  const selected = props.data?.find(d =>
+    typeof d === 'string'
+      ? d === field.value
+      : 'value' in d && d.value === field.value,
+  );
packages/common-utils/src/metadata.ts (1)

451-451: Clamp limit to a safe range before embedding into SQL

Protects against pathological values and keeps ClickHouse memory/response size in check.

   async getKeyValues({
     chartConfig,
     keys,
-    limit = 100,
+    limit = 100,
     disableRowLimit = false,
   }: {
@@
-      async () => {
+      async () => {
+        const safeLimit = Math.max(1, Math.min(500, limit ?? 100));
         const sql = await renderChartConfig(
           {
             ...chartConfig,
             select: keys
-              .map((k, i) => `groupUniqArray(${limit})(${k}) AS param${i}`)
+              .map((k, i) => `groupUniqArray(${safeLimit})(${k}) AS param${i}`)
               .join(', '),
           },
           this,
         );

Also applies to: 461-468

packages/app/src/hooks/useMetadata.tsx (1)

193-197: Optional: extract and reuse the sliced keys for consistency and clarity

Keeps queryKey and request in sync and makes the 50-key limit more discoverable.

-            metadata.getKeyValues({
-              chartConfig,
-              keys: keys.slice(0, 50), // Limit to 50 keys for now, otherwise request fails (max header size)
-              limit,
-              disableRowLimit,
-            }),
+            metadata.getKeyValues({
+              chartConfig,
+              // Limit to 50 keys for now, otherwise request fails (max header size)
+              keys: keys.slice(0, 50),
+              limit,
+              disableRowLimit,
+            }),

Consider hoisting const slicedKeys = keys.slice(0, 50) above and using it in both queryKey and here.

packages/app/src/components/SourceSelect.tsx (1)

34-35: Avoid “undefined | …” labels and satisfy Prettier by building label robustly

If a connection name is not found, the current expression renders “undefined | …”. Build the label defensively.

-          label: connections?.find(c => c.id === d.connection)?.name + ' | ' + d.name,
+          label: [connections?.find(c => c.id === d.connection)?.name, d.name]
+            .filter(Boolean)
+            .join(' | '),

If you have many sources and connections, consider precomputing a Map for O(1) lookups:

const connectionNameById = useMemo(() => {
  const m = new Map<string, string>();
  connections?.forEach(c => m.set(c.id, c.name));
  return m;
}, [connections]);

// then
label: [connectionNameById.get(d.connection), d.name].filter(Boolean).join(' | ')
packages/app/src/TeamPage.tsx (4)

51-51: Sort imports to satisfy linter.

ESLint (simple-import-sort/imports) flagged import order. Run autofix to keep CI green.


335-336: Compute a single canAdmin flag and reuse instead of repeatedly calling hasAccess.

Minor readability and consistency improvement: derive const canAdmin = hasAccess(me?.email) once and reuse across the component.

Apply this diff at the declaration site:

   const deleteTeamInvitation = api.useDeleteTeamInvitation();
-  const { hasAccess } = useAuthEmails();
+  const { hasAccess } = useAuthEmails();
+  const canAdmin = hasAccess(me?.email);

Then use canAdmin at call sites below.

Also applies to: 299-306


554-571: Make the admin check consistent; drop redundant hasAdminAccess.

hasAdminAccess is hardcoded to true, so it’s redundant. Gate with the derived canAdmin instead for clarity.

-                      {!member.isCurrentUser && hasAdminAccess && hasAccess(me?.email) && (
+                      {!member.isCurrentUser && canAdmin && (

554-554: Fix Prettier issues flagged by CI.

  • Line 554: Prettier suggests breaking before hasAdminAccess.
  • Line 1459: Remove the extra spaces on the blank line.

Run Prettier to resolve both.

Also applies to: 1459-1461

packages/app/src/hooks/useAuthEmails.tsx (3)

9-24: Strongly type the parsed value for safer access and better DX.

Type authArray as Record<string, boolean> and handle the case where someone sets the env to a string array of emails.

-export const useAuthEmails = () => {
-  const authArray = useMemo(() => {
+export const useAuthEmails = () => {
+  const authArray = useMemo<Record<string, boolean>>(() => {
     
     if (!HDX_AUTH_EMAILS) {
       console.warn('HDX_AUTH_EMAILS environment variable is not set');
-      return {};
+      return {};
     }
     
     try {
-      const parsed = JSON.parse(HDX_AUTH_EMAILS);
-      return parsed;
+      const parsed = JSON.parse(HDX_AUTH_EMAILS);
+      if (Array.isArray(parsed)) {
+        return parsed.reduce((acc, email) => {
+          if (typeof email === 'string') acc[email] = true;
+          return acc;
+        }, {} as Record<string, boolean>);
+      }
+      return (parsed ?? {}) as Record<string, boolean>;
     } catch (error) {
       console.warn('Failed to parse HDX_AUTH_EMAILS:', error);
       return {};
     }
   }, []);

41-42: Prettier formatting nit for filter callback.

Trailing comma/newline suggested by Prettier. Run autofix.


1-2: Sort imports per linter.

ESLint requests sorted imports.

packages/app/src/components/DBSearchPageFilters.tsx (3)

51-58: Type getKeysForSourceType with SourceKind and simplify.

Use the enum in the signature and safe lookup with fallback.

-const getKeysForSourceType = (sourceType?: string) => {
-  if (sourceType && sourceType in serviceMapOverride) {
-    return serviceMapOverride[sourceType as keyof typeof serviceMapOverride];
-  }
-  // For other source types, return empty array to use default behavior
-  return [];
-};
+const getKeysForSourceType = (sourceType?: SourceKind): string[] => {
+  if (!sourceType) return [];
+  return serviceMapOverride[sourceType] ?? [];
+};

435-465: Remove verbose debug console logging.

The multiple console.error statements spam the console and may leak state. Keep only actionable error logs (e.g., in catch blocks) or guard with a debug flag.

-  // Log all component props and variables
-  console.error('🚀 DBSearchPageFiltersComponent - ALL PROPS AND VARIABLES:');
-  console.error('📋 Main Props:');
-  console.error('  - analysisMode:', analysisMode);
-  console.error('  - isLive:', isLive);
-  console.error('  - sourceId:', sourceId);
-  console.error('  - sourceType:', sourceType);
-  console.error('  - showDelta:', showDelta);
-  console.error('  - denoiseResults:', denoiseResults);
-  console.error('  - chartConfig:', chartConfig);
-  console.error('📊 FilterStateHook Props:');
-  console.error('  - filters (filterState):', filterState);
-  console.error('  - clearAllFilters function:', typeof clearAllFilters);
-  console.error('  - clearFilter function:', typeof clearFilter);
-  console.error('  - setFilterValue function:', typeof setFilterValue);
-  console.error('🔧 Hook Results:');
-
   const { toggleFilterPin, toggleFieldPin, isFilterPinned, isFieldPinned } =
     usePinnedFilters(sourceId ?? null);
-
-  console.error('  - toggleFilterPin function:', typeof toggleFilterPin);
-  console.error('  - toggleFieldPin function:', typeof toggleFieldPin);
-  console.error('  - isFilterPinned function:', typeof isFilterPinned);
-  console.error('  - isFieldPinned function:', typeof isFieldPinned);
-
   const { width, startResize } = useResizable(16, 'left');
-  console.error('  - width:', width);
-  console.error('  - startResize function:', typeof startResize);
@@
-  console.error('  - countData:', countData);
-  console.error('  - allFields data:', data);
-  console.error('  - allFields isLoading:', isLoading);
-  console.error('📊 State Variables:');
-
   const [showMoreFields, setShowMoreFields] = useState(false);
-  console.error('  - showMoreFields:', showMoreFields);
-  console.error('  - setShowMoreFields function:', typeof setShowMoreFields);
-  console.error('🚀 END OF COMPONENT PROPS LOGGING 🚀');
-  console.error('');

Also applies to: 472-481, 461-463


540-541: Consider replacing console.error in catch with a user-visible notification.

Optional: surface an alert when loading more facet values fails; console-only errors are easy to miss.

-      } catch (error) {
-        console.error('failed to fetch more keys', error);
+      } catch (error: any) {
+        notifications.show({
+          color: 'red',
+          message: 'Failed to fetch more filter values',
+        });
packages/app/src/DBSearchPage.tsx (4)

533-542: Avoid empty catch block (no-empty).

Add a small comment or a warn to satisfy the linter and future readers.

-    }())(),
+    }())(),
   );
 
   useEffect(() => {
     if (initialHadFiltersRef.current) {
       try {
         const url = new URL(window.location.href);
         url.searchParams.delete('filters');
         window.history.replaceState({}, '', url.toString());
-      } catch {}
+      } catch {
+        /* noop */
+      }
       setSearchedConfig({ filters: [] });
     }
   }, [setSearchedConfig]);

1264-1274: Gate the “Edit Source” gear icon using hasAccess instead of raw authArray lookup.

Safer and consistent with the rest of the code; also avoids indexing with possibly undefined keys.

-              {authArray[me?.email as keyof typeof authArray] && (
+              {hasAccess(me?.email) && (
                 <Menu.Target>

If you prefer, compute const canAdmin = hasAccess(me?.email) once and use it here and elsewhere.

Also applies to: 1275-1288


102-103: Fix Prettier complaint on multiline import.

The linter suggests breaking the braced import into multiple lines with trailing commas.


521-542: Run Prettier on the file to resolve multiple formatting warnings.

There are several Prettier hints (array/object trailing commas, spacing, and “prefer-const” at Line 1101). Running Prettier and minor fixes will get CI happy.

Also applies to: 676-681, 724-725, 794-804, 1101-1103, 1234-1242, 1264-1272

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
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.

📥 Commits

Reviewing files that changed from the base of the PR and between e2db365 and f9f7b64.

📒 Files selected for processing (9)
  • packages/app/src/DBSearchPage.tsx (19 hunks)
  • packages/app/src/TeamPage.tsx (7 hunks)
  • packages/app/src/components/DBSearchPageFilters.tsx (9 hunks)
  • packages/app/src/components/SelectControlled.tsx (2 hunks)
  • packages/app/src/components/SourceSelect.tsx (3 hunks)
  • packages/app/src/config.ts (1 hunks)
  • packages/app/src/hooks/useAuthEmails.tsx (1 hunks)
  • packages/app/src/hooks/useMetadata.tsx (1 hunks)
  • packages/common-utils/src/metadata.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
packages/app/src/components/SourceSelect.tsx (1)
packages/app/src/connection.ts (1)
  • useConnections (43-54)
packages/app/src/hooks/useAuthEmails.tsx (1)
packages/app/src/config.ts (1)
  • HDX_AUTH_EMAILS (14-16)
packages/app/src/TeamPage.tsx (1)
packages/app/src/hooks/useAuthEmails.tsx (1)
  • useAuthEmails (8-49)
packages/app/src/DBSearchPage.tsx (5)
packages/app/src/connection.ts (1)
  • useConnections (43-54)
packages/app/src/source.ts (1)
  • useSources (77-88)
packages/app/src/timeQuery.ts (2)
  • dateRangeToString (38-48)
  • useNewTimeQuery (432-530)
packages/app/src/hooks/useAuthEmails.tsx (1)
  • useAuthEmails (8-49)
packages/app/src/components/SourceSelect.tsx (1)
  • SourceSelectControlled (64-64)
packages/app/src/components/DBSearchPageFilters.tsx (3)
packages/app/src/searchFilters.tsx (2)
  • FilterStateHook (220-220)
  • usePinnedFilters (278-352)
packages/app/src/hooks/useExplainQuery.tsx (1)
  • useExplainQuery (8-36)
packages/app/src/hooks/useMetadata.tsx (1)
  • useAllFields (82-126)
🪛 ESLint
packages/app/src/components/SourceSelect.tsx

[error] 34-34: Replace ·connections?.find(c·=>·c.id·===·d.connection)?.name·+·'·|·'·+ with ⏎············connections?.find(c·=>·c.id·===·d.connection)?.name·+⏎············'·|·'·+⏎···········

(prettier/prettier)

packages/app/src/config.ts

[error] 14-16: Replace ⏎··'NEXT_PUBLIC_HDX_AUTH_EMAILS',⏎ with 'NEXT_PUBLIC_HDX_AUTH_EMAILS'

(prettier/prettier)

packages/app/src/hooks/useAuthEmails.tsx

[error] 1-2: Run autofix to sort these imports!

(simple-import-sort/imports)


[error] 10-11: Delete ⏎····

(prettier/prettier)


[error] 15-15: Delete ····

(prettier/prettier)


[error] 18-18: Unexpected console statement.

(no-console)


[error] 41-41: Replace email·=>·authArray[email·as·keyof·typeof·authArray] with ⏎······email·=>·authArray[email·as·keyof·typeof·authArray],⏎····

(prettier/prettier)

packages/app/src/TeamPage.tsx

[error] 1-51: Run autofix to sort these imports!

(simple-import-sort/imports)


[error] 554-554: Replace ·hasAdminAccess·&& with ⏎························hasAdminAccess·&&⏎·······················

(prettier/prettier)


[error] 1459-1459: Delete ··

(prettier/prettier)

packages/app/src/DBSearchPage.tsx

[error] 102-102: Replace ·parseTimeQuery,·useNewTimeQuery,·dateRangeToString· with ⏎··parseTimeQuery,⏎··useNewTimeQuery,⏎··dateRangeToString,⏎

(prettier/prettier)


[error] 539-539: Empty block statement.

(no-empty)


[error] 610-610: Delete ····

(prettier/prettier)


[error] 613-613: Replace s·=>·s.id·===·searchedConfig.source with ⏎········s·=>·s.id·===·searchedConfig.source,⏎······

(prettier/prettier)


[error] 618-618: Delete ····

(prettier/prettier)


[error] 621-621: Replace s·=>·s.id·===·lastSelectedSourceId with ⏎········s·=>·s.id·===·lastSelectedSourceId,⏎······

(prettier/prettier)


[error] 626-626: Delete ····

(prettier/prettier)


[error] 629-629: Replace searchedConfig.connection,·searchedConfig.source,·inputSourceObjs,·lastSelectedSourceId,·connections with ⏎····searchedConfig.connection,⏎····searchedConfig.source,⏎····inputSourceObjs,⏎····lastSelectedSourceId,⏎····connections,⏎··

(prettier/prettier)


[error] 676-676: Delete ··

(prettier/prettier)


[error] 678-680: Delete ⏎⏎

(prettier/prettier)


[error] 724-724: Replace ·searchedConfig?.connection·??·watch('connection')·?? with ⏎··········searchedConfig?.connection·??⏎··········watch('connection')·??⏎·········

(prettier/prettier)


[error] 794-794: Replace ·select,·where,·whereLanguage,·source,·filters,·orderBy,·connection with ⏎········select,⏎········where,⏎········whereLanguage,⏎········source,⏎········filters,⏎········orderBy,⏎········connection,⏎·····

(prettier/prettier)


[error] 858-858: Delete ······

(prettier/prettier)


[error] 1101-1101: 'newTimeRange' is never reassigned. Use 'const' instead.

(prefer-const)


[error] 1234-1234: Insert ⏎·······

(prettier/prettier)


[error] 1238-1238: Replace s·=>·s.id·===·(currentSourceId·as·any) with ⏎······s·=>·s.id·===·(currentSourceId·as·any),⏎····

(prettier/prettier)


[error] 1240-1240: Replace ·shouldDirty:·true,·shouldTouch:·true with ⏎········shouldDirty:·true,⏎········shouldTouch:·true,⏎·····

(prettier/prettier)


[error] 1242-1243: Delete ⏎··

(prettier/prettier)


[error] 1264-1264: Insert ·

(prettier/prettier)


[error] 1265-1265: Insert ········

(prettier/prettier)


[error] 1266-1266: Insert ··

(prettier/prettier)


[error] 1267-1267: Insert ··

(prettier/prettier)


[error] 1268-1268: Insert ··

(prettier/prettier)


[error] 1269-1269: Replace ·············· with ················

(prettier/prettier)


[error] 1270-1270: Insert ··

(prettier/prettier)


[error] 1271-1271: Insert ··

(prettier/prettier)


[error] 1272-1272: Insert ··

(prettier/prettier)

🔇 Additional comments (6)
packages/app/src/components/SelectControlled.tsx (1)

15-17: Nice: defers clearing until options are ready to prevent flicker

The hasOptions + conditional value binding is a solid way to avoid premature clears while options load. LGTM.

Also applies to: 41-41

packages/app/src/TeamPage.tsx (2)

1482-1488: LGTM: Sections correctly gated by hasAccess.

Rendering Sources, Connections, TeamQueryConfig, and API Keys only for authorized users looks correct.


1052-1063: LGTM: Button-level gating is appropriate here.

Team name is visible to all, but “Change” action is restricted via hasAccess, which keeps UX friendly without over-gating.

packages/app/src/components/DBSearchPageFilters.tsx (2)

205-210: Auto “load more” on mount may surprise users; ensure it’s desired.

The effect eagerly calls onLoadMore(name) once per group. If this is meant to be lazy, keep it; otherwise consider deferring to user action only.

Would you like me to switch this to only trigger on user click?


276-276: Good: included isFieldPinned in dependencies.

The memo recomputes when pin status changes; avoids stale ordering.

packages/app/src/DBSearchPage.tsx (1)

399-405: Good UX: default to a fixed 15-minute range and disable live tail initially.

The fixed window and live-tail toggle behaviors are coherent and prevent unexpected auto-refreshes.

Also applies to: 673-688

Comment on lines 14 to 16
export const HDX_AUTH_EMAILS = env(
'NEXT_PUBLIC_HDX_AUTH_EMAILS',
);
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Fix Prettier error and add a safe default to prevent JSON.parse crashes downstream

Prettier flags the multi-line arg. Also, if this env is unset, consumers that JSON.parse it will crash. Inline the arg and default to '[]'.

Apply this diff:

-export const HDX_AUTH_EMAILS = env(
-  'NEXT_PUBLIC_HDX_AUTH_EMAILS',
-);
+export const HDX_AUTH_EMAILS = env('NEXT_PUBLIC_HDX_AUTH_EMAILS') ?? '[]';

Follow-up: confirm the client hook parsing HDX_AUTH_EMAILS tolerates malformed JSON and treats empty/missing as an empty list to avoid runtime errors and UX regressions.


🏁 Script executed:

#!/bin/bash
# Inspect imports and env usage in config.ts
sed -n '1,40p' packages/app/src/config.ts

# Search for HDX_AUTH_EMAILS usage across the codebase
rg -n --color=never "HDX_AUTH_EMAILS" -C2

Length of output: 4569


Update HDX_AUTH_EMAILS default and formatting

• In packages/app/src/config.ts (lines 14–16), replace the multi-line env call with an inline invocation that falls back to '[]':

-export const HDX_AUTH_EMAILS = env(
-  'NEXT_PUBLIC_HDX_AUTH_EMAILS',
-);
+export const HDX_AUTH_EMAILS = env('NEXT_PUBLIC_HDX_AUTH_EMAILS') ?? '[]';

This satisfies Prettier, ensures JSON.parse never receives undefined, and guarantees an empty array by default.

• Verify in packages/app/src/hooks/useAuthEmails.tsx that the try/catch around JSON.parse will handle any malformed JSON without crashing and, with the new default, always produce [].

📝 Committable suggestion

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

Suggested change
export const HDX_AUTH_EMAILS = env(
'NEXT_PUBLIC_HDX_AUTH_EMAILS',
);
export const HDX_AUTH_EMAILS = env('NEXT_PUBLIC_HDX_AUTH_EMAILS') ?? '[]';
🧰 Tools
🪛 ESLint

[error] 14-16: Replace ⏎··'NEXT_PUBLIC_HDX_AUTH_EMAILS',⏎ with 'NEXT_PUBLIC_HDX_AUTH_EMAILS'

(prettier/prettier)

🤖 Prompt for AI Agents
In packages/app/src/config.ts around lines 14 to 16, replace the multi-line env
call with a single-line invocation that provides a default string '[]' (e.g.
env('NEXT_PUBLIC_HDX_AUTH_EMAILS', '[]')) so Prettier is satisfied and
JSON.parse never receives undefined; then verify
packages/app/src/hooks/useAuthEmails.tsx still wraps JSON.parse in a try/catch
to gracefully handle malformed JSON and ensure the hook returns an empty array
by default with the new config fallback.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/app/src/DBSearchPage.tsx (1)

1106-1114: Bug: Initial search sends an ISO string instead of a time range.

onSearch expects a time range string (e.g., "start - end") or use onTimeRangeSelect. Passing a single ISO date will not parse into from/to and can yield incorrect behavior.

Apply this diff to fix the initialization:

-      if (!searchParams.has('from') && !searchParams.has('to')) {
-        const newTimeRange = new Date(defaultTimeRange[0]);
-        onSearch(newTimeRange.toISOString());
-      }
+      if (!searchParams.has('from') && !searchParams.has('to')) {
+        onTimeRangeSelect(defaultTimeRange[0], defaultTimeRange[1], initialTimeDisplay);
+      }
♻️ Duplicate comments (3)
packages/app/src/DBSearchPage.tsx (1)

1581-1582: Passing SourceKind to DBSearchPageFilters is correct and aligns with the earlier review.

Prop now receives inputSourceObj?.kind as SourceKind. Good follow-through.

packages/app/src/TeamPage.tsx (2)

400-435: Same refactor applies here (reuse showKyError)


444-477: Same refactor applies here (reuse showKyError)

🧹 Nitpick comments (11)
packages/app/src/DBSearchPage.tsx (4)

600-602: Avoid fetching sources twice; reuse the earlier useSources() result.

You already have const { data: sources } = useSources(). Calling useSources() again will create an extra hook (React Query dedupes network calls, but this is unnecessary churn and cognitive load).

Replace the second call with the existing data:

-  const { data: inputSourceObjs } = useSources();
+  const inputSourceObjs = sources;

682-688: Prevent potential isLive flicker at startup.

This effect sets isLive to false for fixed ranges, but the subsequent init effect that sets live mode when from/to are null can briefly flip it to true. Low impact, but visible UI flicker is possible.

One simple mitigation: in the “auto set live” effect, only enable live mode when the displayedTimeInputValue equals 'Live Tail' (or when it’s not the fixed initial display). Alternatively, rely solely on the corrected initial onTimeRangeSelect (see prior comment), which will populate from/to immediately and avoid the toggle.


1221-1222: Use hasAccess() from useAuthEmails to encapsulate auth logic.

Directly indexing into authArray is brittle; use the provided helper.

-  const { authArray } = useAuthEmails();
+  const { hasAccess } = useAuthEmails();

1290-1304: Gate admin UI with hasAccess() instead of object indexing.

This avoids optional chaining edge cases and centralizes the auth logic.

-              {authArray[me?.email as keyof typeof authArray] && (
+              {hasAccess(me?.email) && (
                 <Menu.Target>
                   <ActionIcon
                     variant="subtle"
                     color="dark.2"
                     size="sm"
                     title="Edit Source"
                   >
                     <Text size="xs">
                       <i className="bi bi-gear" />
                     </Text>
                   </ActionIcon>
                 </Menu.Target>
               )}
packages/app/src/TeamPage.tsx (7)

298-298: Minor: avoid redundant me queries across components

api.useMe() is called in TeamPage, TeamMembersSection, TeamNameSection, and ApiKeysSection. React Query will dedupe/cache, but you can avoid extra hook invocations by passing me down as a prop or via a light context if this grows. Low impact; optional polish.


349-376: DRY up HTTPError notification handling

The onError branch duplicates a ky HTTPError parsing pattern across actions. Extract a small helper to centralize JSON parsing + fallback messaging to reduce duplication and keep behavior consistent.

Apply this diff to this onError:

-          onError: (e: any) => {
-            if (e instanceof HTTPError) {
-              e.response
-                .json()
-                .then((res: any) => {
-                  notifications.show({
-                    color: 'red',
-                    message: res.message,
-                    autoClose: 5000,
-                  });
-                })
-                .catch(() => {
-                  notifications.show({
-                    color: 'red',
-                    message:
-                      'Something went wrong. Please contact HyperDX team.',
-                    autoClose: 5000,
-                  });
-                });
-            } else {
-              notifications.show({
-                color: 'red',
-                message: 'Something went wrong. Please contact HyperDX team.',
-                autoClose: 5000,
-              });
-            }
-          },
+          onError: e => showKyError(e, 'Something went wrong. Please contact HyperDX team.'),

Add this helper near the top of the file (outside these ranges):

function showKyError(e: unknown, fallback = 'Something went wrong. Please contact HyperDX team.') {
  if (e instanceof HTTPError) {
    e.response
      .json()
      .then((res: any) => {
        notifications.show({
          color: 'red',
          message: res?.message ?? fallback,
          autoClose: 5000,
        });
      })
      .catch(() => {
        notifications.show({
          color: 'red',
          message: fallback,
          autoClose: 5000,
        });
      });
  } else {
    const message = (e as any)?.message ?? fallback;
    notifications.show({
      color: 'red',
      message,
      autoClose: 5000,
    });
  }
}

968-972: TeamNameSection: ensure form default value updates when team loads

  • defaultValues uses team.name but the hook may render before team is available; RHF won’t update defaultValues after mount, so the input may stay empty until a manual reset.
  • Also, api.useMe() is invoked again here; consider passing me from parent if convenient (optional).

Suggested fix to keep the form in sync:

-  const form = useForm<{ name: string }>({
-    defaultValues: {
-      name: team.name,
-    },
-  });
+  const form = useForm<{ name: string }>({
+    defaultValues: {
+      name: team?.name ?? '',
+    },
+  });
+
+  useEffect(() => {
+    if (team?.name != null) {
+      form.reset({ name: team.name });
+    }
+  }, [team?.name, form]);

1132-1146: LGTM: ClickHouse settings mutate handler

Straightforward success/failure UX. Consider adopting the same showKyError helper for consistency across mutations, but not required.


1349-1350: Minor: avoid another useMe() here

You can pass me down from TeamPage to ApiKeysSection to save another hook call. Low priority due to caching.


1461-1463: Compute canManage once and reuse in JSX

You call hasAccess(me?.email) multiple times. Compute a single boolean to avoid repetition and make conditions easier to scan.

-  // Use auth emails hook
-  const { hasAccess } = useAuthEmails();
+  // Use auth emails hook
+  const { hasAccess } = useAuthEmails();
+  const canManage = hasAccess(me?.email);

1483-1489: Use the computed canManage for gated sections

Small readability improvement; behavior unchanged.

-              {hasAccess(me?.email) && <SourcesSection />}
-              {hasAccess(me?.email) && <ConnectionsSection />}
+              {canManage && <SourcesSection />}
+              {canManage && <ConnectionsSection />}
               <IntegrationsSection />
               <TeamNameSection />
-              {hasAccess(me?.email) && <TeamQueryConfigSection />}
-              {hasAccess(me?.email) && <ApiKeysSection />}
+              {canManage && <TeamQueryConfigSection />}
+              {canManage && <ApiKeysSection />}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
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.

📥 Commits

Reviewing files that changed from the base of the PR and between f9f7b64 and e4ac2e9.

📒 Files selected for processing (5)
  • packages/app/src/DBSearchPage.tsx (23 hunks)
  • packages/app/src/TeamPage.tsx (15 hunks)
  • packages/app/src/components/SourceSelect.tsx (3 hunks)
  • packages/app/src/config.ts (1 hunks)
  • packages/app/src/hooks/useAuthEmails.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/app/src/config.ts
  • packages/app/src/components/SourceSelect.tsx
  • packages/app/src/hooks/useAuthEmails.tsx
🧰 Additional context used
🧬 Code Graph Analysis (2)
packages/app/src/DBSearchPage.tsx (4)
packages/app/src/connection.ts (1)
  • useConnections (43-54)
packages/app/src/source.ts (1)
  • useSources (77-88)
packages/app/src/timeQuery.ts (2)
  • dateRangeToString (38-48)
  • useNewTimeQuery (432-530)
packages/app/src/hooks/useAuthEmails.tsx (1)
  • useAuthEmails (9-48)
packages/app/src/TeamPage.tsx (1)
packages/app/src/hooks/useAuthEmails.tsx (1)
  • useAuthEmails (9-48)
🔇 Additional comments (13)
packages/app/src/DBSearchPage.tsx (8)

518-542: Stripping 'filters' from the URL on initial load: confirm UX is intentional.

This proactively removes filters from the URL and resets in-memory filters. It makes shared links non-idempotent on first visit and may surprise users who expect the exact filters to be applied when opening/sharing a link.

  • If the intent is to avoid initial narrowing for facets, consider gating this behind a feature flag, applying it only when landing without a saved search, or limiting it to specific routes.
  • Alternatively, only ignore filters for the facets’ initial fetch without mutating the URL (so that a refresh preserves the URL as shared).

395-402: Fixed default time range is clear and safe with SSR disabled.

The intent and implementation look good. Module-level initialization is fine given ssr: false.


507-508: Persisting 'connection' in URL state is a good addition.

Including connection in queryStateMap ensures deep-linking and back/forward navigation consistency.


604-637: Initial connection derivation logic is sensible.

Preferring URL > current source > last-selected source > first available connection covers the common cases.


639-658: Form schema/type alignment for 'connection' looks correct.

Schema, type, and form initialization are consistent. The follow-up effect that sets connection when data arrives closes the loop.


917-926: Ignoring initial filters for the first render is a pragmatic way to avoid narrowed facets.

The searchedConfigForInit approach cleanly decouples UI init from persisted state.


1240-1260: Auto-selecting a source that matches the selected connection is helpful.

This improves UX when connections change. Since your source-change watcher already clears filters and triggers refresh, the state remains consistent.


1280-1289: Min-width wrapper around SourceSelectControlled improves layout stability.

Small UI enhancement that prevents jitter. Looks good.

packages/app/src/TeamPage.tsx (5)

553-572: LGTM: removal action gated behind allowlist

Good call gating destructive actions behind hasAccess. Pair this with backend authorization.


985-991: Simplified onError handler is fine

Moving to onError: () => ... keeps the UX consistent and avoids leaking raw error details. No issues.


1053-1064: LGTM: “Change team name” CTA gated by allowlist

Consistent with the rest of the page’s admin-only gating.


1444-1452: LGTM: Personal API key visibility tied to me presence

Reasonable default; shows only for the current user context.


43-43: Confirm public env var for HDX_AUTH_EMAILS is wired correctly

  • In packages/app/src/config.ts, you export
    export const HDX_AUTH_EMAILS = env('NEXT_PUBLIC_HDX_AUTH_EMAILS')
  • Next.js will inline any NEXT_PUBLIC_* vars into both server and client bundles at build time—but you must still ensure that:
    • Your local .env.local and production/CI environments define NEXT_PUBLIC_HDX_AUTH_EMAILS with the expected JSON string.
    • If you’ve locked down env vars in next.config.js, that key is whitelisted.
  • Without it, useAuthEmails() returns {} and you won’t see an exception—but mismatched or missing values between builds can cause unexpected behavior.

(Optional) consider adding a runtime warning or fallback so you’re alerted if the JSON is missing or invalid.

Comment on lines +334 to 336
const { hasAccess } = useAuthEmails();

const sendTeamInviteAction = (email: string) => {
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Apply hasAccess consistently to invites and pending-invite deletions; consolidate the check

You already gate the Remove action; the “Invite Team Member” and “Delete (pending invite)” actions are still available to everyone. Align these to avoid bypassing your allowlist at the UI layer. Also, define a single canManageMembers variable to reduce repetition and improve readability.

Within the changed lines, add a consolidated flag:

-  const { hasAccess } = useAuthEmails();
+  const { hasAccess } = useAuthEmails();
+  const canManageMembers = hasAdminAccess && hasAccess(me?.email);

Within the changed block for the member Remove button:

-                      {!member.isCurrentUser &&
-                        hasAdminAccess &&
-                        hasAccess(me?.email) && (
+                      {!member.isCurrentUser && canManageMembers && (
                         <Group justify="flex-end" gap="8">
                           <Button
                             size="compact-sm"
                             variant="light"
                             color="red"
                             onClick={() =>
                               setDeleteTeamMemberConfirmationModalData({
                                 mode: 'team',
                                 id: member._id,
                                 email: member.email,
                               })
                             }
                           >
                             Remove
                           </Button>
                         </Group>
                       )}

Additionally apply gating to unchanged spots outside this hunk:

  • Gate “Invite Team Member”:
-            <Button
-              variant="light"
-              leftSection={<i className="bi bi-person-plus-fill" />}
-              onClick={() => setTeamInviteModalShow(true)}
-            >
-              Invite Team Member
-            </Button>
+            {canManageMembers && (
+              <Button
+                variant="light"
+                leftSection={<i className="bi bi-person-plus-fill" />}
+                onClick={() => setTeamInviteModalShow(true)}
+              >
+                Invite Team Member
+              </Button>
+            )}
  • Gate “Delete (pending invite)”:
-                      {hasAdminAccess && (
+                      {canManageMembers && (
                         <Group justify="flex-end" gap="8">
                           <Button
                             size="compact-sm"
                             variant="light"
                             color="red"
                             onClick={() =>
                               setDeleteTeamMemberConfirmationModalData({
                                 mode: 'teamInvite',
                                 id: invitation._id,
                                 email: invitation.email,
                               })
                             }
                           >
                             Delete
                           </Button>
                         </Group>
                       )}

Note: UI gating is not a security boundary. Ensure the backend endpoints for invite/delete are also enforcing this allowlist (email-based gating).


🏁 Script executed:

#!/bin/bash
set -e

echo "1) useAuthEmails import and usage:"
grep -R "useAuthEmails" -n packages/app/src/TeamPage.tsx || true

echo -e "\n2) hasAdminAccess usage:"
rg -n --type=tsx "hasAdminAccess" packages/app/src/TeamPage.tsx -C2 || true

echo -e "\n3) hasAccess usage:"
rg -n --type=tsx "hasAccess" packages/app/src/TeamPage.tsx -C2 || true

echo -e "\n4) Invite Team Member button location:"
rg -n "Invite Team Member" packages/app/src/TeamPage.tsx -C3 || true

echo -e "\n5) Pending invite delete button (mode 'teamInvite'):"
rg -n "teamInvite" packages/app/src/TeamPage.tsx -C3 || true

Length of output: 2959


🏁 Script executed:

#!/bin/bash
set -e

echo "1) hasAdminAccess usages around line 330:"
grep -R "hasAdminAccess" -n packages/app/src/TeamPage.tsx -C3 | sed -n '1,20p'

echo -e "\n2) Pending-invite delete button context:"
grep -R "invitation" -n packages/app/src/TeamPage.tsx -C3 | sed -n '580,650p'

Length of output: 1081


Consolidate UI checks for team member management and gate invite/delete actions with canManageMembers

In packages/app/src/TeamPage.tsx, inside TeamMembersSection:

  • After retrieving hasAccess from useAuthEmails, define a single flag:
     const { hasAccess } = useAuthEmails();
    +const canManageMembers = hasAdminAccess && hasAccess(me?.email);
  • Replace the “Remove” button conditional (around line 553):
    -{!member.isCurrentUser && hasAdminAccess && hasAccess(me?.email) && (
    +{!member.isCurrentUser && canManageMembers && (
  • Wrap the “Invite Team Member” button (around line 499):
    -<Button
    -  variant="light"
    -  leftSection={<i className="bi bi-person-plus-fill" />}
    -  onClick={() => setTeamInviteModalShow(true)}
    ->
    +{canManageMembers && (
    +  <Button
    +    variant="light"
    +    leftSection={<i className="bi bi-person-plus-fill" />}
    +    onClick={() => setTeamInviteModalShow(true)}
    +  >
    +    Invite Team Member
    +  </Button>
    +)}
  • Update the pending-invite “Delete” button conditional (around line 601):
    -{hasAdminAccess && (
    +{canManageMembers && (

Note: UI gating is not a security boundary—verify that your backend invite/delete endpoints also enforce this email-based allowlist.

📝 Committable suggestion

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

Suggested change
const { hasAccess } = useAuthEmails();
const sendTeamInviteAction = (email: string) => {
const { hasAccess } = useAuthEmails();
const canManageMembers = hasAdminAccess && hasAccess(me?.email);
const sendTeamInviteAction = (email: string) => {
🤖 Prompt for AI Agents
In packages/app/src/TeamPage.tsx around lines 334-336, define a single boolean
flag const canManageMembers = hasAccess('team:manage-members') (or equivalent
access key) immediately after extracting hasAccess; then update the UI checks to
use this flag: replace the conditional that shows the “Remove” button (around
line 553) to check canManageMembers, wrap the “Invite Team Member” button
(around line 499) with canManageMembers, and change the pending-invite “Delete”
button conditional (around line 601) to use canManageMembers as well so all
invite/delete/manage-member UI is gated by the same flag.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/app/src/DBSearchPage.tsx (2)

1106-1114: Bug: Initial onSearch sends a single ISO, not a range (parser likely expects a range).

This calls onSearch with only the start ISO timestamp. useNewTimeQuery.onSearch expects a parsable time query (e.g., "start - end" or "Live Tail"). Sending a single ISO will likely fail parseTimeQuery and keep from/to unset, which can flip isLive or prevent initial data load.

Apply this diff to set the initial range correctly:

-      if (!searchParams.has('from') && !searchParams.has('to')) {
-        const newTimeRange = new Date(defaultTimeRange[0]);
-        onSearch(newTimeRange.toISOString());
-      }
+      if (!searchParams.has('from') && !searchParams.has('to')) {
+        onTimeRangeSelect(
+          defaultTimeRange[0],
+          defaultTimeRange[1],
+          initialTimeDisplay,
+        );
+      }

1068-1086: Include the connection parameter and guard against undefined filters in generateSearchUrl

The current generateSearchUrl omits connection and always serializes filters, which can produce filters=undefined or drop the user’s connection selection when sharing URLs. This breaks reproducibility and may run queries against the wrong connection.

Apply this refactor around lines ~1068–1086 in packages/app/src/DBSearchPage.tsx:

-    }) => {
-      const qParams = new URLSearchParams({
-        where: where || searchedConfig.where || '',
-        whereLanguage: whereLanguage || 'sql',
-        from: searchedTimeRange[0].getTime().toString(),
-        to: searchedTimeRange[1].getTime().toString(),
-        select: searchedConfig.select || '',
-        source: searchedSource?.id || '',
-        filters: JSON.stringify(searchedConfig.filters),
-      });
-      return `/search?${qParams.toString()}`;
-    },
+    }) => {
+      const qParams = new URLSearchParams();
+      qParams.set('where', where || searchedConfig.where || '');
+      qParams.set('whereLanguage', whereLanguage || 'sql');
+      qParams.set('from', searchedTimeRange[0].getTime().toString());
+      qParams.set('to', searchedTimeRange[1].getTime().toString());
+      qParams.set('select', searchedConfig.select || '');
+      qParams.set('source', searchedSource?.id || '');
+      // Only include filters when present to avoid filters=undefined/null
+      if (searchedConfig.filters?.length) {
+        qParams.set('filters', JSON.stringify(searchedConfig.filters));
+      }
+      // Preserve the selected connection in the URL
+      if (searchedConfig.connection) {
+        qParams.set('connection', searchedConfig.connection);
+      }
+      return `/search?${qParams.toString()}`;
+    },
  • Adds the missing connection query parameter.
  • Guards filters to prevent filters=undefined from appearing.
🧹 Nitpick comments (10)
packages/app/src/TeamPage.tsx (7)

493-501: Also gate “Invite Team Member” and “Delete pending invite” behind hasAccess

Right now, Invite and Delete-invite actions are only guarded by hasAdminAccess. For consistency with the new email-allowlist gate (hasAccess), these should be gated as well to avoid exposing sensitive admin actions to users not in HDX_AUTH_EMAILS.

Apply these diffs:

--- a/packages/app/src/TeamPage.tsx
+++ b/packages/app/src/TeamPage.tsx
@@
-            <Button
-              variant="light"
-              leftSection={<i className="bi bi-person-plus-fill" />}
-              onClick={() => setTeamInviteModalShow(true)}
-            >
-              Invite Team Member
-            </Button>
+            {hasAdminAccess && hasAccess(me?.email) && (
+              <Button
+                variant="light"
+                leftSection={<i className="bi bi-person-plus-fill" />}
+                onClick={() => setTeamInviteModalShow(true)}
+              >
+                Invite Team Member
+              </Button>
+            )}
--- a/packages/app/src/TeamPage.tsx
+++ b/packages/app/src/TeamPage.tsx
@@
-                      {hasAdminAccess && (
+                      {hasAdminAccess && hasAccess(me?.email) && (
                         <Group justify="flex-end" gap="8">
                           <Button
                             size="compact-sm"
                             variant="light"
                             color="red"

Also applies to: 596-614


1461-1463: Reduce repetition: compute canManage once and reuse

You call hasAccess(me?.email) multiple times. Compute once for readability and to avoid redundant evaluations.

@@
-  // Use auth emails hook
-  const { hasAccess } = useAuthEmails();
+  // Use auth emails hook
+  const { hasAccess } = useAuthEmails();
+  const canManage = hasAccess(me?.email);
@@
-              {hasAccess(me?.email) && <SourcesSection />}
-              {hasAccess(me?.email) && <ConnectionsSection />}
+              {canManage && <SourcesSection />}
+              {canManage && <ConnectionsSection />}
@@
-              {hasAccess(me?.email) && <TeamQueryConfigSection />}
-              {hasAccess(me?.email) && <ApiKeysSection />}
+              {canManage && <TeamQueryConfigSection />}
+              {canManage && <ApiKeysSection />}

Also applies to: 1483-1484, 1487-1488


1390-1396: Use distinct data-test-id values to avoid selector collisions

Both team API key and personal API key use dataTestId="api-key". This can make E2E/DOM tests flaky/ambiguous.

@@
-            <APIKeyCopyButton value={team.apiKey} dataTestId="api-key" />
+            <APIKeyCopyButton value={team.apiKey} dataTestId="team-api-key" />
@@
-            <APIKeyCopyButton value={me.accessKey} dataTestId="api-key" />
+            <APIKeyCopyButton value={me.accessKey} dataTestId="personal-api-key" />

Also applies to: 1447-1451


1443-1452: Guard on me.accessKey existence before rendering Personal API key

If me exists but accessKey is missing/undefined, the prop type contract for APIKeyCopyButton (value: string) is violated.

-      {me != null && (
+      {Boolean(me?.accessKey) && (
         <Card>
           <Card.Section p="md">
             <Text c="gray.3" mb="md">
               Personal API Access Key
             </Text>
-            <APIKeyCopyButton value={me.accessKey} dataTestId="api-key" />
+            <APIKeyCopyButton value={me!.accessKey as string} dataTestId="personal-api-key" />
           </Card.Section>
         </Card>
       )}

Note: Alternatively, tighten the me type upstream so accessKey is always present for authenticated users.


341-376: Deduplicate HTTPError notification handling into a small helper

The onError branches repeat the same HTTPError parsing and fallback logic. Centralize to reduce duplication and keep behavior consistent.

Add a small helper (near imports or in a shared utils file):

function showApiError(e: unknown, fallback: string) {
  if (e instanceof HTTPError) {
    e.response
      .json()
      .then((res: any) => {
        notifications.show({
          color: 'red',
          message: res?.message ?? fallback,
          autoClose: 5000,
        });
      })
      .catch(() => {
        notifications.show({ color: 'red', message: fallback, autoClose: 5000 });
      });
  } else {
    notifications.show({ color: 'red', message: (e as any)?.message ?? fallback, autoClose: 5000 });
  }
}

Then replace in onError callbacks, e.g.:

-          onError: (e: any) => {
-            if (e instanceof HTTPError) {
-              e.response
-                .json()
-                .then((res: any) => {
-                  notifications.show({
-                    color: 'red',
-                    message: res.message,
-                    autoClose: 5000,
-                  });
-                })
-                .catch(() => {
-                  notifications.show({
-                    color: 'red',
-                    message:
-                      'Something went wrong. Please contact HyperDX team.',
-                    autoClose: 5000,
-                  });
-                });
-            } else {
-              notifications.show({
-                color: 'red',
-                message: 'Something went wrong. Please contact HyperDX team.',
-                autoClose: 5000,
-              });
-            }
-          },
+          onError: (e: unknown) =>
+            showApiError(e, 'Something went wrong. Please contact HyperDX team.'),

Same simplification can be applied to deleteTeamInviteAction, deleteTeamMemberAction, rotateTeamApiKeyAction, and similar onError sites.

Also applies to: 400-436, 444-479, 985-999, 1132-1146, 1365-1371


968-972: Ensure form defaults update if team changes (edge case)

If team.name updates while this component is mounted (e.g., switching teams via cache warm), defaultValues won’t reinitialize. Consider resetting the form when team.name changes.

You can add:

useEffect(() => {
  form.reset({ name: team?.name ?? '' });
}, [team?.name]); 

298-305: Avoid redundant useMe/useTeam calls across nested components

You call api.useMe()/api.useTeam() in multiple sibling/child components. React Query will cache responses, but you still incur extra hook invocations and render churn. Consider lifting me/team to TeamPage and passing down via context/props where practical.

Also applies to: 968-972, 1348-1352, 1458-1463

packages/app/src/DBSearchPage.tsx (3)

395-402: Avoid module-evaluated default time range (stale on hot reload / long sessions).

defaultTimeRange is computed once at module load. In long-lived sessions or hot reload, this baseline drifts from user expectations of "last 15 minutes" at page open.

Consider computing it per-mount:

-const defaultTimeRange = createFixedTimeRange();

Then inside the DBSearchPage component (near other initializations):

// inside DBSearchPage
const defaultTimeRange = useMemo(() => createFixedTimeRange(), []);

This keeps your "fixed 15m" invariant per mount without drifting across reloads.


1220-1222: Use hasAccess() from useAuthEmails for clarity and safety.

Directly indexing authArray with me?.email is brittle; hasAccess already encapsulates parsing and guards.

Apply this diff:

-  const { authArray } = useAuthEmails();
+  const { hasAccess } = useAuthEmails();
-              {authArray[me?.email as keyof typeof authArray] && (
+              {hasAccess(me?.email) && (
                 <Menu.Target>
                   <ActionIcon
                     variant="subtle"
                     color="dark.2"
                     size="sm"
                     title="Edit Source"
                   >
                     <Text size="xs">
                       <i className="bi bi-gear" />
                     </Text>
                   </ActionIcon>
                 </Menu.Target>
               )}

Also applies to: 1291-1303


462-497: Unify return shape of useSearchedConfigToChartConfig.

When sourceObj exists, the hook returns { data } without isLoading; else it returns { data: null, isLoading }. This leads to isLoading being boolean | undefined downstream. Prefer consistent shape.

Apply this diff:

-  return useMemo(() => {
+  return useMemo(() => {
     if (sourceObj != null) {
       return {
-        data: {
+        data: {
           select: select || (sourceObj.defaultTableSelectExpression ?? ''),
           from: sourceObj.from,
           ...(sourceObj.tableFilterExpression != null
             ? {
                 filters: [
                   {
                     type: 'sql' as const,
                     condition: sourceObj.tableFilterExpression,
                   },
                   ...(filters ?? []),
                 ],
               }
             : {}),
           ...(filters != null ? { filters } : {}),
           where: where ?? '',
           whereLanguage: whereLanguage ?? 'sql',
           timestampValueExpression: sourceObj.timestampValueExpression,
           implicitColumnExpression: sourceObj.implicitColumnExpression,
           connection: sourceObj.connection,
           displayType: DisplayType.Search,
           orderBy:
             orderBy ||
             `${getFirstTimestampValueExpression(
               sourceObj.timestampValueExpression,
             )} DESC`,
-        },
+        },
+        isLoading,
       };
     }
 
-    return { data: null, isLoading };
+    return { data: null, isLoading };
   }, [sourceObj, isLoading, select, filters, where, whereLanguage, orderBy]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
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.

📥 Commits

Reviewing files that changed from the base of the PR and between f9f7b64 and e4ac2e9.

📒 Files selected for processing (5)
  • packages/app/src/DBSearchPage.tsx (23 hunks)
  • packages/app/src/TeamPage.tsx (15 hunks)
  • packages/app/src/components/SourceSelect.tsx (3 hunks)
  • packages/app/src/config.ts (1 hunks)
  • packages/app/src/hooks/useAuthEmails.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/app/src/components/SourceSelect.tsx
  • packages/app/src/config.ts
  • packages/app/src/hooks/useAuthEmails.tsx
🧰 Additional context used
🧬 Code Graph Analysis (2)
packages/app/src/TeamPage.tsx (1)
packages/app/src/hooks/useAuthEmails.tsx (1)
  • useAuthEmails (9-48)
packages/app/src/DBSearchPage.tsx (5)
packages/app/src/connection.ts (1)
  • useConnections (43-54)
packages/app/src/source.ts (1)
  • useSources (77-88)
packages/app/src/timeQuery.ts (2)
  • dateRangeToString (38-48)
  • useNewTimeQuery (432-530)
packages/app/src/hooks/useAuthEmails.tsx (1)
  • useAuthEmails (9-48)
packages/app/src/components/SourceSelect.tsx (1)
  • SourceSelectControlled (67-67)
🔇 Additional comments (6)
packages/app/src/TeamPage.tsx (4)

43-43: Import of useAuthEmails is correct and scoped well

The hook location and relative path look good. No concerns here.


553-572: Good: remove-member action is now gated by authorized email

Combining not-current-user, admin access, and hasAccess(me?.email) is sensible and consistent with the auth hook intent.


1053-1064: Team name “Change” control gated by hasAccess: looks good

The admin + email-allowlist check aligns with the intended access restrictions.


1461-1463: Confirm deny-by-default behavior for hasAccess is intentional

We’ve verified that HDX_AUTH_EMAILS is loaded from NEXT_PUBLIC_HDX_AUTH_EMAILS via env() in packages/app/src/config.ts, and no .env file defines it by default. As a result, useAuthEmails returns hasAccess = false whenever the variable is missing or invalid—hiding all gated UI.

• If this gating is intended as a feature-flag or temporary allowlist, no changes are needed.
• Otherwise, consider providing a permissive default in dev/staging, for example:
– Define NEXT_PUBLIC_HDX_AUTH_EMAILS in your .env.development or .env.local.
– Or adjust useAuthEmails to default to true outside production, e.g.:
const hasAccess = IS_PROD ? emails.length > 0 : true;

packages/app/src/DBSearchPage.tsx (2)

518-542: Stripping filters from URL on first load: confirm desired UX and history behavior.

This mutates the URL and resets in-memory filters on mount if filters was present. If users share URLs including filters, the page will discard them on first open, which may be surprising. Also note history.replaceState removes the param without leaving a back entry.

If intentional (as per PR summary to avoid narrowing facets), consider:

  • Showing a one-time toast hinting that filters were cleared.
  • Gating behind a feature flag or only applying on landing routes (not internal navigations).

No action requested if this is by design—flagging for confirmation.


1581-1582: Propagating SourceKind to filters: LGTM.

Passing sourceType={inputSourceObj?.kind} aligns with the prior recommendation to type this prop as SourceKind in DBSearchPageFilters. Ensure the filters component prop and helper typings are updated accordingly.

Comment on lines 1241 to 1260
useEffect(() => {
if (!selectedConnectionId || !inputSourceObjs) return;
const allowedKinds = [SourceKind.Log, SourceKind.Trace];
const firstMatching = inputSourceObjs.find(
s =>
s.connection === selectedConnectionId && allowedKinds.includes(s.kind),
);
if (!firstMatching) return;
const currentSourceId = watch('source');
const currentSource = inputSourceObjs.find(
s => s.id === (currentSourceId as any),
);
if (!currentSource || currentSource.connection !== selectedConnectionId) {
setValue('source', firstMatching.id, {
shouldDirty: true,
shouldTouch: true,
});
}
}, [selectedConnectionId, inputSourceObjs, setValue, watch]);

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Programmatic source change doesn't apply source defaults (select/orderBy) or clear filters.

The watch handler at Lines 842-869 only fires its reset logic on type === 'change', which may not trigger when you setValue('source', ...) programmatically in the connection effect (Lines 1241-1260). Result: source updates via connection may leave select/orderBy/filters stale.

Two options:

  • Replicate the same reset logic inside the connection effect after setValue('source', ...).
  • Or extract a helper (applySourceDefaults) and call it from both places.

Minimal patch replicated in the connection effect:

   if (!currentSource || currentSource.connection !== selectedConnectionId) {
     setValue('source', firstMatching.id, {
       shouldDirty: true,
       shouldTouch: true,
     });
+    const newInputSourceObj = inputSourceObjs.find(s => s.id === firstMatching.id);
+    if (newInputSourceObj) {
+      setLastSelectedSourceId(newInputSourceObj.id);
+      setValue(
+        'select',
+        newInputSourceObj.defaultTableSelectExpression ?? '',
+      );
+      setValue(
+        'orderBy',
+        `${getFirstTimestampValueExpression(
+          newInputSourceObj.timestampValueExpression ?? '',
+        )} DESC`,
+      );
+      searchFilters.clearAllFilters();
+      setTimeout(() => debouncedSubmit(), 0);
+    }
   }

Remember to include missing deps in the effect: setLastSelectedSourceId, searchFilters, debouncedSubmit.

Also applies to: 842-869


🏁 Script executed:

#!/bin/bash
# Display lines around the watch handler (Lines ~842-869)
sed -n '830,900p' packages/app/src/DBSearchPage.tsx

Length of output: 2168


🏁 Script executed:

#!/bin/bash
# Show the top imports to see how watch is obtained
sed -n '1,200p' packages/app/src/DBSearchPage.tsx

Length of output: 5564


🏁 Script executed:

#!/bin/bash
# Search for definitions of inputSourceObj(s)
rg -n "inputSourceObj" -n "inputSourceObjs" -C3 packages/app/src/DBSearchPage.tsx

Length of output: 9372


🌐 Web query:

In react-hook-form, does calling setValue trigger the watch subscription callback, and if so, what is the 'type' parameter passed (e.g., 'change')?

💡 Result:

Short answer: Yes — programmatic setValue updates will (in most cases) invoke the callback-style watch, but the event "type" is NOT "change" — it comes through as undefined.

Details:

  • The callback form of watch gets called with (values, { name, type }). The docs show that signature. (react-hook-form.com)
  • In practice user input produces type === 'change', while calling setValue programmatically will trigger the watcher but the type field is undefined (not 'change'). This is reported in the project issues/threads. (github.com)

Notes / gotchas:

  • useWatch/watch reactivity can be order-sensitive: if you set a value before a useWatch subscription is set up, that update may be missed. If you need to distinguish programmatic vs user changes, either check for type === 'change' (user) vs undefined (programmatic), or explicitly call a handler/emit your own event after calling setValue. (react-hook-form.com)

If you want, I can show a minimal example demonstrating watch receiving type 'change' for user input and undefined for setValue.

Citations:


Apply source defaults on programmatic source changes

Programmatic calls to setValue('source', …) in the connection effect (packages/app/src/DBSearchPage.tsx lines 1241–1260) do invoke your watch subscription, but the { type } parameter is undefined—not 'change'. As a result, the reset logic in your watch handler (lines 842–869) never runs for programmatic source updates, leaving select, orderBy, and filters stale.

Two ways to address this:

• Extract a helper (e.g. applySourceDefaults) that:

  • calls setLastSelectedSourceId
  • sets select & orderBy defaults
  • clears filters via searchFilters.clearAllFilters()
  • triggers debouncedSubmit()
    and invoke it both:
    – in your watch handler under if (name==='source' && type==='change')
    – in the connection effect immediately after setValue('source', …)

• Or inline the same reset logic in the connection effect. For example:

// packages/app/src/DBSearchPage.tsx:1249–1257
if (!currentSource || currentSource.connection !== selectedConnectionId) {
  setValue('source', firstMatching.id, {
    shouldDirty: true,
    shouldTouch: true,
  });
+ // Apply source defaults for the new source
+ const newSource = inputSourceObjs.find(s => s.id === firstMatching.id);
+ if (newSource) {
+   setLastSelectedSourceId(newSource.id);
+   setValue('select', newSource.defaultTableSelectExpression ?? '');
+   setValue(
+     'orderBy',
+     `${getFirstTimestampValueExpression(newSource.timestampValueExpression ?? '')} DESC`
+   );
+   searchFilters.clearAllFilters();
+   setTimeout(() => debouncedSubmit(), 0);
+ }
}

Don’t forget to add the missing dependencies to that effect:
setLastSelectedSourceId, searchFilters, and debouncedSubmit.

📝 Committable suggestion

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

Suggested change
useEffect(() => {
if (!selectedConnectionId || !inputSourceObjs) return;
const allowedKinds = [SourceKind.Log, SourceKind.Trace];
const firstMatching = inputSourceObjs.find(
s =>
s.connection === selectedConnectionId && allowedKinds.includes(s.kind),
);
if (!firstMatching) return;
const currentSourceId = watch('source');
const currentSource = inputSourceObjs.find(
s => s.id === (currentSourceId as any),
);
if (!currentSource || currentSource.connection !== selectedConnectionId) {
setValue('source', firstMatching.id, {
shouldDirty: true,
shouldTouch: true,
});
}
}, [selectedConnectionId, inputSourceObjs, setValue, watch]);
useEffect(() => {
if (!selectedConnectionId || !inputSourceObjs) return;
const allowedKinds = [SourceKind.Log, SourceKind.Trace];
const firstMatching = inputSourceObjs.find(
s =>
s.connection === selectedConnectionId && allowedKinds.includes(s.kind),
);
if (!firstMatching) return;
const currentSourceId = watch('source');
const currentSource = inputSourceObjs.find(
s => s.id === (currentSourceId as any),
);
if (!currentSource || currentSource.connection !== selectedConnectionId) {
setValue('source', firstMatching.id, {
shouldDirty: true,
shouldTouch: true,
});
// Apply source defaults for the new source
const newSource = inputSourceObjs.find(s => s.id === firstMatching.id);
if (newSource) {
setLastSelectedSourceId(newSource.id);
setValue('select', newSource.defaultTableSelectExpression ?? '');
setValue(
'orderBy',
`${getFirstTimestampValueExpression(newSource.timestampValueExpression ?? '')} DESC`
);
searchFilters.clearAllFilters();
setTimeout(() => debouncedSubmit(), 0);
}
}
}, [
selectedConnectionId,
inputSourceObjs,
setValue,
watch,
setLastSelectedSourceId,
searchFilters,
debouncedSubmit,
]);

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/app/src/DBSearchPage.tsx (2)

1106-1116: Initial search sends an ISO string to onSearch; parseTimeQuery likely fails

onSearch expects a time query string parseable into [start, end] (e.g., via dateRangeToString or “Live Tail”), but here it receives a single ISO string, which will not round-trip through parseTimeQuery. This prevents the initial time range from being set.

Apply this diff to pass a proper formatted range and sync the input:

-      if (!searchParams.has('from') && !searchParams.has('to')) {
-        const newTimeRange = new Date(defaultTimeRange[0]);
-        onSearch(newTimeRange.toISOString());
-      }
+      if (!searchParams.has('from') && !searchParams.has('to')) {
+        const initial = dateRangeToString(defaultTimeRange, false);
+        setDisplayedTimeInputValue(initial);
+        onSearch(initial);
+      }

700-709: Conflicting default for isLive re-enables live tail when URL has no time range

This effect turns live tail on whenever from/to are not in the URL, which contradicts the new fixed 15-minute default and the earlier effect that forces isLive to false at init. Remove or invert this to avoid flicker/race.

Two options, pick one:

  • Remove the effect entirely (recommended):
-  useEffect(() => {
-    if (_isLive == null && isReady) {
-      if (from == null && to == null) {
-        setIsLive(true);
-      } else {
-        setIsLive(false);
-      }
-    }
-  }, [_isLive, setIsLive, from, to, isReady]);
+  // default isLive is false when starting from a fixed time range; no-op here
  • Or invert the default to false:
-      if (from == null && to == null) {
-        setIsLive(true);
-      } else {
-        setIsLive(false);
-      }
+      // Keep live tail off by default unless explicitly enabled
+      setIsLive(false);
♻️ Duplicate comments (1)
packages/app/src/config.ts (1)

14-14: Provide a safe default for HDX_AUTH_EMAILS to avoid surprises downstream

Even though the hook guards against falsy values, defaulting here makes the contract explicit and prevents future direct JSON.parse sites from crashing.

-export const HDX_AUTH_EMAILS = env('NEXT_PUBLIC_HDX_AUTH_EMAILS');
+export const HDX_AUTH_EMAILS = env('NEXT_PUBLIC_HDX_AUTH_EMAILS') ?? '[]';
🧹 Nitpick comments (4)
packages/app/src/TeamPage.tsx (2)

341-377: De-duplicate ky error handling across mutations

onSuccess/onError handlers are repeated with nearly identical HTTPError parsing. Extract a tiny helper to improve maintainability and consistency.

Example helper (module-level or a small util):

function showKyError(e: unknown, fallback = 'Something went wrong. Please contact HyperDX team.') {
  if (e instanceof HTTPError) {
    e.response.json()
      .then((res: any) => notifications.show({ color: 'red', message: res?.message ?? fallback, autoClose: 5000 }))
      .catch(() => notifications.show({ color: 'red', message: fallback, autoClose: 5000 }));
  } else {
    notifications.show({ color: 'red', message: fallback, autoClose: 5000 });
  }
}

Then replace onError blocks with:

onError: (e) => showKyError(e),

Also applies to: 400-435, 444-477


1132-1146: Prefer typing the error parameter instead of using an untyped callback

Minor nit: give onError an explicit parameter type to aid tooling and future refactors.

-          onError: () => {
+          onError: (_e: unknown) => {
             notifications.show({
               color: 'red',
               message: `Failed to update ${label}`,
             });
           },
packages/app/src/DBSearchPage.tsx (2)

1220-1226: Prefer using hasAccess(email) instead of indexing authArray directly

Leverage the hook’s API for clarity and guardrails. This avoids type gymnastics on authArray and centralizes logic (e.g., truthiness, defaulting).

-  // Parse demo auth emails from environment variable
-  const { authArray } = useAuthEmails();
+  // Parse demo auth emails from environment variable
+  const { hasAccess } = useAuthEmails();
-              {authArray[me?.email as keyof typeof authArray] && (
+              {hasAccess(me?.email) && (
                 <Menu.Target>
                   <ActionIcon
                     variant="subtle"
                     color="dark.2"
                     size="sm"
                     title="Edit Source"
                   >
                     <Text size="xs">
                       <i className="bi bi-gear" />
                     </Text>
                   </ActionIcon>
                 </Menu.Target>
               )}

Also applies to: 1290-1304


1-1: Avoid disabling all ESLint rules at file scope

Prefer targeted disables or fixing the underlying issues to keep lint coverage effective.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
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.

📥 Commits

Reviewing files that changed from the base of the PR and between f9f7b64 and e4ac2e9.

📒 Files selected for processing (5)
  • packages/app/src/DBSearchPage.tsx (23 hunks)
  • packages/app/src/TeamPage.tsx (15 hunks)
  • packages/app/src/components/SourceSelect.tsx (3 hunks)
  • packages/app/src/config.ts (1 hunks)
  • packages/app/src/hooks/useAuthEmails.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/app/src/components/SourceSelect.tsx
  • packages/app/src/hooks/useAuthEmails.tsx
🧰 Additional context used
🧬 Code Graph Analysis (2)
packages/app/src/TeamPage.tsx (1)
packages/app/src/hooks/useAuthEmails.tsx (1)
  • useAuthEmails (9-48)
packages/app/src/DBSearchPage.tsx (5)
packages/app/src/connection.ts (1)
  • useConnections (43-54)
packages/app/src/source.ts (1)
  • useSources (77-88)
packages/app/src/timeQuery.ts (2)
  • dateRangeToString (38-48)
  • useNewTimeQuery (432-530)
packages/app/src/hooks/useAuthEmails.tsx (1)
  • useAuthEmails (9-48)
packages/app/src/components/SourceSelect.tsx (1)
  • SourceSelectControlled (67-67)
🔇 Additional comments (10)
packages/app/src/TeamPage.tsx (5)

43-43: Importing useAuthEmails to gate UI: LGTM

This aligns with the new access-control approach and keeps concerns separated in a hook.


298-305: Gating destructive actions behind hasAccess is correct

Conditionally showing “Remove” only when not current user, admin, and authorized is sound.

Also applies to: 334-334, 553-572


1053-1064: Team name “Change” button gating is consistent with auth model

Conditionally showing the control only for admins with access aligns with the broader UI policy.


1444-1452: Personal API Key visibility gated by me != null: LGTM

Showing the personal key card only when me is present prevents undefined access errors and is a reasonable UX.

Also applies to: 1349-1349


1460-1463: Section-level gating with hasAccess is applied consistently

Sources, Connections, TeamQueryConfig, and API Keys are correctly gated. Integrations and Team Name remain open, which seems intentional.

Also applies to: 1483-1489

packages/app/src/DBSearchPage.tsx (5)

395-402: Fixed 15-minute range helper: LGTM

The helper is clear and the intent to avoid auto-updating is preserved via module-level initialization.


573-575: Time initialization flow matches the new default behavior

  • Defaulting isLive to false is consistent.
  • Initial display value and useNewTimeQuery wiring look correct.

Also applies to: 675-688, 690-696


119-120: Adding connection to schema and queryStateMap: LGTM

Optional connection in the schema and query state will help persist the selection across reloads.

Also applies to: 507-508


600-637: Connection derivation and sync to URL are sensible

getInitialConnection priority (URL → current source → last source → first connection) is reasonable, and the watch handler keeps URL state in sync.

Also applies to: 639-658, 870-892


1240-1260: Auto-align source to selected connection: LGTM

Ensuring the selected source matches the active connection prevents cross-connection queries. The allowedKinds filter is a pragmatic constraint for current UI.

Comment on lines 1581 to 1582
sourceType={inputSourceObj?.kind}
showDelta={!!searchedSource?.durationExpression}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Passing SourceKind to filters matches the intent; verify the prop typing in the filters component

Good to pass inputSourceObj?.kind. Ensure DBSearchPageFilters (and any helper) now types sourceType?: SourceKind.

Run this to verify prop typing and usage:


🏁 Script executed:

#!/bin/bash
# Confirm DBSearchPageFilters prop and helper types accept SourceKind

echo "Searching for DBSearchPageFilters prop definitions..."
rg -n -C2 --type=ts --type=tsx 'interface\s+DBSearchPageFiltersProps|type\s+DBSearchPageFiltersProps' 

echo "Verifying sourceType typing to SourceKind..."
rg -n -C2 --type=ts --type=tsx 'sourceType\??:\s*SourceKind'

echo "Checking imports of SourceKind in the filters file..."
fd DBSearchPageFilters.tsx | xargs -I{} rg -n -C2 'SourceKind' {}

Length of output: 625


Let’s locate the filters file and search without relying on --type filters:


🏁 Script executed:

#!/bin/bash
set -e

echo "1. Locate DBSearchPageFilters.tsx..."
FILTER_FILE=$(fd DBSearchPageFilters.tsx | head -n1)
echo "$FILTER_FILE"

echo "2. Search for props definition (interface or type)..."
rg -n -C2 'DBSearchPageFiltersProps' "$FILTER_FILE"

echo "3. Check sourceType prop typing..."
rg -n -C2 'sourceType\??\s*:' "$FILTER_FILE"

echo "4. Verify import of SourceKind..."
rg -n -C2 'import.*SourceKind' "$FILTER_FILE"

Length of output: 528


Let’s inspect the filters component to confirm its prop typing and imports:


🏁 Script executed:

#!/bin/bash
set -e

# Locate the Filters component file
FILTER_FILE=$(fd DBSearchPageFilters.tsx | head -n1)
echo "Using filter file: $FILTER_FILE"

# 1) Look for Props definition (interface or type)
echo -e "\n-- Props interface/type definitions --"
rg -n 'interface DBSearchPageFiltersProps|type DBSearchPageFiltersProps' "$FILTER_FILE"

# 2) Locate the component signature
echo -e "\n-- Component declaration --"
rg -n -C2 'DBSearchPageFilters' "$FILTER_FILE"

# 3) Find all sourceType occurrences in props or default values
echo -e "\n-- sourceType usages --"
rg -n -C2 'sourceType' "$FILTER_FILE"

# 4) Verify import of SourceKind
echo -e "\n-- SourceKind imports --"
rg -n -C2 'import.*SourceKind' "$FILTER_FILE"

Length of output: 487


Let’s inspect the beginning of the filters component to see how props (including sourceType) are declared:


🏁 Script executed:

#!/bin/bash
set -e

FILE=packages/app/src/components/DBSearchPageFilters.tsx
echo "===== Displaying first 200 lines of $FILE ====="
sed -n '1,200p' "$FILE"

Length of output: 5371


Let’s locate the actual component declaration and its props type further down in the file:


🏁 Script executed:

#!/bin/bash
set -e

FILE=packages/app/src/components/DBSearchPageFilters.tsx

echo "1) Search for the component export:"
rg -n 'export\s+(const|function)\s+DBSearchPageFilters' "$FILE" || echo "→ Not found via export pattern"

echo -e "\n2) Search all occurrences of DBSearchPageFilters:"
rg -n -C2 'DBSearchPageFilters' "$FILE"

echo -e "\n3) If we find the props usage, show 10 lines around it:"
# Replace <LINE> with the line number from step 2 if found; otherwise we'll show lines 200–400
rg -n -C5 'DBSearchPageFilters\(' "$FILE" || sed -n '200,400p' "$FILE"

Length of output: 7632


Ensure DBSearchPageFilters accepts sourceType and showDelta

The filters component currently doesn’t declare sourceType or showDelta in its props, so passing them from DBSearchPage will break typing. Please update:

• packages/app/src/components/DBSearchPageFilters.tsx
– Define or extend a DBSearchPageFiltersProps interface (or type) that includes:
sourceType?: SourceKind
showDelta?: boolean
– Annotate the DBSearchPageFiltersComponent parameter with this props type.
– Update the helper signature:
```ts
// before
const getKeysForSourceType = (sourceType?: string) => { … }

// after
const getKeysForSourceType = (sourceType?: SourceKind) => { … }
```  

– Add an import for SourceKind at the top of the file.

🤖 Prompt for AI Agents
In packages/app/src/components/DBSearchPageFilters.tsx around the top and where
the component is declared, add or extend a DBSearchPageFiltersProps type that
includes sourceType?: SourceKind and showDelta?: boolean, import SourceKind from
its module at the top of the file, annotate the DBSearchPageFiltersComponent
function parameter with this DBSearchPageFiltersProps type, and change the
helper signature from getKeysForSourceType = (sourceType?: string) => { … } to
getKeysForSourceType = (sourceType?: SourceKind) => { … } so the component
accepts the props passed from DBSearchPage.tsx.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (6)
packages/app/src/components/DBSearchPageFilters.tsx (6)

233-237: Gate “Load more” auto-trigger to avoid N extra network calls on mount

Auto-triggering onLoadMore for every facet is costly. Gate by a “pinned field” signal and include it in deps.

-useEffect(() => {
-  if (onLoadMore && !hasLoadedMore) {
-    onLoadMore(name);
-  }
-}, [onLoadMore, hasLoadedMore, name]);
+useEffect(() => {
+  if (onLoadMore && !hasLoadedMore && isFieldPinned) {
+    onLoadMore(name);
+  }
+}, [onLoadMore, hasLoadedMore, name, isFieldPinned]);

448-466: Remove unused prop sourceType (noise in deps and logs)

sourceType is not used for behavior. Drop it from props and all references to reduce noise and accidental re-renders.

   sourceId,
-  sourceType,
   showDelta,
   denoiseResults,
   setDenoiseResults,
   sourceName,
   connectionName,
 }: {
@@
   chartConfig: ChartConfigWithDateRange;
   sourceId?: string;
-  sourceType?: string;
   showDelta: boolean;
   denoiseResults: boolean;
   setDenoiseResults: (denoiseResults: boolean) => void;
   sourceName?: string;
   connectionName?: string;

Note: also remove sourceType from any dependency arrays and debug logs below.


496-496: Remove unused useExplainQuery (saves an extra ClickHouse round-trip)

countData is only referenced in removed debug logs. Drop the hook call (and the import at top).

-  const { data: countData } = useExplainQuery(chartConfig);

Also remove the import:

- import { useExplainQuery } from '@/hooks/useExplainQuery';

562-565: Heads-up: limit: keyLimit won’t be honored until hook bug is fixed

useGetKeyValues currently hardcodes limit: 1000 internally, ignoring the passed limit. Please patch packages/app/src/hooks/useMetadata.tsx accordingly.

Apply in useMetadata.tsx (outside this diff):

-              limit: 1000,
+              limit,

Verification script:

#!/bin/bash
rg -nP '\blimit\s*:\s*1000\b' packages/app/src/hooks/useMetadata.tsx -n -C2

603-604: Fix callback dependencies to avoid stale facets config

loadMoreFilterValuesForKey closes over facetsChartConfig and dateRange; remove unused deps and include facetsChartConfig.

-    [chartConfig, setExtraFacets, dateRange, sourceType],
+    [facetsChartConfig, dateRange],

467-514: Strip debug console logging (risk of PII + noisy prod); use dev-only guard if needed

Large swaths of console.error logging in the render path will leak identifiers and bloat logs. Remove them or gate behind a dev-only guard; also avoid console in catch.

-  // Log all component props and variables
-  console.error('🚀 DBSearchPageFiltersComponent - ALL PROPS AND VARIABLES:');
-  console.error('📋 Main Props:');
-  console.error('  - analysisMode:', analysisMode);
-  console.error('  - isLive:', isLive);
-  console.error('  - sourceId:', sourceId);
-  console.error('  - sourceType:', sourceType);
-  console.error('  - showDelta:', showDelta);
-  console.error('  - denoiseResults:', denoiseResults);
-  console.error('  - chartConfig:', chartConfig);
-  console.error('📊 FilterStateHook Props:');
-  console.error('  - filters (filterState):', filterState);
-  console.error('  - clearAllFilters function:', typeof clearAllFilters);
-  console.error('  - clearFilter function:', typeof clearFilter);
-  console.error('  - setFilterValue function:', typeof setFilterValue);
-  console.error('🔧 Hook Results:');
@@
-  console.error('  - toggleFilterPin function:', typeof toggleFilterPin);
-  console.error('  - toggleFieldPin function:', typeof toggleFieldPin);
-  console.error('  - isFilterPinned function:', typeof isFilterPinned);
-  console.error('  - isFieldPinned function:', typeof isFieldPinned);
@@
-  console.error('  - width:', width);
-  console.error('  - startResize function:', typeof startResize);
@@
-  console.error('  - countData:', countData);
-  console.error('  - allFields data:', data);
-  console.error('  - allFields isLoading:', isLoading);
-  console.error('📊 State Variables:');
@@
-  console.error('  - showMoreFields:', showMoreFields);
-  console.error('  - setShowMoreFields function:', typeof setShowMoreFields);
-  console.error('🚀 END OF COMPONENT PROPS LOGGING 🚀');
-  console.error('');

Prefer a dev-only pattern in the catch:

-      } catch (error) {
-        console.error('failed to fetch more keys', error);
+      } catch (error) {
+        if (process.env.NODE_ENV !== 'production') {
+          // eslint-disable-next-line no-console
+          console.warn('failed to fetch more keys', error);
+        }

Also applies to: 493-495, 504-513, 509-514, 594-596

🧹 Nitpick comments (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)

32-63: Align override map typing and intent (currently untyped; joined-key path can’t match this map)

serviceMapOverride only has generic type keys (Logs/Traces/K8sEvents). The joined "connectionName | sourceName" lookup in getKeysForSourceType cannot hit this map, making that primary branch dead. Also add an explicit type to prevent accidental value shape drift.

Apply:

-// Override keys for specific source types
-const serviceMapOverride = {
+// Override keys for specific source types (and optionally connection|source tuples)
+const serviceMapOverride: Record<string, string[]> = {
📜 Review details

Configuration used: Repository: ShareChat/coderabbit/.coderabbit.yaml

Review profile: CHILL

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 15ed95d and f1e5697.

📒 Files selected for processing (1)
  • packages/app/src/components/DBSearchPageFilters.tsx (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/app/src/components/DBSearchPageFilters.tsx (5)
packages/common-utils/src/types.ts (1)
  • ChartConfigWithDateRange (419-419)
packages/app/src/searchFilters.tsx (2)
  • FilterStateHook (220-220)
  • usePinnedFilters (278-352)
packages/app/src/hooks/useExplainQuery.tsx (1)
  • useExplainQuery (8-36)
packages/app/src/hooks/useMetadata.tsx (2)
  • useAllFields (82-126)
  • useGetKeyValues (156-207)
packages/app/src/utils.ts (1)
  • mergePath (649-655)
🔇 Additional comments (3)
packages/app/src/components/DBSearchPageFilters.tsx (3)

122-124: LGTM: stable noop avoids double-firing

emptyFn keeps Checkbox onChange from double-firing because Group handles toggling; good pattern.


549-556: LGTM: facetsChartConfig correctly ignores current filters

Dropping where/orderBy for facet discovery avoids narrowing option sets; good call.


609-646: LGTM: facet merging and pin-first ordering

Merging extraFacets and reordering by pinned fields looks correct and stable.

Comment on lines +515 to +526
const fieldKeys = useMemo(() => {
const fields = data ?? [];
// Map Field.path to SQL-style key notation used in filters (e.g., ResourceAttributes['k8s.namespace.name'])
const keys = new Set<string>();
for (const f of fields as any[]) {
if (f?.path && Array.isArray(f.path)) {
const key = mergePath(f.path);
if (key) keys.add(key);
}
}
return Array.from(keys);
}, [data]);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use metadata-derived fieldKeys as a fallback when overrides are empty

When overrides don’t match, keysToFetch is [], useGetKeyValues is disabled, and users see “No filters available” despite metadata. Fall back to fieldKeys.

-const keysToFetch = useMemo(() => {
-  const overrides = getKeysForSourceType(sourceName, connectionName);
-  return overrides;
-}, [sourceName, connectionName]);
+const keysToFetch = useMemo(() => {
+  const overrides = getKeysForSourceType(sourceName, connectionName);
+  return overrides.length ? overrides : fieldKeys;
+}, [sourceName, connectionName, fieldKeys]);

Also applies to: 528-532

🤖 Prompt for AI Agents
In packages/app/src/components/DBSearchPageFilters.tsx around lines 515-526 (and
similarly lines 528-532), the current logic builds fieldKeys from metadata but
later paths use overrides/keysToFetch and disable useGetKeyValues which can
leave keysToFetch empty and show “No filters available”; update the conditional
flow so that when overrides or keysToFetch are empty (or useGetKeyValues is
false) you fall back to using the computed fieldKeys: if keysToFetch is empty,
populate it from fieldKeys before deciding to disable filters or skip fetching
values, and ensure the same fallback is applied in the other block at 528-532 so
metadata-derived keys are used whenever overrides are absent.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/app/src/DBSearchPage.tsx (2)

474-486: Bug: tableFilterExpression is dropped when user filters exist (filters overridden)

The current construction sets filters twice; when filters != null, it overrides the earlier array that included tableFilterExpression, losing the implicit table constraint. Build filters once, combining both inputs.

Apply:

-          ...(sourceObj.tableFilterExpression != null
-            ? {
-                filters: [
-                  {
-                    type: 'sql' as const,
-                    condition: sourceObj.tableFilterExpression,
-                  },
-                  ...(filters ?? []),
-                ],
-              }
-            : {}),
-          ...(filters != null ? { filters } : {}),
+          ...(sourceObj.tableFilterExpression != null || filters != null
+            ? {
+                filters: [
+                  ...(sourceObj.tableFilterExpression
+                    ? [
+                        {
+                          type: 'sql' as const,
+                          condition: sourceObj.tableFilterExpression,
+                        },
+                      ]
+                    : []),
+                  ...(filters ?? []),
+                ],
+              }
+            : {}),

1066-1076: Initial search uses an invalid timeQuery string (only start ISO); call onTimeRangeSelect instead

onSearch expects a parseable timeQuery string (e.g., “start - end”), but this effect passes only an ISO start. Use onTimeRangeSelect with both dates to set from/to correctly.

-      if (!searchParams.has('from') && !searchParams.has('to')) {
-        const newTimeRange = new Date(defaultTimeRange[0]);
-        onSearch(newTimeRange.toISOString());
-      }
+      if (!searchParams.has('from') && !searchParams.has('to')) {
+        const [start, end] = defaultTimeRange;
+        onTimeRangeSelect(start, end);
+      }
-  }, [isReady, queryReady, isChartConfigLoading, onSearch]);
+  }, [isReady, queryReady, isChartConfigLoading, onTimeRangeSelect]);
♻️ Duplicate comments (4)
packages/app/src/DBSearchPage.tsx (4)

462-503: Add connectionName to memo deps to avoid stale labels in charts/filters

connectionName is derived via useConnections and included in the returned config, but it’s not in the useMemo dependency array. This can leave a stale connectionName in downstream UIs when connections resolve or change.

Apply this patch:

   return useMemo(() => {
     if (sourceObj != null) {
       return {
         data: {
           select: select || (sourceObj.defaultTableSelectExpression ?? ''),
           from: sourceObj.from,
-          ...(sourceObj.tableFilterExpression != null
-            ? {
-                filters: [
-                  {
-                    type: 'sql' as const,
-                    condition: sourceObj.tableFilterExpression,
-                  },
-                  ...(filters ?? []),
-                ],
-              }
-            : {}),
-          ...(filters != null ? { filters } : {}),
+          // See next comment for a safer filters construction
           where: where ?? '',
           whereLanguage: whereLanguage ?? 'sql',
           timestampValueExpression: sourceObj.timestampValueExpression,
           implicitColumnExpression: sourceObj.implicitColumnExpression,
           connection: sourceObj.connection,
           connectionName,
           displayType: DisplayType.Search,
           orderBy:
             orderBy ||
             `${getFirstTimestampValueExpression(
               sourceObj.timestampValueExpression,
             )} DESC`,
         },
       };
     }

     return { data: null, isLoading };
-  }, [sourceObj, isLoading, select, filters, where, whereLanguage, orderBy]);
+  }, [sourceObj, isLoading, select, filters, where, whereLanguage, orderBy, connectionName]);

819-857: Watchers: user-driven updates covered; ensure programmatic updates apply defaults too

  • Source watcher (type === 'change') resets defaults and clears filters — good.
  • Connection watcher (type === 'change') updates URL and refreshes — good.

But programmatic setValue does not set type === 'change'. When you call setValue('source', ...) in effects, the watcher won’t reset select/orderBy/filters. See fix below in the connection→source effect.


1546-1549: Filters component receives new props; ensure its typings include them

DBSearchPageFilters now receives:

  • sourceType?: SourceKind
  • sourceName?: string
  • connectionName?: string

Please verify DBSearchPageFilters’ props/types are updated accordingly (and that SourceKind is imported in that file). Prior feedback covered sourceType/showDelta; connectionName and sourceName need the same treatment.

#!/bin/bash
# Verify DBSearchPageFilters props include the new fields and SourceKind
FILE=$(fd DBSearchPageFilters.tsx | head -n1)
echo "Inspecting $FILE"
rg -n -C2 'interface\s+DBSearchPageFiltersProps|type\s+DBSearchPageFiltersProps' "$FILE"
rg -n -C2 'sourceType\??:\s*SourceKind' "$FILE"
rg -n -C2 'sourceName\??:\s*string' "$FILE"
rg -n -C2 'connectionName\??:\s*string' "$FILE"
rg -n -C2 'import\s+{\s*SourceKind\s*}' "$FILE"

1201-1227: Programmatic source selection doesn’t apply defaults/filters reset

When the connection effect sets a new source (setValue('source', …)), the watch(handler) for source changes won’t run because type !== 'change'. Inline the same defaults/reset logic here.

   if (!currentSource) {
     setValue('source', firstMatching.id, {
       shouldDirty: true,
       shouldTouch: true,
     });
-    return;
+    // Apply the same defaults as the user-driven source change
+    setLastSelectedSourceId(firstMatching.id);
+    setValue('select', firstMatching.defaultTableSelectExpression ?? '');
+    setValue(
+      'orderBy',
+      `${getFirstTimestampValueExpression(firstMatching.timestampValueExpression ?? '')} DESC`,
+    );
+    searchFilters.clearAllFilters();
+    setTimeout(() => debouncedSubmit(), 0);
+    return;
   }
   // If there is a mismatch, prefer keeping the current source and align the connection to it
   if (currentSource.connection !== selectedConnectionId) {
     setValue('connection', currentSource.connection as any);
     setSearchedConfig(prev => ({ ...prev, connection: currentSource.connection }));
   }
-}, [selectedConnectionId, inputSourceObjs, setValue, setSearchedConfig, watch]);
+}, [selectedConnectionId, inputSourceObjs, setValue, setSearchedConfig, watch, setLastSelectedSourceId, searchFilters, debouncedSubmit]);
🧹 Nitpick comments (7)
packages/app/src/DBSearchPage.tsx (7)

1-1: Avoid file-wide ESLint disable; scope or justify rule exceptions

A blanket /* eslint-disable / can hide legitimate issues. Prefer targeted disables (/ eslint-disable-next-line */) at the smallest scope or document which rules are intentionally disabled and why.


119-134: Schema extended with optional connection; ensure it’s persisted and shareable

You added connection?: string to the zod schema, but:

  • Save/Update flows in SaveSearchModal do not persist connection.
  • generateSearchUrl omits connection, so shared URLs may lose context.

Consider persisting and serializing connection too. See separate comment on generateSearchUrl for a concrete patch.


395-402: Fixed default time window: OK, but compute at mount for accuracy

createFixedTimeRange is good for a non-live default. Minor: defining defaultTimeRange at module init may capture time slightly earlier than first render. If you want “15 minutes before first render,” compute it in a useMemo inside the component.


656-678: Initial time display may ignore user UTC preference

initialTimeDisplay is built with dateRangeToString(defaultTimeRange, false) and then passed to useNewTimeQuery as initialDisplayValue. This may disagree with a user’s isUTC preference that useNewTimeQuery applies elsewhere.

Options:

  • Pass undefined for initialDisplayValue and let useNewTimeQuery derive it from initialTimeRange + isUTC.
  • Or compute initialTimeDisplay with the user preference.

Example (preferred minimal change):

-  const initialTimeDisplay = useMemo(() => {
-    return dateRangeToString(defaultTimeRange, false);
-  }, []);
+  const initialTimeDisplay = undefined; // let useNewTimeQuery format using user isUTC

And update the useNewTimeQuery call to handle undefined initialDisplayValue (it already does).


1028-1045: Include connection in shareable search URL

Connection is part of state now but isn’t serialized into generateSearchUrl. Add it so links retain the connection context.

   const qParams = new URLSearchParams({
     where: where || searchedConfig.where || '',
     whereLanguage: whereLanguage || 'sql',
     from: searchedTimeRange[0].getTime().toString(),
     to: searchedTimeRange[1].getTime().toString(),
     select: searchedConfig.select || '',
     source: searchedSource?.id || '',
     filters: JSON.stringify(searchedConfig.filters),
   });
+  if (searchedConfig.connection) {
+    qParams.set('connection', searchedConfig.connection);
+  }
   return `/search?${qParams.toString()}`;

1184-1199: When auto-setting connection, also trigger a refresh

The effect sets form state and URL with the derived connection but doesn’t submit. If the intended UX is “auto-refresh on connection availability,” add a debounced submit.

   if (!currentConnection && inputSourceObjs && connections && connections.length > 0) {
     const preferredConnectionId = getInitialConnection();
     if (preferredConnectionId) {
       setValue('connection', preferredConnectionId);
       // Update URL state as well
       setSearchedConfig(prev => ({
         ...prev,
         connection: preferredConnectionId,
       }));
+      // Trigger refresh for the resolved connection
+      setTimeout(() => debouncedSubmit(), 0);
     }
   }
-}, [connections, inputSourceObjs, setValue, watch, getInitialConnection, setSearchedConfig]);
+}, [connections, inputSourceObjs, setValue, watch, getInitialConnection, setSearchedConfig, debouncedSubmit]);

1548-1549: Avoid (filtersChartConfig as any)?.connectionName; carry meta separately or type it

To prevent any-casts, either:

  • Extend the chart config type to include a optional meta: { connectionName?: string }, or
  • Compute connectionName in this component (via connections + searchedSource.connection) and pass it directly.

Small but improves type safety.

📜 Review details

Configuration used: Repository: ShareChat/coderabbit/.coderabbit.yaml

Review profile: CHILL

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.

📥 Commits

Reviewing files that changed from the base of the PR and between f1e5697 and 7b57907.

📒 Files selected for processing (1)
  • packages/app/src/DBSearchPage.tsx (27 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/app/src/DBSearchPage.tsx (5)
packages/app/src/connection.ts (1)
  • useConnections (43-54)
packages/app/src/source.ts (1)
  • useSources (77-88)
packages/app/src/timeQuery.ts (2)
  • dateRangeToString (38-48)
  • useNewTimeQuery (432-530)
packages/app/src/hooks/useAuthEmails.tsx (1)
  • useAuthEmails (9-48)
packages/app/src/components/SourceSelect.tsx (1)
  • SourceSelectControlled (70-70)
🔇 Additional comments (8)
packages/app/src/DBSearchPage.tsx (8)

82-108: New imports wiring looks consistent; verify side-effects and caching

  • useAuthEmails and useAliasMapFromChartConfig are used later and look fine.
  • useConnections import is appropriate; React Query caching should dedupe the duplicate useConnections call in this file (one inside useSearchedConfigToChartConfig and one in the page), so no concern.

514-515: Connection added to URL state: LGTM

queryStateMap now includes connection, enabling URL/state sync. This aligns with the new schema and form value.


556-687: Live Tail default/off ramp looks consistent

  • isLive explicitly defaults to false (Line 556), and several interactions disable it intentionally (Lines 565–574, 559–563, 665–669, 683–686). This matches the “fixed window” default UX.

702-709: URL→form sync preserves/derives connection: LGTM

The reset merges searchedConfig.connection or preserves existing form connection, otherwise falls back via getInitialConnection(). Good guardrails for history navigation.


997-1012: Derived table config memo: OK

dbSqlRowTableConfig memoization over chartConfig and searchedTimeRange is appropriate.


1243-1246: Connection selector commented out — confirm this UX

If hiding connection selection is intentional (deriving it from source), great. Otherwise, consider keeping it available for advanced users. Note: your watchers and effects already handle connection changes correctly.


1258-1271: Auth-gated “Edit Source” gear: LGTM

Conditional rendering based on useAuthEmails and me.email is straightforward and safe; the button is entirely hidden for unauthorized users.


1472-1477: TimePicker live toggle off: LGTM

showLive={false} and forcing setIsLive(false) on search aligns with the fixed-window UX.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/common-utils/src/metadata.ts (1)

491-496: Remove read_overflow_mode: 'break' from all metadata queries and update tests

  • In packages/common-utils/src/metadata.ts, remove read_overflow_mode: 'break' and drop the String(...) wrapper around DEFAULT_METADATA_MAX_ROWS_TO_READ in every occurrence (three spots where you guard on disableRowLimit).
  • Update packages/common-utils/src/__tests__/metadata.test.ts to no longer expect read_overflow_mode in the generated settings.
  • Review packages/common-utils/src/clickhouse.ts’s default read_overflow_mode: 'break'—switch to server defaults or 'throw' if you want errors on overflow rather than silent truncation.
♻️ Duplicate comments (9)
packages/common-utils/src/clickhouse.ts (1)

466-471: Do not set read_overflow_mode='break' globally; this risks silent, truncated results

Setting read_overflow_mode='break' at a global/default level can silently truncate datasets when max_rows_to_read hits, corrupting charts/metrics. Let the server default (usually 'throw') apply or allow callers to opt in per query. Also switch the row limit to a numeric literal for clarity.

Apply this diff:

-    // Apply global defaults for all queries
-    clickhouse_settings = {
-      max_rows_to_read: '10000000',
-      read_overflow_mode: 'break',
-      ...clickhouse_settings,
-    } as ClickHouseSettings;
+    // Apply safe defaults for all queries (callers can override)
+    clickhouse_settings = {
+      max_rows_to_read: 10_000_000,
+      // Intentionally omit read_overflow_mode to avoid silent truncation; callers can override per-query.
+      ...clickhouse_settings,
+    } as ClickHouseSettings;

Run to find any remaining occurrences forcing 'break':

#!/bin/bash
rg -nP -C2 "read_overflow_mode\s*:\s*['\"]break['\"]"
packages/app/src/components/DBSearchPageFilters.tsx (8)

256-261: Avoid eager “Load more” for every facet on mount

This causes N extra metadata calls. Gate by field pin.

-useEffect(() => {
-  if (onLoadMore && !hasLoadedMore) {
-    onLoadMore(name);
-  }
-}, [onLoadMore, hasLoadedMore, name]);
+useEffect(() => {
+  if (onLoadMore && !hasLoadedMore && isFieldPinned) {
+    onLoadMore(name);
+  }
+}, [onLoadMore, hasLoadedMore, name, isFieldPinned]);

22-22: Drop useExplainQuery; it’s not used functionally and adds a query

Remove import and hook call.

-import { useExplainQuery } from '@/hooks/useExplainQuery';
-  const { data: countData } = useExplainQuery(chartConfig);

Also applies to: 519-519


624-625: Fix callback dependencies (avoid stale facets config; drop unused deps)

Use facetsChartConfig and dateRange; remove chartConfig, sourceType, and the setter.

-    [chartConfig, setExtraFacets, dateRange, sourceType],
+    [facetsChartConfig, dateRange],

471-471: sourceType appears unused; remove or wire it into logic

It’s only in logs/deps; safe to drop if call sites don’t rely on it.

Run to verify before removal:

#!/bin/bash
# Find prop usage and references
rg -nP '\bsourceType\b' -C2

If unused, remove from props/types and from the loadMore callback deps:

-  sourceType,
@@
-  sourceType?: string;

Also applies to: 483-483


102-109: Harden getKeysForSourceType: trim/guard + explicit return type

Prevents undefined/whitespace joins and makes behavior predictable.

-const getKeysForSourceType = (sourceName?: string, connectionName?: string) => {
-  const newSourceKey = connectionName + ' | ' + sourceName;
-  if (newSourceKey && newSourceKey in serviceMapOverride) {
-    return serviceMapOverride[newSourceKey as keyof typeof serviceMapOverride];
-  }
-  return [];
-};
+const getKeysForSourceType = (
+  sourceName?: string,
+  connectionName?: string,
+): string[] => {
+  const src = sourceName?.trim();
+  const conn = connectionName?.trim();
+  if (!src || !conn) return [];
+  const newSourceKey = `${conn} | ${src}`;
+  if (newSourceKey in serviceMapOverride) {
+    return serviceMapOverride[newSourceKey as keyof typeof serviceMapOverride];
+  }
+  return [];
+};

490-518: Remove production console logging (noisy + PII risk)

Large debug dumps should not ship.

-  // Log all component props and variables
-  console.error('🚀 DBSearchPageFiltersComponent - ALL PROPS AND VARIABLES:');
-  console.error('📋 Main Props:');
-  console.error('  - analysisMode:', analysisMode);
-  console.error('  - isLive:', isLive);
-  console.error('  - sourceId:', sourceId);
-  console.error('  - sourceType:', sourceType);
-  console.error('  - showDelta:', showDelta);
-  console.error('  - denoiseResults:', denoiseResults);
-  console.error('  - chartConfig:', chartConfig);
-  console.error('📊 FilterStateHook Props:');
-  console.error('  - filters (filterState):', filterState);
-  console.error('  - clearAllFilters function:', typeof clearAllFilters);
-  console.error('  - clearFilter function:', typeof clearFilter);
-  console.error('  - setFilterValue function:', typeof setFilterValue);
-  console.error('🔧 Hook Results:');
+  // (debug logging removed)
@@
-  console.error('  - toggleFilterPin function:', typeof toggleFilterPin);
-  console.error('  - toggleFieldPin function:', typeof toggleFieldPin);
-  console.error('  - isFilterPinned function:', typeof isFilterPinned);
-  console.error('  - isFieldPinned function:', typeof isFieldPinned);
+  // (debug logging removed)
@@
-  console.error('  - width:', width);
-  console.error('  - startResize function:', typeof startResize);
+  // (debug logging removed)
@@
-  console.error('  - countData:', countData);
-  console.error('  - allFields data:', data);
-  console.error('  - allFields isLoading:', isLoading);
-  console.error('📊 State Variables:');
   const [showMoreFields, setShowMoreFields] = useState(false);
-  console.error('  - showMoreFields:', showMoreFields);
-  console.error('  - setShowMoreFields function:', typeof setShowMoreFields);
-  console.error('🚀 END OF COMPONENT PROPS LOGGING 🚀');
-  console.error('');

Also applies to: 527-537


538-555: Use metadata-derived fieldKeys as fallback when no override matches

Prevents “No filters available” when overrides are absent.

 const fieldKeys = useMemo(() => {
   const fields = data ?? [];
@@
   return Array.from(keys);
 }, [data]);

-const keysToFetch = useMemo(() => {
-  const overrides = getKeysForSourceType(sourceName, connectionName);
-  return overrides;
-}, [sourceName, connectionName]);
+const keysToFetch = useMemo(() => {
+  const overrides = getKeysForSourceType(sourceName, connectionName);
+  return overrides.length ? overrides : fieldKeys;
+}, [sourceName, connectionName, fieldKeys]);

583-586: Hook ignores caller limit; update useGetKeyValues to honor it

Your limit: keyLimit (200) is ignored because the hook hardcodes 1000.

Apply in packages/app/src/hooks/useMetadata.tsx:

-              limit: 1000,
+              limit,
🧹 Nitpick comments (1)
packages/common-utils/src/metadata.ts (1)

451-451: Limit bumped to 1000—confirm UX and latency at scale

A default of 1000 unique values per key can be heavy for wide keys. Ensure UI can handle it and dashboards remain responsive. Consider a defensive clamp or surfacing this via config.

📜 Review details

Configuration used: Repository: ShareChat/coderabbit/.coderabbit.yaml

Review profile: CHILL

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 7b57907 and 2bd3055.

📒 Files selected for processing (3)
  • packages/app/src/components/DBSearchPageFilters.tsx (9 hunks)
  • packages/common-utils/src/clickhouse.ts (1 hunks)
  • packages/common-utils/src/metadata.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/app/src/components/DBSearchPageFilters.tsx (5)
packages/common-utils/src/types.ts (1)
  • ChartConfigWithDateRange (419-419)
packages/app/src/searchFilters.tsx (2)
  • FilterStateHook (220-220)
  • usePinnedFilters (278-352)
packages/app/src/hooks/useExplainQuery.tsx (1)
  • useExplainQuery (8-36)
packages/app/src/hooks/useMetadata.tsx (2)
  • useAllFields (82-126)
  • useGetKeyValues (156-207)
packages/app/src/utils.ts (1)
  • mergePath (649-655)
packages/common-utils/src/metadata.ts (1)
packages/common-utils/src/types.ts (1)
  • ChartConfigWithDateRange (419-419)
🪛 ESLint
packages/app/src/components/DBSearchPageFilters.tsx

[error] 77-77: Delete ···

(prettier/prettier)


[error] 92-92: Delete ···

(prettier/prettier)

🔇 Additional comments (1)
packages/common-utils/src/metadata.ts (1)

18-18: Raising DEFAULT_METADATA_MAX_ROWS_TO_READ to 10e6—validate cluster safety

This can increase CPU/memory and query time notably. Confirm CH server/user-level limits (max_rows_to_read, max_result_rows, max_bytes_to_read, timeout) and dashboard latency budgets.

Comment on lines 59 to 68
'US | Logs': [
'SeverityText ',
'ServiceName',
"ResourceAttributes['k8s.cluster.name']",
"ResourceAttributes['k8s.namespace.name']",
"ResourceAttributes['k8s.container.name']",
"ResourceAttributes['label.team']",
"ResourceAttributes['label.pod']",
],
'US | Traces': [
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix override entries: trailing space and duplicate key break matching; clean up lint errors

  • 'SeverityText ' has a trailing space; it won’t match the actual field.
  • Duplicate "ResourceAttributes['label.pod']" in US | Traces.
  • Trailing spaces (Prettier errors at Lines 77, 92).

Apply:

   'US | Logs': [
-    'SeverityText ',
+    'SeverityText',
     'ServiceName',
@@
   'US | Traces': [
@@
-    "ResourceAttributes['label.pod']",
-    "ResourceAttributes['label.pod']",   
+    "ResourceAttributes['label.pod']",
@@
   'Sgp | Logs': [
@@
-    "ResourceAttributes['label.pod']",   
+    "ResourceAttributes['label.pod']",

Also applies to: 68-79, 85-93

🤖 Prompt for AI Agents
In packages/app/src/components/DBSearchPageFilters.tsx around lines 59-68 (and
also review lines 68-79 and 85-93), fix the override entries: remove the
trailing space from 'SeverityText ' so it exactly matches the actual field name,
remove the duplicate "ResourceAttributes['label.pod']" entry in the US | Traces
list, and trim any trailing spaces on the affected lines to resolve
Prettier/lint errors; ensure no duplicate keys remain and reformat the arrays so
each field string matches the real field name exactly.

Comment on lines +460 to +471
const cacheKey = [
chartConfig.from.databaseName,
chartConfig.from.tableName,
keys.join(','),
chartConfig.dateRange.toString(),
String(disableRowLimit),
chartConfig.connection ?? '',
chartConfig.whereLanguage ?? '',
chartConfig.where ?? '',
JSON.stringify(chartConfig.filters ?? []),
].join('.');

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Cache-key bug: dateRange.toString() yields “[object Object]” → collisions/stale data

Using Object.prototype.toString on dateRange produces “[object Object]”, so different ranges share the same cache entry. Serialize deterministically.

Apply this diff:

-    const cacheKey = [
-      chartConfig.from.databaseName,
-      chartConfig.from.tableName,
-      keys.join(','),
-      chartConfig.dateRange.toString(),
-      String(disableRowLimit),
-      chartConfig.connection ?? '',
-      chartConfig.whereLanguage ?? '',
-      chartConfig.where ?? '',
-      JSON.stringify(chartConfig.filters ?? []),
-    ].join('.');
+    const cacheKey = JSON.stringify({
+      from: chartConfig.from,
+      keys,
+      dateRange: chartConfig.dateRange, // stable object serialization
+      disableRowLimit,
+      connection: chartConfig.connection ?? '',
+      whereLanguage: chartConfig.whereLanguage ?? '',
+      where: chartConfig.where ?? '',
+      filters: chartConfig.filters ?? [],
+    });
📝 Committable suggestion

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

Suggested change
const cacheKey = [
chartConfig.from.databaseName,
chartConfig.from.tableName,
keys.join(','),
chartConfig.dateRange.toString(),
String(disableRowLimit),
chartConfig.connection ?? '',
chartConfig.whereLanguage ?? '',
chartConfig.where ?? '',
JSON.stringify(chartConfig.filters ?? []),
].join('.');
const cacheKey = JSON.stringify({
from: chartConfig.from,
keys,
dateRange: chartConfig.dateRange, // stable object serialization
disableRowLimit,
connection: chartConfig.connection ?? '',
whereLanguage: chartConfig.whereLanguage ?? '',
where: chartConfig.where ?? '',
filters: chartConfig.filters ?? [],
});
🤖 Prompt for AI Agents
In packages/common-utils/src/metadata.ts around lines 460 to 471, the cache key
uses chartConfig.dateRange.toString(), which yields "[object Object]" and causes
cache collisions; replace that with a deterministic serialization such as
JSON.stringify(chartConfig.dateRange ?? {}) (or JSON.stringify with sorted keys
if you need stable ordering) so different date ranges produce distinct cache
keys and handle undefined/null safely.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)

22-22: Remove debug logging and unused explain hook

Noisy console logging risks PII; useExplainQuery is only used for logs.

-import { useExplainQuery } from '@/hooks/useExplainQuery';
-  // Log all component props and variables
-  console.error('🚀 DBSearchPageFiltersComponent - ALL PROPS AND VARIABLES:');
-  console.error('📋 Main Props:');
-  console.error('  - analysisMode:', analysisMode);
-  console.error('  - isLive:', isLive);
-  console.error('  - sourceId:', sourceId);
-  console.error('  - sourceType:', sourceType);
-  console.error('  - showDelta:', showDelta);
-  console.error('  - denoiseResults:', denoiseResults);
-  console.error('  - chartConfig:', chartConfig);
-  console.error('📊 FilterStateHook Props:');
-  console.error('  - filters (filterState):', filterState);
-  console.error('  - clearAllFilters function:', typeof clearAllFilters);
-  console.error('  - clearFilter function:', typeof clearFilter);
-  console.error('  - setFilterValue function:', typeof setFilterValue);
-  console.error('🔧 Hook Results:');
-  const { toggleFilterPin, toggleFieldPin, isFilterPinned, isFieldPinned } =
-    usePinnedFilters(sourceId ?? null);
-  console.error('  - toggleFilterPin function:', typeof toggleFilterPin);
-  console.error('  - toggleFieldPin function:', typeof toggleFieldPin);
-  console.error('  - isFilterPinned function:', typeof isFilterPinned);
-  console.error('  - isFieldPinned function:', typeof isFieldPinned);
-  const { width, startResize } = useResizable(16, 'left');
-  console.error('  - width:', width);
-  console.error('  - startResize function:', typeof startResize);
-  const { data: countData } = useExplainQuery(chartConfig);
-  const { data, isLoading } = useAllFields({
+  const { toggleFilterPin, toggleFieldPin, isFilterPinned, isFieldPinned } =
+    usePinnedFilters(sourceId ?? null);
+  const { width, startResize } = useResizable(16, 'left');
+  const { data, isLoading } = useAllFields({
       databaseName: chartConfig.from.databaseName,
       tableName: chartConfig.from.tableName,
       connectionId: chartConfig.connection,
     });
-  console.error('  - countData:', countData);
-  console.error('  - allFields data:', data);
-  console.error('  - allFields isLoading:', isLoading);
-  console.error('📊 State Variables:');
-  const [showMoreFields, setShowMoreFields] = useState(false);
-  console.error('  - showMoreFields:', showMoreFields);
-  console.error('  - setShowMoreFields function:', typeof setShowMoreFields);
-  console.error('🚀 END OF COMPONENT PROPS LOGGING 🚀');
-  console.error('');
+  const [showMoreFields, setShowMoreFields] = useState(false);

Also applies to: 490-537, 519-519

♻️ Duplicate comments (6)
packages/app/src/components/DBSearchPageFilters.tsx (6)

256-261: Don’t auto “Load more” for every facet on mount (perf/cost)

Gate by pinned fields (or remove auto-run).

-useEffect(() => {
-  if (onLoadMore && !hasLoadedMore) {
-    onLoadMore(name);
-  }
-}, [onLoadMore, hasLoadedMore, name]);
+useEffect(() => {
+  if (onLoadMore && !hasLoadedMore && isFieldPinned) {
+    onLoadMore(name);
+  }
+}, [onLoadMore, hasLoadedMore, name, isFieldPinned]);

583-586: Minor: ensure hook honors the passed limit (external file change)

If useGetKeyValues internally hardcodes limit (e.g., 1000), the 200 here is ignored. Please adjust the hook to forward the caller’s limit.

#!/bin/bash
# Verify hook respects caller's limit
rg -nP '\blimit\s*:\s*1000\b' packages/app/src/hooks/useMetadata.tsx -C2 || true
rg -nP 'useGetKeyValues\(\s*\{' packages/app/src/components/DBSearchPageFilters.tsx -n -A5 -B2

59-61: Fix override typos/duplicates so matches actually work

  • 'SeverityText ' has a trailing space → won’t match.
  • Duplicate "ResourceAttributes['label.pod']" under US | Traces.
   'US | Logs': [
-    'SeverityText ',
+    'SeverityText',
     'ServiceName',
@@
   'US | Traces': [
     'ServiceName',
     'StatusCode',
     'SpanKind',
     "ResourceAttributes['k8s.cluster.name']",
     "ResourceAttributes['k8s.namespace.name']",
     "ResourceAttributes['k8s.container.name']",
     "ResourceAttributes['label.team']",
-    "ResourceAttributes['label.pod']",
-    "ResourceAttributes['label.pod']",
+    "ResourceAttributes['label.pod']",
   ],

Also applies to: 68-79, 85-93


102-109: Harden getKeysForSourceType (guard/trim/explicit type)

Avoid undefined joins and normalize inputs; add explicit return type.

-// Helper function to get keys - override for specific types, use default for others
-const getKeysForSourceType = (sourceName?: string, connectionName?: string) => {
-  const newSourceKey = connectionName + ' | ' + sourceName;
-  if (newSourceKey && newSourceKey in serviceMapOverride) {
-    return serviceMapOverride[newSourceKey as keyof typeof serviceMapOverride];
-  }
-  return [];
-};
+// Helper function to get keys - override for specific types, use default for others
+const getKeysForSourceType = (
+  sourceName?: string,
+  connectionName?: string,
+): string[] => {
+  const src = sourceName?.trim();
+  const conn = connectionName?.trim();
+  if (!src || !conn) return [];
+  const newSourceKey = `${conn} | ${src}`;
+  return (
+    serviceMapOverride[newSourceKey as keyof typeof serviceMapOverride] ?? []
+  );
+};

551-555: Use metadata-derived fieldKeys as fallback to avoid “No filters available”

When no override matches, fetch by discovered fields.

-const keysToFetch = useMemo(() => {
-  const overrides = getKeysForSourceType(sourceName, connectionName);
-  return overrides;
-}, [sourceName, connectionName]);
+const keysToFetch = useMemo(() => {
+  const overrides = getKeysForSourceType(sourceName, connectionName);
+  return overrides.length ? overrides : fieldKeys;
+}, [sourceName, connectionName, fieldKeys]);

Also applies to: 538-549


624-624: Fix callback deps (avoid stale facets config; remove unused deps)

Use facetsChartConfig/dateRange; drop chartConfig/setExtraFacets/sourceType.

-    [chartConfig, setExtraFacets, dateRange, sourceType],
+    [facetsChartConfig, dateRange],
🧹 Nitpick comments (2)
packages/app/src/components/DBSearchPageFilters.tsx (2)

471-477: Drop unused sourceType prop if no longer read

After removing debug logs there’s no usage. Safer to delete prop to avoid confusion; update call sites accordingly.

-  sourceId,
-  sourceType,
+  sourceId,
@@
-  sourceType?: string;
@@
-  sourceName?: string;
-  connectionName?: string;
+  sourceName?: string;
+  connectionName?: string;

Would you like a follow-up PR to update DBSearchPage.tsx and other call sites?

Also applies to: 483-489


615-615: Avoid console in production error path

Use a dev-only guard or app logger.

-        console.error('failed to fetch more keys', error);
+        if (process.env.NODE_ENV !== 'production') {
+          // eslint-disable-next-line no-console
+          console.error('failed to fetch more keys', error);
+        }
📜 Review details

Configuration used: Repository: ShareChat/coderabbit/.coderabbit.yaml

Review profile: CHILL

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 2bd3055 and e3bbf04.

📒 Files selected for processing (1)
  • packages/app/src/components/DBSearchPageFilters.tsx (9 hunks)
🔇 Additional comments (1)
packages/app/src/components/DBSearchPageFilters.tsx (1)

627-667: Facet merging + pinned-first ordering looks solid

Merges extra values without dupes and prioritizes pinned fields.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (11)
packages/app/src/config.ts (1)

14-17: Fix formatting issues and improve error handling

The current implementation has formatting issues flagged by Prettier and lacks proper error handling for the Number conversion.

Apply this diff to fix both issues:

-export const HDX_AUTH_EMAILS = env('NEXT_PUBLIC_HDX_AUTH_EMAILS');
-export const HDX_METADATA_MAX_ROWS_TO_READ = Number(
-  env('NEXT_PUBLIC_HDX_METADATA_MAX_ROWS_TO_READ') ?? '31000000'
-);
+export const HDX_AUTH_EMAILS = env('NEXT_PUBLIC_HDX_AUTH_EMAILS') ?? '[]';
+export const HDX_METADATA_MAX_ROWS_TO_READ = Number(
+  env('NEXT_PUBLIC_HDX_METADATA_MAX_ROWS_TO_READ') ?? '31000000',
+);

This addresses:

  • HDX_AUTH_EMAILS: Provides a safe default to prevent JSON.parse(undefined) crashes downstream
  • HDX_METADATA_MAX_ROWS_TO_READ: Adds missing trailing comma for Prettier compliance
packages/app/src/hooks/useMetadata.tsx (1)

191-193: Review the hardcoded limits and their interaction with function parameters

There are a few concerns with these hardcoded values:

  1. Keys limit increased: The change from 20 to 50 keys significantly increases the potential for request header size issues (mentioned in the comment). Has this been tested with larger key sets?

  2. Limit parameter shadowing: The hardcoded limit: 1000 completely ignores the function's limit parameter, which could confuse callers who expect their limit to be respected.

Consider this approach to respect the caller's intent while providing sensible defaults:

-keys: keys.slice(0, 50), // Limit to 50 keys for now, otherwise request fails (max header size)
-limit: 1000,
+keys: keys.slice(0, Math.min(keys.length, 50)), // Limit to 50 keys for now, otherwise request fails (max header size)
+limit: limit ?? 1000,

Let me verify if these limits are used consistently across the codebase:

#!/bin/bash
# Description: Check how useGetKeyValues limit parameter is used and if the hardcoded limits are appropriate

# Search for useGetKeyValues usage patterns
rg -n --type=tsx --type=ts "useGetKeyValues" -A 10 -B 5

# Look for other key/limit configurations
rg -n --type=tsx --type=ts "keys.*slice.*\d+" -C 3

# Search for limit parameter usage patterns
rg -n --type=tsx --type=ts "limit.*\d+" -C 3
packages/app/src/components/DBSearchPageFilters.tsx (9)

267-271: Don’t auto “Load more” for every facet (cost/perf); gate by pinned

Prevents N extra metadata calls on mount.

Apply:

 useEffect(() => {
-  if (onLoadMore && !hasLoadedMore) {
+  if (onLoadMore && !hasLoadedMore && isFieldPinned) {
     onLoadMore(name);
   }
-}, [onLoadMore, hasLoadedMore, name]);
+}, [onLoadMore, hasLoadedMore, name, isFieldPinned]);

22-22: Drop unused explain query (unnecessary network call)

No longer used after removing debug logs.

Apply:

-import { useExplainQuery } from '@/hooks/useExplainQuery';
-  const { data: countData } = useExplainQuery(chartConfig);

Also applies to: 530-530


611-616: Use the same limit constant and fix callback deps

Avoid magic number; prevent stale closure.

Apply:

           keys: [key],
-          limit: 200,
+          limit: keyLimit,
           disableRowLimit: true,
-  }, [chartConfig, setExtraFacets, dateRange, sourceType],
+  }, [facetsChartConfig, dateRange],

Also applies to: 635-636


482-487: Remove unused sourceType prop and dependency

It’s not referenced anymore; keep props minimal.

Apply:

   sourceId,
-  sourceType,
   showDelta,
@@
-  sourceType?: string;
@@
   sourceName?: string;
   connectionName?: string;

And drop from deps (covered above).

Also applies to: 494-500, 635-636


593-597: Hook ignores passed limit; update useGetKeyValues to honor it

The hook hardcodes limit: 1000 in useMetadata.tsx; your limit here is ineffective.

Update in packages/app/src/hooks/useMetadata.tsx:

-              limit: 1000,
+              limit,

Verification script:

#!/bin/bash
rg -nP '\blimit\s*:\s*1000\b' packages/app/src/hooks/useMetadata.tsx -C2
rg -nP 'useGetKeyValues\(\s*\{' packages/app/src/components/DBSearchPageFilters.tsx -n -A6 -B2

63-73: Fix override typos/dupes that break matching

  • 'SeverityText ' has a trailing space (no match).
  • Duplicate "ResourceAttributes['label.pod']" in US | Traces.

Apply:

   'US | Logs': [
     "ResourceAttributes['cloud']",
     "LogAttributes['log.iostream']",
-    'SeverityText ',
+    'SeverityText',
     'ServiceName',
@@
   'US | Traces': [
@@
-    "ResourceAttributes['label.pod']",
-    "ResourceAttributes['label.pod']",
+    "ResourceAttributes['label.pod']",
@@
   'Sgp | Logs': [
@@
-    "ResourceAttributes['label.pod']",
+    "ResourceAttributes['label.pod']",

Also applies to: 74-85, 93-104


113-120: Normalize and guard joined-key lookup; add explicit return type

Prevents misses from stray spaces/undefined parts.

Apply:

-// Helper function to get keys - override for specific types, use default for others
-const getKeysForSourceType = (sourceName?: string, connectionName?: string) => {
-  const newSourceKey = connectionName + ' | ' + sourceName;
-  if (newSourceKey && newSourceKey in serviceMapOverride) {
-    return serviceMapOverride[newSourceKey as keyof typeof serviceMapOverride];
-  }
-  return [];
-};
+// Helper function to get keys - override for specific types, use default for others
+const getKeysForSourceType = (
+  sourceName?: string,
+  connectionName?: string,
+): string[] => {
+  const src = sourceName?.trim();
+  const conn = connectionName?.trim();
+  if (!src || !conn) return [];
+  const joined = `${conn} | ${src}`;
+  return serviceMapOverride[joined as keyof typeof serviceMapOverride] ?? [];
+};

501-529: Remove debug console logging (noise + PII risk)

These run in render path and should not ship.

Apply:

-  // Log all component props and variables
-  console.error('🚀 DBSearchPageFiltersComponent - ALL PROPS AND VARIABLES:');
-  console.error('📋 Main Props:');
-  console.error('  - analysisMode:', analysisMode);
-  console.error('  - isLive:', isLive);
-  console.error('  - sourceId:', sourceId);
-  console.error('  - sourceType:', sourceType);
-  console.error('  - showDelta:', showDelta);
-  console.error('  - denoiseResults:', denoiseResults);
-  console.error('  - chartConfig:', chartConfig);
-  console.error('📊 FilterStateHook Props:');
-  console.error('  - filters (filterState):', filterState);
-  console.error('  - clearAllFilters function:', typeof clearAllFilters);
-  console.error('  - clearFilter function:', typeof clearFilter);
-  console.error('  - setFilterValue function:', typeof setFilterValue);
-  console.error('🔧 Hook Results:');
-
   const { toggleFilterPin, toggleFieldPin, isFilterPinned, isFieldPinned } =
     usePinnedFilters(sourceId ?? null);
-
-  console.error('  - toggleFilterPin function:', typeof toggleFilterPin);
-  console.error('  - toggleFieldPin function:', typeof toggleFieldPin);
-  console.error('  - isFilterPinned function:', typeof isFilterPinned);
-  console.error('  - isFieldPinned function:', typeof isFieldPinned);
-
   const { width, startResize } = useResizable(16, 'left');
-  console.error('  - width:', width);
-  console.error('  - startResize function:', typeof startResize);
@@
-  console.error('  - countData:', countData);
-  console.error('  - allFields data:', data);
-  console.error('  - allFields isLoading:', isLoading);
-  console.error('📊 State Variables:');
-
   const [showMoreFields, setShowMoreFields] = useState(false);
-  console.error('  - showMoreFields:', showMoreFields);
-  console.error('  - setShowMoreFields function:', typeof setShowMoreFields);
-  console.error('🚀 END OF COMPONENT PROPS LOGGING 🚀');
-  console.error('');

Also applies to: 538-547


562-566: Fallback to metadata-derived keys when no override found

Prevents “No filters available” when overrides don’t match.

Apply:

 const keysToFetch = useMemo(() => {
   const overrides = getKeysForSourceType(sourceName, connectionName);
-  return overrides;
-}, [sourceName, connectionName]);
+  return overrides.length ? overrides : fieldKeys;
+}, [sourceName, connectionName, fieldKeys]);

Also applies to: 549-560

🧹 Nitpick comments (5)
packages/app/src/components/DBSearchPageFilters.tsx (3)

32-112: Type the override map to catch mistakes at compile time

Adds safety and avoids silent mismatches.

Apply:

-const serviceMapOverride = {
+const serviceMapOverride: Record<string, string[]> = {

625-627: Remove console in catch; surface errors via UI/telemetry if needed

Console noise in UI path; keep UX clean.

Apply:

-      } catch (error) {
-        console.error('failed to fetch more keys', error);
+      } catch (error) {
+        // Swallow fetch error; consider toast/telemetry if desired.

32-112: Run Prettier/ESLint after changes

Ensures consistent quotes/spacing and catches any now-unused imports.

Run:

#!/bin/bash
npx prettier --write packages/app/src/components/DBSearchPageFilters.tsx
npx eslint packages/app/src/components/DBSearchPageFilters.tsx --fix
rg -nP --hidden --no-ignore '\bconsole\.' packages/app/src/components/DBSearchPageFilters.tsx || echo "✅ No console statements found."
rg -nP 'useExplainQuery' packages/app/src/components/DBSearchPageFilters.tsx || echo "✅ useExplainQuery removed."

Also applies to: 113-120, 501-529

packages/app/src/components/DBRowTable.tsx (2)

530-531: Respect the wrapLines prop; avoid unexpected default and dead prop.

Hard-coding true changes UX to wrapped lines by default and ignores the wrapLines prop (the caller passes wrapLines={false} at Line 1162). Initialize from the prop so parents can control the initial state.

-    const [wrapLinesEnabled, setWrapLinesEnabled] = useState(true);
+    const [wrapLinesEnabled, setWrapLinesEnabled] = useState(wrapLines);

If you intend to make wrapping always-on globally, consider removing the wrapLines prop from RawLogTable and its call sites to prevent API drift.


692-697: Add accessible label for the wrap-lines toggle.

Icon-only button lacks an accessible name; add aria-label or title for a11y and better UX.

-                          <UnstyledButton
-                            onClick={() => setWrapLinesEnabled(prev => !prev)}
-                          >
+                          <UnstyledButton
+                            aria-label="Toggle line wrapping"
+                            title="Toggle line wrapping"
+                            onClick={() => setWrapLinesEnabled(prev => !prev)}
+                          >
📜 Review details

Configuration used: Repository: ShareChat/coderabbit/.coderabbit.yaml

Review profile: CHILL

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.

📥 Commits

Reviewing files that changed from the base of the PR and between e3bbf04 and f6b5dc4.

📒 Files selected for processing (6)
  • packages/app/src/components/DBRowTable.tsx (1 hunks)
  • packages/app/src/components/DBSearchPageFilters.tsx (9 hunks)
  • packages/app/src/components/SearchTotalCountChart.tsx (2 hunks)
  • packages/app/src/config.ts (1 hunks)
  • packages/app/src/hooks/useChartConfig.tsx (2 hunks)
  • packages/app/src/hooks/useMetadata.tsx (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
packages/app/src/components/SearchTotalCountChart.tsx (1)
packages/app/src/config.ts (1)
  • HDX_METADATA_MAX_ROWS_TO_READ (15-17)
packages/app/src/hooks/useMetadata.tsx (1)
packages/app/src/config.ts (1)
  • HDX_METADATA_MAX_ROWS_TO_READ (15-17)
packages/app/src/components/DBSearchPageFilters.tsx (5)
packages/common-utils/src/types.ts (1)
  • ChartConfigWithDateRange (419-419)
packages/app/src/searchFilters.tsx (2)
  • FilterStateHook (220-220)
  • usePinnedFilters (278-352)
packages/app/src/hooks/useExplainQuery.tsx (1)
  • useExplainQuery (8-36)
packages/app/src/hooks/useMetadata.tsx (2)
  • useAllFields (83-125)
  • useGetKeyValues (155-204)
packages/app/src/utils.ts (1)
  • mergePath (649-655)
🪛 ESLint
packages/app/src/config.ts

[error] 16-16: Insert ,

(prettier/prettier)

🔇 Additional comments (5)
packages/app/src/components/SearchTotalCountChart.tsx (1)

30-34: LGTM: Proper configuration override for metadata read limits

The implementation correctly uses the new HDX_METADATA_MAX_ROWS_TO_READ configuration to override ClickHouse settings specifically for total count queries. The comment clearly indicates this is an API-specific override, and the pattern aligns well with the broader PR's approach to configurable metadata limits.

packages/app/src/hooks/useMetadata.tsx (2)

104-106: LGTM: Consistent global metadata configuration

The refactoring from per-team conditional metadata settings to using the global HDX_METADATA_MAX_ROWS_TO_READ configuration provides better consistency and maintainability.


183-185: LGTM: Consistent with useAllFields approach

The metadata configuration follows the same pattern established in useAllFields, providing consistent behavior across the codebase.

packages/app/src/hooks/useChartConfig.tsx (2)

24-24: LGTM: Clean interface extension for ClickHouse settings

The addition of the optional clickhouseSettings parameter provides a flexible way to pass per-query ClickHouse settings without breaking existing usage.


54-54: LGTM: Proper forwarding of ClickHouse settings

The implementation correctly passes the optional clickhouseSettings to the ClickHouse client, maintaining the chain from API call to database query configuration.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/app/src/config.ts (1)

14-14: Default HDX_AUTH_EMAILS to a safe JSON string to prevent JSON.parse crashes

Without a fallback, downstream JSON.parse can throw when the env is unset or empty. Use a trimmed default.

-export const HDX_AUTH_EMAILS = env('NEXT_PUBLIC_HDX_AUTH_EMAILS');
+export const HDX_AUTH_EMAILS =
+  env('NEXT_PUBLIC_HDX_AUTH_EMAILS')?.trim() || '[]';

Run to confirm the hook guards parsing:

#!/bin/bash
rg -nC2 --color=never "useAuthEmails|HDX_AUTH_EMAILS|JSON\.parse" packages/app/src
📜 Review details

Configuration used: Repository: ShareChat/coderabbit/.coderabbit.yaml

Review profile: CHILL

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 3b35709 and bf65fa2.

📒 Files selected for processing (1)
  • packages/app/src/config.ts (1 hunks)

Comment on lines +15 to +17
export const HDX_METADATA_MAX_ROWS_TO_READ = Number(
env('NEXT_PUBLIC_HDX_METADATA_MAX_ROWS_TO_READ') ?? '31000000',
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden numeric parsing for HDX_METADATA_MAX_ROWS_TO_READ (avoid NaN/0 on empty/malformed values)

Number('') -> 0 and Number('abc') -> NaN; ?? '31000000' won't catch empty strings. Add validation and a sane fallback.

-export const HDX_METADATA_MAX_ROWS_TO_READ = Number(
-  env('NEXT_PUBLIC_HDX_METADATA_MAX_ROWS_TO_READ') ?? '31000000',
-);
+export const HDX_METADATA_MAX_ROWS_TO_READ = (() => {
+  const raw = env('NEXT_PUBLIC_HDX_METADATA_MAX_ROWS_TO_READ');
+  const n = Number(raw);
+  const fallback = 31_000_000;
+  return Number.isFinite(n) && n > 0 ? n : fallback;
+})();
📝 Committable suggestion

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

Suggested change
export const HDX_METADATA_MAX_ROWS_TO_READ = Number(
env('NEXT_PUBLIC_HDX_METADATA_MAX_ROWS_TO_READ') ?? '31000000',
);
export const HDX_METADATA_MAX_ROWS_TO_READ = (() => {
const raw = env('NEXT_PUBLIC_HDX_METADATA_MAX_ROWS_TO_READ');
const n = Number(raw);
const fallback = 31_000_000;
return Number.isFinite(n) && n > 0 ? n : fallback;
})();
🤖 Prompt for AI Agents
In packages/app/src/config.ts around lines 15-17, the current Number(...)
parsing will convert empty strings to 0 and malformed strings to NaN and the
nullish coalescing won't catch empty values; change the export to robustly parse
and validate the env var: read the raw env string, if it's non-empty use
parseInt(raw, 10) (or Number) then check Number.isFinite(value) && value > 0,
otherwise fall back to the default 31000000; ensure the exported constant is an
integer (Math.floor if necessary) and document/handle when the env is invalid by
using the default.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/app/src/components/DBRowJsonViewer.tsx (2)

148-156: Do not mutate props: clone before filtering.

You’re deleting keys directly from data, which can cause subtle bugs if the caller reuses the same object. Clone/filter first, then pass to the recursive filter.

   const rowData = useMemo(() => {
     if (!data) {
       return null;
     }

-    // remove internal aliases (keys that start with __hdx_)
-    Object.keys(data).forEach(key => {
-      if (key.startsWith('__hdx_')) {
-        delete data[key];
-      }
-    });
-
-    return filterObjectRecursively(data, debouncedFilter);
+    // remove internal aliases (keys that start with __hdx_) without mutating props
+    const cleaned = Object.fromEntries(
+      Object.entries(data).filter(([k]) => !k.startsWith('__hdx_')),
+    );
+    return filterObjectRecursively(cleaned, debouncedFilter);
   }, [data, debouncedFilter]);

206-233: Unsafe SQL interpolation; escape strings and handle JSON columns.

Directly embedding string values can break the query or enable injection if values contain quotes. Also, JSON columns likely need toString(...) for equality.

Apply this diff to build a safe, typed WHERE:

       onClick: () => {
-        let defaultWhere = `${fieldPath} = ${
-          typeof value === 'string' ? `'${value}'` : value
-        }`;
-
-        // FIXME: TOTAL HACK
-        if (fieldPath == 'Timestamp' || fieldPath == 'TimestampTime') {
-          defaultWhere = `${fieldPath} = parseDateTime64BestEffort('${value}', 9)`;
-        }
+        const targetField = isJsonColumn ? `toString(${fieldPath})` : fieldPath;
+        const literal =
+          typeof value === 'string' ? sqlStringLiteral(String(value)) : String(value);
+        let defaultWhere = `${targetField} = ${literal}`;
+        // Timestamp fields: preserve existing behavior but with proper escaping
+        if (
+          (fieldPath === 'Timestamp' || fieldPath === 'TimestampTime') &&
+          typeof value === 'string'
+        ) {
+          defaultWhere = `${fieldPath} = parseDateTime64BestEffort(${sqlStringLiteral(
+            String(value),
+          )}, 9)`;
+        }
         router.push(
           generateSearchUrl({
             where: defaultWhere,
             whereLanguage: 'sql',
           }),
         );
       },

Add this helper somewhere top-level in this file:

function sqlStringLiteral(v: string): string {
  // SQL standard: escape single quotes by doubling them
  return `'${v.replace(/'/g, "''")}'`;
}
packages/app/src/searchFilters.tsx (1)

21-36: Escape values when constructing SQL fragments

Values are interpolated into SQL without escaping, which can break queries (e.g., O'Brien) and risks injection if any value/key is user-controlled. At minimum, escape single quotes before joining.

Apply within this range:

-          condition: `${key} IN (${Array.from(values.included)
-            .map(v => `'${v}'`)
-            .join(', ')})`,
+          condition: `${key} IN (${Array.from(values.included)
+            .map(sqlQuote)
+            .join(', ')})`,
...
-          condition: `${key} NOT IN (${Array.from(values.excluded)
-            .map(v => `'${v}'`)
-            .join(', ')})`,
+          condition: `${key} NOT IN (${Array.from(values.excluded)
+            .map(sqlQuote)
+            .join(', ')})`,

Add once near the top of the file:

const sqlQuote = (v: string) => `'${String(v).replace(/'/g, "''")}'`;
🧹 Nitpick comments (6)
packages/app/src/AutocompleteInput.tsx (4)

284-289: Confirm post-Enter behavior: suggestions won’t reopen while focused.

After setIsInputDropdownOpen(false), suggestions only reopen on blur→focus, not on continued typing. If you want them to reappear when the user resumes typing, consider reopening on change.

Apply this diff to reopen on typing:

-        onChange={e => onChange(e.target.value)}
+        onChange={e => {
+          const next = e.target.value;
+          onChange(next);
+          if (
+            isSearchInputFocused &&
+            !isInputDropdownOpen &&
+            (showSuggestionsOnEmpty || next.trim().length > 0)
+          ) {
+            setIsInputDropdownOpen(true);
+          }
+        }}

284-286: Trim before writing to history to avoid dupes/whitespace entries.

Persisting raw value can create duplicates like "foo" vs "foo " and allow empty strings.

Apply this diff:

-              if (queryHistoryType && value) {
-                setQueryHistory(value);
-              }
+              const trimmed = value?.trim();
+              if (queryHistoryType && trimmed) {
+                setQueryHistory(trimmed);
+              }

274-289: Consider preventing native form submit on Enter to avoid double-submit.

If this input lives inside a <form>, pressing Enter may trigger both the form submit and onSubmit. Guard with preventDefault() where you handle the key.

Apply this diff:

           if (e.key === 'Enter' && e.target instanceof HTMLInputElement) {
             if (
               suggestedProperties.length > 0 &&
               selectedAutocompleteIndex < suggestedProperties.length &&
               selectedAutocompleteIndex >= 0
             ) {
+              e.preventDefault(); // avoid form submit when accepting suggestion
               onAcceptSuggestion(
                 suggestedProperties[selectedAutocompleteIndex].value,
               );
             } else {
+              e.preventDefault(); // avoid native form submit; delegate to onSubmit
               if (queryHistoryType && value) {
-                setQueryHistory(value);
+                setQueryHistory(value);
               }
               setIsInputDropdownOpen(false); // Close suggestion box on Enter
               onSubmit?.();
             }
           }

134-141: onToggle guard vs. explicit close—clarify single source of truth.

onToggle ignores close while focused, but the Enter path explicitly closes the dropdown, bypassing that guard. Consider removing the guard or adding a comment to document why both patterns coexist.

packages/app/src/components/DBRowJsonViewer.tsx (2)

24-51: Filtering alters array structure and ignores non-string primitives.

filterObjectRecursively converts arrays into objects and only matches string values. This can surprise users (e.g., arrays of numbers) and change shapes.

  • Preserve arrays with Array.isArray(value) and filter their elements.
  • Consider matching String(value) for numbers/booleans.

I can provide a drop-in function if you want it.


285-295: Clipboard ops: handle promise rejections.

navigator.clipboard.writeText returns a Promise; failures (permissions/HTTP) should notify the user.

-  window.navigator.clipboard.writeText(
-    JSON.stringify(copiedObj, null, 2),
-  );
-  notifications.show({ color: 'green', message: `Copied object to clipboard` });
+  window.navigator.clipboard
+    .writeText(JSON.stringify(copiedObj, null, 2))
+    .then(() =>
+      notifications.show({ color: 'green', message: 'Copied object to clipboard' }),
+    )
+    .catch(() =>
+      notifications.show({ color: 'red', message: 'Failed to copy to clipboard' }),
+    );
-  window.navigator.clipboard.writeText(
-    typeof value === 'string' ? value : JSON.stringify(value, null, 2),
-  );
-  notifications.show({ color: 'green', message: `Value copied to clipboard` });
+  window.navigator.clipboard
+    .writeText(typeof value === 'string' ? value : JSON.stringify(value, null, 2))
+    .then(() =>
+      notifications.show({ color: 'green', message: 'Value copied to clipboard' }),
+    )
+    .catch(() =>
+      notifications.show({ color: 'red', message: 'Failed to copy to clipboard' }),
+    );

Also applies to: 303-318

📜 Review details

Configuration used: Repository: ShareChat/coderabbit/.coderabbit.yaml

Review profile: CHILL

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.

📥 Commits

Reviewing files that changed from the base of the PR and between bf65fa2 and d1f0bf5.

📒 Files selected for processing (3)
  • packages/app/src/AutocompleteInput.tsx (1 hunks)
  • packages/app/src/components/DBRowJsonViewer.tsx (1 hunks)
  • packages/app/src/searchFilters.tsx (1 hunks)
🔇 Additional comments (3)
packages/app/src/AutocompleteInput.tsx (1)

284-289: Dropdown now closes on Enter — good UX polish.

Closing the suggestions popover before firing onSubmit avoids leftover UI and feels snappier.

packages/app/src/searchFilters.tsx (2)

124-130: Reset-on-empty behavior change: LGTM; confirm UX expectations

Removing the non-empty guard ensures URL-clears sync the in-memory state to {}. This should prevent stale chips when route/query changes. Please verify flows: clearing query params, switching source/connection, and back/forward navigation behave as intended (local selections are reset when URL has no filters).


146-178: EnableMapSet already configured
enableMapSet() is imported and called in packages/app/pages/_app.tsx (line 38), so Set mutations in produce work correctly.

Comment on lines +177 to +204
// if (
// onPropertyAddClick != null &&
// typeof value === 'string' &&
// value &&
// fieldPath != 'Timestamp' &&
// fieldPath != 'TimestampTime'
// ) {
// actions.push({
// key: 'add-to-search',
// label: (
// <>
// <i className="bi bi-funnel-fill me-1" />
// Add to Filters
// </>
// ),
// title: 'Add to Filters',
// onClick: () => {
// onPropertyAddClick(
// isJsonColumn ? `toString(${fieldPath})` : fieldPath,
// value,
// );
// notifications.show({
// color: 'green',
// message: `Added "${fieldPath} = ${value}" to filters`,
// });
// },
// });
// }
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Commented-out “Add to Filters”: remove dead code and related deps.

If the feature is intentionally retired (moved to the new source-type filters), delete the block and drop onPropertyAddClick from the context destructure and dependency array to avoid churn/re-renders and lint noise.

-  const {
-    onPropertyAddClick,
-    generateSearchUrl,
-    generateChartUrl,
-    displayedColumns,
-    toggleColumn,
-  } = useContext(RowSidePanelContext);
+  const { generateSearchUrl, generateChartUrl, displayedColumns, toggleColumn } =
+    useContext(RowSidePanelContext);
-      // Add to Filters action (strings only)
-      // FIXME: TOTAL HACK To disallow adding timestamp to filters
-      // if (
-      //   onPropertyAddClick != null &&
-      //   typeof value === 'string' &&
-      //   value &&
-      //   fieldPath != 'Timestamp' &&
-      //   fieldPath != 'TimestampTime'
-      // ) {
-      //   actions.push({
-      //     key: 'add-to-search',
-      //     label: (
-      //       <>
-      //         <i className="bi bi-funnel-fill me-1" />
-      //         Add to Filters
-      //       </>
-      //     ),
-      //     title: 'Add to Filters',
-      //     onClick: () => {
-      //       onPropertyAddClick(
-      //         isJsonColumn ? `toString(${fieldPath})` : fieldPath,
-      //         value,
-      //       );
-      //       notifications.show({
-      //         color: 'green',
-      //         message: `Added "${fieldPath} = ${value}" to filters`,
-      //       });
-      //     },
-      //   });
-      // }
   }, [
     displayedColumns,
     generateChartUrl,
     generateSearchUrl,
-    onPropertyAddClick,
     rowData,
     toggleColumn,
     jsonColumns,
   ]);

Run to confirm nothing else depends on it:

Also applies to: 132-139, 322-331


🏁 Script executed:

#!/bin/bash
rg -nC2 '\bonPropertyAddClick\b|Add to Filters'

Length of output: 12087


Remove dead “Add to Filters” code and clean up DBRowJsonViewer context

  • Delete the commented-out Add to Filters block (lines 175–193) and drop onPropertyAddClick from the useContext(RowSidePanelContext) destructure (lines 132–139) and effect dependency array (lines 322–331).
  • Update DBRowJsonViewer.test.tsx to remove or adjust the test asserting on “Add to Filters” and calls to onPropertyAddClick.
🤖 Prompt for AI Agents
In packages/app/src/components/DBRowJsonViewer.tsx around lines 175–193 and the
context destructure at ~132–139 and effect deps at ~322–331, remove the entire
commented-out “Add to Filters” block (lines 175–193), delete onPropertyAddClick
from the RowSidePanelContext destructure and from the effect dependency array,
and then update packages/app/src/components/__tests__/DBRowJsonViewer.test.tsx
to remove or adjust any assertions and mock calls related to “Add to Filters” /
onPropertyAddClick so tests no longer reference that prop or UI.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (8)
packages/app/src/components/DBSearchPageFilters.tsx (8)

267-272: Avoid eager “Load more” for every facet (perf/cost).

Auto-triggering on mount fans out N extra requests. Gate by pin state (already passed in as prop).

-useEffect(() => {
-  if (onLoadMore && !hasLoadedMore) {
-    onLoadMore(name);
-  }
-}, [onLoadMore, hasLoadedMore, name]);
+useEffect(() => {
+  if (onLoadMore && !hasLoadedMore && isFieldPinned) {
+    onLoadMore(name);
+  }
+}, [onLoadMore, hasLoadedMore, name, isFieldPinned]);

530-531: Remove unused/expensive useExplainQuery.

It’s solely logged (and logs are being removed). Drop the import and hook to avoid an extra EXPLAIN query.

- import { useExplainQuery } from '@/hooks/useExplainQuery';
-  const { data: countData } = useExplainQuery(chartConfig);

Also applies to: 22-22


623-629: Fix callback deps: remove unused, add facetsChartConfig.

Avoid stale config capture; remove unrelated deps and stable setters.

-          chartConfig: {
-            ...facetsChartConfig,
-            dateRange,
-          },
+          chartConfig: { ...facetsChartConfig, dateRange },
-  }, [chartConfig, setExtraFacets, dateRange, sourceType]);
+  }, [facetsChartConfig, dateRange]);

Also applies to: 647-648


501-548: Strip debug console logging (noise + potential PII leakage).

Large console.error blocks in render path. Remove or gate behind dev-only logger.

-  // Log all component props and variables
-  console.error('🚀 DBSearchPageFiltersComponent - ALL PROPS AND VARIABLES:');
-  console.error('📋 Main Props:');
-  console.error('  - analysisMode:', analysisMode);
-  console.error('  - isLive:', isLive);
-  console.error('  - sourceId:', sourceId);
-  console.error('  - sourceType:', sourceType);
-  console.error('  - showDelta:', showDelta);
-  console.error('  - denoiseResults:', denoiseResults);
-  console.error('  - chartConfig:', chartConfig);
-  console.error('📊 FilterStateHook Props:');
-  console.error('  - filters (filterState):', filterState);
-  console.error('  - clearAllFilters function:', typeof clearAllFilters);
-  console.error('  - clearFilter function:', typeof clearFilter);
-  console.error('  - setFilterValue function:', typeof setFilterValue);
-  console.error('🔧 Hook Results:');
-
   const { toggleFilterPin, toggleFieldPin, isFilterPinned, isFieldPinned } =
     usePinnedFilters(sourceId ?? null);
-
-  console.error('  - toggleFilterPin function:', typeof toggleFilterPin);
-  console.error('  - toggleFieldPin function:', typeof toggleFieldPin);
-  console.error('  - isFilterPinned function:', typeof isFilterPinned);
-  console.error('  - isFieldPinned function:', typeof isFieldPinned);
-
   const { width, startResize } = useResizable(16, 'left');
-  console.error('  - width:', width);
-  console.error('  - startResize function:', typeof startResize);
-
-  const { data: countData } = useExplainQuery(chartConfig);
@@
-  console.error('  - countData:', countData);
-  console.error('  - allFields data:', data);
-  console.error('  - allFields isLoading:', isLoading);
-  console.error('📊 State Variables:');
-
   const [showMoreFields, setShowMoreFields] = useState(false);
-  console.error('  - showMoreFields:', showMoreFields);
-  console.error('  - setShowMoreFields function:', typeof setShowMoreFields);
-  console.error('🚀 END OF COMPONENT PROPS LOGGING 🚀');
-  console.error('');

549-566: Use metadata-derived fieldKeys as fallback when overrides are empty.

Prevents “No filters available” when no override match exists.

-const keysToFetch = useMemo(() => {
-  const overrides = getKeysForSourceType(sourceName, connectionName);
-  return overrides;
-}, [sourceName, connectionName]);
+const keysToFetch = useMemo(() => {
+  const overrides = getKeysForSourceType(sourceName, connectionName);
+  return overrides.length ? overrides : fieldKeys;
+}, [sourceName, connectionName, fieldKeys]);

63-85: Fix bad override entries (trailing space + duplicate key).

  • 'SeverityText ' has a trailing space (won’t match).
  • "ResourceAttributes['label.pod']" is duplicated.
   'US | Logs': [
@@
-    'SeverityText ',
+    'SeverityText',
@@
   'US | Traces': [
@@
-    "ResourceAttributes['label.pod']",
-    "ResourceAttributes['label.pod']",
+    "ResourceAttributes['label.pod']",

606-609: Replace hardcoded limit with passed limit parameter in useGetKeyValues
Hook currently ignores the limit argument and always uses limit: 1000 (useMetadata.tsx:192). Update as follows:

 packages/app/src/hooks/useMetadata.tsx
-              limit: 1000,
+              limit,

113-121: Harden getKeysForSourceType: type, trim/guard, and normalized join.

Prevents undefined/whitespace mismatches and clarifies return type. Also allows a fallback to sourceName-only keys in case connectionName is absent.

-// Helper function to get keys - override for specific types, use default for others
-const getKeysForSourceType = (sourceName?: string, connectionName?: string) => {
-  const newSourceKey = connectionName + ' | ' + sourceName;
-  if (newSourceKey && newSourceKey in serviceMapOverride) {
-    return serviceMapOverride[newSourceKey as keyof typeof serviceMapOverride];
-  }
-  return [];
-};
+// Helper function to get keys - override for specific types, use default for others
+const getKeysForSourceType = (
+  sourceName?: string,
+  connectionName?: string,
+): string[] => {
+  const src = sourceName?.trim();
+  const conn = connectionName?.trim();
+  if (src && conn) {
+    const joined = `${conn} | ${src}`;
+    if (joined in serviceMapOverride) {
+      return serviceMapOverride[joined as keyof typeof serviceMapOverride];
+    }
+  }
+  // Fallback: allow direct sourceName key match, else empty list
+  if (src && src in serviceMapOverride) {
+    return serviceMapOverride[src as keyof typeof serviceMapOverride];
+  }
+  return [];
+};
🧹 Nitpick comments (2)
packages/app/src/components/DBSearchPageFilters.tsx (2)

482-500: Remove unused sourceType prop (not referenced after cleanup).

It’s only present in logs and a stray callback dep. Drop it to avoid prop drift.

   sourceId,
-  sourceType,
   showDelta,
@@
-  sourceType?: string;
@@
   sourceName?: string;
   connectionName?: string;

If other files still pass sourceType, update call sites accordingly.


579-590: Effect dependencies: avoid stringify churn.

JSON.stringify on where/filters risks frequent resets and perf hits. Prefer a stable hash or deep-compare effect.

Example: compute a memoized key once and depend on that.

const whereKey = useMemo(() => JSON.stringify(chartConfig.where), [chartConfig.where]);
const filtersKey = useMemo(() => JSON.stringify(chartConfig.filters), [chartConfig.filters]);

useEffect(() => {
  setExtraFacets({});
}, [chartConfig.from.tableName, chartConfig.from.databaseName, chartConfig.connection, whereKey, filtersKey]);
📜 Review details

Configuration used: Repository: ShareChat/coderabbit/.coderabbit.yaml

Review profile: CHILL

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.

📥 Commits

Reviewing files that changed from the base of the PR and between d1f0bf5 and 2f5a492.

📒 Files selected for processing (2)
  • packages/app/src/components/DBSearchPageFilters.tsx (9 hunks)
  • packages/app/src/hooks/useMetadata.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/app/src/hooks/useMetadata.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
packages/app/src/components/DBSearchPageFilters.tsx (5)
packages/common-utils/src/types.ts (1)
  • ChartConfigWithDateRange (419-419)
packages/app/src/searchFilters.tsx (2)
  • FilterStateHook (217-217)
  • usePinnedFilters (275-349)
packages/app/src/hooks/useExplainQuery.tsx (1)
  • useExplainQuery (8-36)
packages/app/src/hooks/useMetadata.tsx (2)
  • useAllFields (83-125)
  • useGetKeyValues (155-203)
packages/app/src/utils.ts (1)
  • mergePath (649-655)
🔇 Additional comments (4)
packages/app/src/components/DBSearchPageFilters.tsx (4)

156-158: LGTM: noop handler to prevent double fire.

Clear intent and avoids duplicate onChange triggers.


338-338: Good catch: include isPinned in deps.

Prevents stale closures in sorting logic.


593-601: LGTM: facetsChartConfig removes orderBy.

Keeps value distribution unbiased for facet lookup.


650-691: LGTM: facet merging and pin-first ordering.

Merges “load more” values safely and prioritizes pinned fields.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/app/src/components/ContextSidePanel.tsx (3)

83-90: Fix Prettier violations and make Pod default more robust.

  • Prettier: remove trailing spaces (Line 83, Line 86) and add the trailing comma Prettier expects (Line 89).
  • Optional: if rowData can change without a remount, sync the default when a pod becomes available.

Apply:

-  const [contextBy, setContextBy] = useState<ContextBy>(
-    k8sPodName ? ContextBy.Pod : ContextBy.All
-  );
+  const [contextBy, setContextBy] = useState<ContextBy>(
+    k8sPodName ? ContextBy.Pod : ContextBy.All,
+  );

If rowData can change, consider:

// keep defaulting to Pod when available and user hasn't chosen something else
useEffect(() => {
  if (k8sPodName && contextBy === ContextBy.All) setContextBy(ContextBy.Pod);
}, [k8sPodName, contextBy]);

151-179: Prefer scoping by Pod UID (fallback to name) and guard undefined values.

Using k8s.pod.uid avoids ambiguity across restarts/rollouts; use it when present and fall back to name. Also ensure we never build a clause with an empty value.

Apply:

@@
-        [ContextBy.Pod]: {
-          field: 'k8s.pod.name',
-          value: k8sPodName,
-        },
+        [ContextBy.Pod]: {
+          field: k8sPodUid ? 'k8s.pod.uid' : 'k8s.pod.name',
+          value: k8sPodUid ?? k8sPodName,
+        },
@@
-    [k8sNodeName, k8sPodName, host, service, debouncedWhere],
+    [k8sNodeName, k8sPodName, k8sPodUid, host, service, debouncedWhere],

And (paired change above in the earlier block):

-  // Extract pod name to determine initial context
-  const k8sPodName = rowData.ResourceAttributes?.['k8s.pod.name'];
+  // Extract pod identifiers to determine initial context
+  const k8sPodName = rowData.ResourceAttributes?.['k8s.pod.name'];
+  const k8sPodUid = rowData.ResourceAttributes?.['k8s.pod.uid'];

Additionally, update the SegmentedControl data builder to show “Pod” if either UID or name exists:

...(k8sPodUid || k8sPodName ? [{ label: 'Pod', value: ContextBy.Pod }] : []),

Finally, inside getWhereClause, consider short‑circuiting when !mapping.value to avoid producing invalid clauses.


148-149: Comment clarity.

If you adopt the UID fallback, update this comment to reflect both k8sPodUid and k8sPodName extraction.

📜 Review details

Configuration used: Repository: ShareChat/coderabbit/.coderabbit.yaml

Review profile: CHILL

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 2f5a492 and 5a85c23.

📒 Files selected for processing (1)
  • packages/app/src/components/ContextSidePanel.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/app/src/components/ContextSidePanel.tsx (1)
packages/app/src/components/DBInfraPanel.tsx (1)
  • rowData (206-277)
🪛 ESLint
packages/app/src/components/ContextSidePanel.tsx

[error] 83-83: Delete ··

(prettier/prettier)


[error] 86-86: Delete ··

(prettier/prettier)


[error] 89-89: Insert ,

(prettier/prettier)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/app/src/hooks/useOffsetPaginatedQuery.tsx (1)

288-307: Fix next-offset calculation to avoid duplicates when maxPages trims earlier pages.

Using flattenPages(allPages).length under maxPages can undercount and cause overlapping pages. Advance by lastPageParam + rows returned.

-    getNextPageParam: (lastPage, allPages) => {
+    getNextPageParam: (lastPage, allPages, lastPageParam) => {
       if (lastPage == null) {
         return undefined;
       }
 
       const len = lastPage.data.length;
       if (len === 0) {
         return undefined;
       }
 
-      // If we got less data than the page size, we've reached the end
+      // If we got less data than the page size, we've reached the end
       if (len < pageSize) {
         return undefined;
       }
 
-      const data = flattenPages(allPages);
-
-      // Next offset is the total number of records fetched so far
-      return data.length;
+      // Next offset advances by the rows we just fetched
+      return (lastPageParam ?? 0) + len;
     },
🧹 Nitpick comments (4)
packages/app/src/hooks/useOffsetPaginatedQuery.tsx (4)

46-46: Strongly type meta instead of using any; guard pageSize.

Casting meta to any hides errors and risks undefined/NaN pageSize flowing into limits.

Apply this diff:

-  const pageSize = (meta as any).pageSize as number;
+  const { pageSize } = meta as QueryMeta;

Add this type near the imports (outside the shown range):

type QueryMeta = {
  queryClient: QueryClient;
  hasPreviousQueries: boolean;
  pageSize: number;
};

57-59: Clamp pageSize before injecting into SQL limit.

Prevents accidental 0/negative/float values.

-        limit: pageSize,
+        // Ensure a sane integer limit
+        limit: Math.max(1, Math.floor(pageSize)),

248-249: Name the default as a constant for discoverability.

Minor ergonomics; makes the magic number self-documenting.

-    pageSize = 3400,
+    pageSize = DEFAULT_PAGE_SIZE,

Add near the top (outside this hunk):

const DEFAULT_PAGE_SIZE = 3400 as const;

72-75: Use the existing HDX_METADATA_MAX_ROWS_TO_READ constant instead of a hard-coded value

Replace the string literal in clickhouse_settings with the imported constant, converting it to a string for consistency with other usages:

-      clickhouse_settings: {
-        max_rows_to_read: '91000000',
-      },
+      clickhouse_settings: {
+        max_rows_to_read: HDX_METADATA_MAX_ROWS_TO_READ.toString(),
+      },

And at the top of the file add:

import { HDX_METADATA_MAX_ROWS_TO_READ } from '@/config';
📜 Review details

Configuration used: Repository: ShareChat/coderabbit/.coderabbit.yaml

Review profile: CHILL

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb3a76 and 55b1c92.

📒 Files selected for processing (1)
  • packages/app/src/hooks/useOffsetPaginatedQuery.tsx (5 hunks)
🔇 Additional comments (3)
packages/app/src/hooks/useOffsetPaginatedQuery.tsx (3)

298-301: End-of-data guard looks good.

Stopping when last page < pageSize is correct; with the offset fix above this will avoid an extra empty fetch.


312-313: Meta propagation LGTM.

Passing pageSize and hasPreviousQueries via meta cleanly connects options to queryFn.


253-254: Public API change: added pageSize parameter (default 3400) to useOffsetPaginatedQuery—verify call sites.

Review all hook invocations (e.g. in DBTableChart.tsx, DBRowTable.tsx, DBTraceWaterfallChart.tsx) to ensure callers don’t rely on the previous implicit server-side limits, and update the hook’s documentation to state the new default page size.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/app/src/hooks/useOffsetPaginatedQuery.tsx (1)

249-249: Consider the impact of the default page size on performance.

The default HDX_DEFAULT_PAGE_SIZE of 3,400 rows per page is relatively large. This could impact initial load times and memory usage, especially for users on slower connections or devices.

Consider documenting the rationale for this default value and ensure it's appropriate for your typical use cases. You might want to make this configurable based on the type of query or user preferences.

📜 Review details

Configuration used: Repository: ShareChat/coderabbit/.coderabbit.yaml

Review profile: CHILL

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 55b1c92 and a473009.

📒 Files selected for processing (2)
  • packages/app/src/config.ts (1 hunks)
  • packages/app/src/hooks/useOffsetPaginatedQuery.tsx (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/app/src/config.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/app/src/hooks/useOffsetPaginatedQuery.tsx (1)
packages/app/src/config.ts (2)
  • HDX_METADATA_MAX_ROWS_TO_READ (15-17)
  • HDX_DEFAULT_PAGE_SIZE (18-20)
🪛 ESLint
packages/app/src/hooks/useOffsetPaginatedQuery.tsx

[error] 1-21: Run autofix to sort these imports!

(simple-import-sort/imports)

🔇 Additional comments (3)
packages/app/src/hooks/useOffsetPaginatedQuery.tsx (3)

305-307: LGTM! Offset calculation is correct.

The offset calculation using data.length after flattening all pages correctly represents the total number of records fetched, which aligns well with the new pagination termination logic.


73-76: Verify ClickHouse max_rows_to_read override. This hook now sets max_rows_to_read to HDX_METADATA_MAX_ROWS_TO_READ (~31 M), overriding the client-lib default of 10 M (common-utils/src/clickhouse.ts:468) and the metadata default of 3 M (common-utils/src/metadata.ts:287). Confirm your ClickHouse servers support this limit without degrading performance or exceeding resource quotas.


299-303: Add pagination edge-case tests for useOffsetPaginatedQuery
No existing tests cover when total rows % pageSize === 0; verify that the if (len < pageSize) termination correctly handles both “less than” and “exactly divisible” scenarios.

throw new Error('Query missing client meta');
}
const queryClient = meta.queryClient as QueryClient;
const pageSize = (meta as any).pageSize as number;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add type safety for pageSize extraction from meta.

The current type assertion (meta as any).pageSize as number bypasses TypeScript's type system. Consider adding proper typing to the meta object to ensure type safety.

Consider extending the meta type properly instead of using any:

-  const pageSize = (meta as any).pageSize as number;
+  const pageSize = (meta as { queryClient: QueryClient; hasPreviousQueries: boolean; pageSize: number }).pageSize;

Alternatively, create a custom type for the meta object that can be reused across the file.

📝 Committable suggestion

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

Suggested change
const pageSize = (meta as any).pageSize as number;
const pageSize = (meta as { queryClient: QueryClient; hasPreviousQueries: boolean; pageSize: number }).pageSize;
🤖 Prompt for AI Agents
In packages/app/src/hooks/useOffsetPaginatedQuery.tsx around line 47, the code
unsafely extracts pageSize using (meta as any).pageSize as number; replace this
with a proper meta type: declare an interface (e.g. PaginatedMeta { pageSize?:
number; total?: number; cursor?: string; }) and use it in the query/useQuery
generics or local variable typing so meta is typed as PaginatedMeta; then access
pageSize via const pageSize = (meta as PaginatedMeta).pageSize ??
DEFAULT_PAGE_SIZE and add a runtime check to ensure it's a number (e.g. typeof
pageSize === 'number' ? pageSize : DEFAULT_PAGE_SIZE). Make the interface
reusable by exporting it from the hook file (or a shared types file) so other
code can use the same typed meta.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/app/src/hooks/useOffsetPaginatedQuery.tsx (2)

46-59: Type meta and validate pageSize; avoid any and NaN/≤0 values.

(meta as any).pageSize risks NaN/negative values leading to runaway pagination. Use a typed meta and derive an effectivePageSize; also use it in the query limit.

-  const queryClient = meta.queryClient as QueryClient;
-  const pageSize = (meta as any).pageSize as number;
+  const { queryClient, pageSize: metaPageSize } = meta as {
+    queryClient: QueryClient;
+    hasPreviousQueries: boolean;
+    pageSize?: number;
+  };
+  const mps = typeof metaPageSize === 'number' ? metaPageSize : NaN;
+  const effectivePageSize =
+    Number.isFinite(mps) && mps > 0 ? Math.floor(mps) : HDX_DEFAULT_PAGE_SIZE;
@@
-      limit: {
-        limit: pageSize,
+      limit: {
+        limit: effectivePageSize,
         offset: pageParam,
       },

243-255: Add pageSize to the query key and sanitize its value.

  • Update queryKeyFn to accept (prefix, config, pageSize) and include pageSize in the returned tuple.
  • In useOffsetPaginatedQuery, compute
    const safeDefaultPageSize =
      Number.isFinite(HDX_DEFAULT_PAGE_SIZE) && HDX_DEFAULT_PAGE_SIZE > 0
        ? Math.floor(HDX_DEFAULT_PAGE_SIZE)
        : /* fallback value */;
    const effectivePageSize =
      Number.isFinite(pageSize) && pageSize > 0
        ? Math.floor(pageSize)
        : safeDefaultPageSize;
    then call queryKeyFn(queryKeyPrefix, config, effectivePageSize).
  • Update all affected generics from [string, ChartConfigWithDateRange] to [string, ChartConfigWithDateRange, number].
♻️ Duplicate comments (1)
packages/app/src/hooks/useOffsetPaginatedQuery.tsx (1)

21-21: Fix import sorting (ESLint: simple-import-sort/imports).

Keep alias imports sorted; move @/config above @/metadata and @/utils.

 import { getClickhouseClient } from '@/clickhouse';
-import { getMetadata } from '@/metadata';
-import { omit } from '@/utils';
-import { HDX_METADATA_MAX_ROWS_TO_READ, HDX_DEFAULT_PAGE_SIZE } from '@/config';
+import { HDX_DEFAULT_PAGE_SIZE, HDX_METADATA_MAX_ROWS_TO_READ } from '@/config';
+import { getMetadata } from '@/metadata';
+import { omit } from '@/utils';
🧹 Nitpick comments (1)
packages/app/src/hooks/useOffsetPaginatedQuery.tsx (1)

73-75: Guard ClickHouse setting from invalid env (NaN/≤0).

If HDX_METADATA_MAX_ROWS_TO_READ becomes NaN due to a bad env, ClickHouse may reject the query. Prefer validating at definition time in config.ts; keep String(...) here.

Proposed hardening in packages/app/src/config.ts (additional context, not in this file):

-export const HDX_METADATA_MAX_ROWS_TO_READ = Number(
-  env('NEXT_PUBLIC_HDX_METADATA_MAX_ROWS_TO_READ') ?? '31000000',
-);
+export const HDX_METADATA_MAX_ROWS_TO_READ = (() => {
+  const raw = env('NEXT_PUBLIC_HDX_METADATA_MAX_ROWS_TO_READ');
+  const n = Number(raw ?? '31000000');
+  return Number.isFinite(n) && n > 0 ? n : 31_000_000;
+})();
📜 Review details

Configuration used: Repository: ShareChat/coderabbit/.coderabbit.yaml

Review profile: CHILL

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.

📥 Commits

Reviewing files that changed from the base of the PR and between a473009 and c53d7ef.

📒 Files selected for processing (1)
  • packages/app/src/hooks/useOffsetPaginatedQuery.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/app/src/hooks/useOffsetPaginatedQuery.tsx (1)
packages/app/src/config.ts (2)
  • HDX_METADATA_MAX_ROWS_TO_READ (15-17)
  • HDX_DEFAULT_PAGE_SIZE (18-20)
🪛 ESLint
packages/app/src/hooks/useOffsetPaginatedQuery.tsx

[error] 1-21: Run autofix to sort these imports!

(simple-import-sort/imports)

Comment on lines +299 to 307
// If we got less data than the page size, we've reached the end
if (len < pageSize) {
return undefined;
}

const data = flattenPages(allPages);

// TODO: Need to configure overlap and account for granularity
// Next offset is the total number of records fetched so far
return data.length;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use effectivePageSize for end-of-data detection and offsets.

Keep EoD logic consistent with the sanitized size.

-      // If we got less data than the page size, we've reached the end
-      if (len < pageSize) {
+      // If we got less data than the page size, we've reached the end
+      if (len < effectivePageSize) {
         return undefined;
       }
@@
-      // Next offset is the total number of records fetched so far
+      // Next offset is the total number of records fetched so far
       return data.length;
📝 Committable suggestion

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

Suggested change
// If we got less data than the page size, we've reached the end
if (len < pageSize) {
return undefined;
}
const data = flattenPages(allPages);
// TODO: Need to configure overlap and account for granularity
// Next offset is the total number of records fetched so far
return data.length;
// If we got less data than the page size, we've reached the end
if (len < effectivePageSize) {
return undefined;
}
const data = flattenPages(allPages);
// Next offset is the total number of records fetched so far
return data.length;
🤖 Prompt for AI Agents
In packages/app/src/hooks/useOffsetPaginatedQuery.tsx around lines 299 to 307,
the end-of-data detection and next-offset calculation use the raw pageSize and
data.length instead of the sanitized effectivePageSize and the flattened pages'
sanitized length; change the conditional to compare len (or flattened length)
against effectivePageSize to decide EoD and compute the next offset using the
total number of records counted with the same sanitization (effectivePageSize)
so both EoD detection and returned offset are consistent with the sanitized page
size.

Comment on lines +313 to 314
pageSize,
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Propagate effectivePageSize via meta.

Align meta with the sanitized value used in the query.

     meta: {
       queryClient,
       hasPreviousQueries,
-      pageSize,
+      pageSize: effectivePageSize,
     },
📝 Committable suggestion

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

Suggested change
pageSize,
},
meta: {
queryClient,
hasPreviousQueries,
pageSize: effectivePageSize,
},
🤖 Prompt for AI Agents
In packages/app/src/hooks/useOffsetPaginatedQuery.tsx around lines 313-314, the
meta returned from the hook currently reflects the original pageSize instead of
the sanitized value used in the query; update the meta to include (or replace
with) effectivePageSize set to the sanitized value the query actually uses (the
computed/sanitized pageSize variable) so consumers see the true page size used
for fetching.

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

Successfully merging this pull request may close these issues.

1 participant