Skip to content

Conversation

@adityathebe
Copy link
Member

@adityathebe adityathebe commented Feb 5, 2026

Summary by CodeRabbit

  • New Features

    • Namespaced URL query params (paramPrefix) for per-section/filter isolation
    • New preset-driven filter UIs and dedicated UI sections for Changes and Configs
    • Interactive filter badges, two-way URL sync, and per-section table/filter behavior
    • Utilities to normalize/translate backend filter formats for the UI
  • Bug Fixes

    • Adjusted React Query test cache configuration
  • Tests

    • Added comprehensive filter-translation unit tests

@vercel
Copy link

vercel bot commented Feb 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
aws-preview Ready Ready Preview Feb 12, 2026 9:32pm
flanksource-ui Ready Ready Preview Feb 12, 2026 9:32pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 2026

Warning

Rate limit exceeded

@adityathebe has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 5 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

Adds a namespaced URL search-params mechanism (paramPrefix) and threads it through hooks, tables, filters, time-range, and pagination; introduces filter translation utilities and new UI-driven sections (Configs/Changes) that initialize and merge preset filters into prefixed params.

Changes

Cohort / File(s) Summary
Prefixed search params hook
src/hooks/usePrefixedSearchParams.ts
New usePrefixedSearchParams(prefix?, useGlobalParams?) API returning [URLSearchParams, setter] that scopes keys by prefix, preserves optional global params, and provides a functional updater signature.
Query hooks
src/api/query-hooks/useAllConfigsQuery.ts, src/api/query-hooks/useConfigChangesHooks.ts
Expose and propagate paramPrefix into time-range, sort and pagination hooks; default sortBy/sortOrder applied and pagination state initialized with { paramPrefix }.
Table & column plumbing
src/components/Configs/ConfigList/ConfigsTable.tsx, src/components/Configs/ConfigList/MRTConfigListColumn.tsx, src/components/Configs/ConfigList/.../MRTConfigListTagsCell.tsx, src/components/Configs/ConfigList/.../ConfigListTagsCell.tsx
Components accept paramPrefix; mrtConfigListColumns changed to a function (paramPrefix?); tag cells use usePrefixedSearchParams and functional updaters for URL params.
Config change table & filters
src/components/Configs/Changes/ConfigChangeTable.tsx, src/components/Configs/Changes/ConfigChangesFilters/ConfigChangesDateRangeFIlter.tsx
Add paramPrefix prop and forward it into column generators and useTimeRangeParams, enabling per-instance param scoping.
Generic filter/form utilities
src/components/Forms/FormikFilterForm.tsx, src/ui/DataTable/FilterByCellValue.tsx, src/ui/Dates/TimeRangePicker/useTimeRangeParams.tsx, src/ui/Tags/TagsFilterCell.tsx
Switch to usePrefixedSearchParams, accept paramPrefix, and refactor param mutations to functional setParams updaters with scoped key handling and init-from-defaults.
Config group & group-by UI
src/components/Configs/ConfigsListFilters/ConfigGroupByDropdown.tsx
Adds paramPrefix; replaces direct params mutation with functional updater and deterministic params reset logic.
New UI sections & filter components
src/pages/views/components/ChangesUISection.tsx, src/pages/views/components/ConfigsUISection.tsx, src/pages/views/components/ChangesUIFilters.tsx, src/pages/views/components/ConfigsUIFilters.tsx
New components that translate preset filters, merge presets into prefixed params on init, and render prefixed filter UIs and tables (Configs/Changes).
ViewSection & view wiring
src/pages/views/components/ViewSection.tsx, src/pages/views/components/ViewWithSections.tsx, src/pages/views/hooks/useViewData.ts
Support uiRef vs viewRef, unified header/content rendering with stable IDs, safer section key generation, and exclude native UI sections from viewRef aggregation.
Filter translation utilities + tests
src/pages/views/utils/filterTranslation.ts, src/pages/views/utils/__tests__/filterTranslation.test.ts
New translators (translateTristate, translateTags, translateChangesFilters, translateConfigsFilters) and comprehensive tests for translation logic.
Arbitrary filters & hooks
src/hooks/useConfigChangesArbitraryFilters.tsx, src/api/query-hooks/useConfigChangesHooks.ts
Hook signatures accept optional paramPrefix and read/wire prefixed params for arbitrary filters; prefix propagated to related hooks.
Misc types, connections, tests
src/pages/audit-report/types/index.ts, src/components/Connections/ConnectionFormModal.tsx, src/components/Connections/__tests__/ConnectionsList.unit.test.tsx, src/components/Configs/Sidebar/__tests__/ConfigDetails.unit.test.tsx
Added UI filter types (UIRef, ChangesUIFilters, ConfigsUIFilters), added connection metadata fields, updated tests (enum usage, React Query cacheTime rename).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User as User
  participant UI as Filter UI / FormikFilterForm
  participant Params as usePrefixedSearchParams
  participant Query as useAllConfigsQuery / useGetAllConfigsChangesQuery
  participant API as Server/API

  User->>UI: change filters / remove badge
  UI->>Params: setParams(updater with prefixed keys)
  Params-->>UI: updated prefixed URLSearchParams
  UI->>Query: reads prefixed params -> triggers refetch
  Query->>API: fetch with composed params
  API-->>Query: response
  Query-->>UI: data (loading false)
  UI-->>User: render updated table/results
Loading

Possibly related PRs

Suggested Reviewers

  • moshloop
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'View UI refs' is vague and does not clearly convey the main purpose of the changes, which involve implementing parameter-prefixed filtering, URL state management, and new UI filter components for views. Consider a more descriptive title such as 'Add parameter-scoped UI filters for views' or 'Implement parameterized filtering for config and changes UI sections' to better reflect the scope and intent of the changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch view-ui-refs

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

❤️ Share

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

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: 8

🤖 Fix all issues with AI agents
In `@src/components/Configs/ConfigList/Cells/ConfigListTagsCell.tsx`:
- Around line 55-79: The code reads current tags from the hard-coded "tags" key
causing custom keys to be ignored; update the logic in the setParams callback
(where setParams, nextParams, currentTags/currentTagsArray, updatedValue, and
filterByTagParamKey are used) to read using nextParams.get(filterByTagParamKey)
instead of nextParams.get("tags"), ensuring both read and write use
filterByTagParamKey so existing values under a custom param key are preserved
and de-duplicated the same way.

In `@src/components/Configs/ConfigsListFilters/ConfigGroupByDropdown.tsx`:
- Around line 112-125: The forEach callback in groupByChange currently uses an
expression body that implicitly returns the result of nextParams.delete, causing
a Biome lint warning; change the paramsToReset.forEach((param) =>
nextParams.delete(param)) to use a block body with braces and an explicit
statement (e.g., paramsToReset.forEach((param) => { nextParams.delete(param);
})) so the function no longer has an implicit return while keeping the same
behavior in groupByChange and still updating nextParams.

In `@src/hooks/usePrefixedSearchParams.ts`:
- Around line 67-87: The bug is that when useGlobalParams is true you always
write updated entries with the prefix (creating duplicate prefixed copies of
global params); fix by when useGlobalParams is true delete any prefixed variants
up front (use prefixWithSeparator and nextParams to remove keys starting with
`${prefix}__`), then when applying updatedFiltered (from filterPrefixedParams
and updater) write keys that should be global without the prefix (set key
directly on nextParams) and ensure that if the updater removed a global key you
delete its unprefixed form from nextParams so globals are not left stale.

In `@src/pages/views/components/ChangesUISection.tsx`:
- Around line 69-87: The effect that merges presetParams into the URL is gated
by isInitialized so later changes to presetParams (from new filters) don't
reapply defaults; add a new useEffect that watches presetParams and calls
setIsInitialized(false) (or otherwise resets the initialization flag) so the
merge effect runs again and setSearchParams is reapplied with the new
presetParams; ensure the new effect depends only on presetParams and uses the
same setIsInitialized function.

In `@src/pages/views/components/ViewSection.tsx`:
- Around line 43-44: Remove the contradictory non-null assertion on name:
replace the expression using section.viewRef?.name! with a plain optional result
(e.g., const name = section.viewRef?.name) and let the existing guard (enabled:
isViewRefSection && !!name in the query) handle undefined names; ensure any
downstream uses of name accept string | undefined or are already protected by
isViewRefSection/!!name (symbols: section.viewRef, name, namespace,
isViewRefSection, enabled: isViewRefSection && !!name).

In `@src/pages/views/components/ViewWithSections.tsx`:
- Around line 69-81: The generated React keys for sections (in
viewResult.sections.map) can collide when multiple sections share the same
section.title; update the key creation logic for sectionKey so it guarantees
uniqueness (e.g., detect duplicate keys or always append the map index or a
section-specific unique id if present) and use that unique value as the key on
the ViewSection wrapper to prevent React mis-diffing; adjust the code around the
sectionKey computation inside the map iterator that returns the <div
key={sectionKey}> and ensure any existing references to section.viewRef or
section.uiRef are preserved.

In `@src/ui/DataTable/FilterByCellValue.tsx`:
- Around line 30-54: The forEach callback passed to paramsToReset currently uses
an expression body which causes an implicit return from nextParams.delete(...)
and triggers the Biome useIterableCallbackReturn lint; update the callback in
the setParams block (the paramsToReset.forEach call) to use a block body (e.g.,
paramsToReset.forEach((param) => { nextParams.delete(param); });) or replace it
with an explicit loop, ensuring you reference nextParams, paramsToReset, and
paramKey in the same setParams closure.

In `@src/ui/Dates/TimeRangePicker/useTimeRangeParams.tsx`:
- Around line 28-49: The current useEffect in useTimeRangeParams uses
Object.entries(defaults) which breaks when defaults is a URLSearchParamsInit
other than a plain object; normalize defaults by creating new
URLSearchParams(defaults) and iterate with its .forEach((value, key) => { ... })
to set missing keys on nextParams so all URLSearchParamsInit types (string,
URLSearchParams, iterable, object) are handled; update the loop that currently
references Object.entries(defaults) inside the setParams callback to use this
normalized URLSearchParams and keep the same changed logic and nextParams.set
behavior.
🧹 Nitpick comments (4)
src/pages/views/components/ViewSection.tsx (1)

111-121: Consider sanitizing section.title in paramPrefix.

Using section.title directly in paramPrefix (e.g., changes_${section.title}) could produce URL parameter keys with spaces or special characters if titles contain them. While this may work, it could cause unexpected behavior with URL encoding.

Optional: sanitize title for paramPrefix
+const sanitizeForParam = (str: string) => str.replace(/[^a-zA-Z0-9_-]/g, '_');
+
 {section.uiRef?.changes && (
   <ChangesUISection
     filters={section.uiRef.changes}
-    paramPrefix={`changes_${section.title}`}
+    paramPrefix={`changes_${sanitizeForParam(section.title)}`}
   />
 )}
 {section.uiRef?.configs && (
   <ConfigsUISection
     filters={section.uiRef.configs}
-    paramPrefix={`configs_${section.title}`}
+    paramPrefix={`configs_${sanitizeForParam(section.title)}`}
   />
 )}
src/pages/views/components/ConfigsUISection.tsx (1)

78-81: Consider if cacheTime: 0 is intentional.

Setting cacheTime: 0 disables React Query's caching entirely, meaning every navigation or re-mount triggers a fresh fetch. If real-time data is required, this is appropriate. Otherwise, consider allowing some caching for better performance.

src/pages/views/components/ChangesUIFilters.tsx (1)

134-139: Use a unique key per filter entry.

key={value} can collide across different param keys. Compose a stable key to avoid React list warnings.

🔧 Suggested fix
-          <FilterBadge
-            filters={value}
-            key={value}
-            paramKey={key}
-            paramPrefix={paramPrefix}
-          />
+          <FilterBadge
+            filters={value}
+            key={`${key}:${value}`}
+            paramKey={key}
+            paramPrefix={paramPrefix}
+          />
src/pages/views/utils/filterTranslation.ts (1)

54-55: Return undefined for empty tristate output.

Keeps behavior consistent with translateTags and avoids propagating empty strings.

🔧 Suggested fix
-  return result.join(",");
+  return result.length > 0 ? result.join(",") : undefined;

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: 4

🤖 Fix all issues with AI agents
In `@src/pages/views/components/ViewSection.tsx`:
- Around line 80-90: The header's aria-controls in renderHeader references
sectionId but the error container is using id={`${sectionId}-error`}, breaking
the aria relationship; update the error container element (the element that
currently sets id={`${sectionId}-error`} near the error rendering logic) to use
id={sectionId} so it matches aria-controls (alternatively, change aria-controls
to `${sectionId}-error`, but prefer making the error element id={sectionId} to
match renderHeader and preserve the existing sectionId variable).
- Around line 42-55: The namespace defaulting is inconsistent: change how
namespace is derived so that the variable named namespace (used in the useQuery
queryKey and passed to getViewDataByNamespace) falls back to the same "default"
value as the sectionId logic instead of an empty string; update the declaration
that sets namespace = section.viewRef?.namespace to use "default" when
undefined, ensuring useQuery (queryKey ["view-result", namespace, name,
currentViewVariables]) and the queryFn calling getViewDataByNamespace(namespace,
name, currentViewVariables) receive a valid namespace; keep the enabled
condition (isViewRefSection && !!name) unchanged.
- Around line 66-78: The sectionId and paramPrefix are built from raw
section.title and can contain spaces/punctuation; use the existing toSafeId
utility (or implement a local slugifier) to sanitize values before constructing
sectionId and paramPrefix so IDs and URL param prefixes are valid and unique;
specifically, wrap section.title (used in the ternaries that produce
`section-changes-${section.title}`, `section-configs-${section.title}`, and the
default `section-${section.title}`) with toSafeId when computing sectionId, and
similarly sanitize the strings used to build paramPrefix (the variables
referenced as paramPrefix around the props/controls) to prevent invalid DOM
id/aria-controls and collisions. Ensure you import/extract toSafeId from the
TimeseriesPanel module or add a small local implementation and update all
occurrences consistently.

In `@src/ui/Dates/TimeRangePicker/useTimeRangeParams.tsx`:
- Line 83: The forEach callback in useTimeRangeParams
(paramsToReset.forEach((param) => nextParams.delete(param))) uses an implicit
return which triggers the useIterableCallbackReturn lint error; change the arrow
function to a block body so it does not implicitly return (i.e., replace the
concise arrow callback with a block callback that calls nextParams.delete(param)
without returning its result) to satisfy the Biome lint.
🧹 Nitpick comments (2)
src/hooks/usePrefixedSearchParams.ts (1)

90-99: Consider handling explicit deletions from the updater.

When the updater explicitly deletes a key (rather than setting it to empty), the current logic won't remove the corresponding prefixed key from nextParams since the key won't appear in updatedFiltered.entries(). The prefixed keys are already cleared on lines 70-74, so this works for prefixed params. However, if useGlobalParams is true and the updater deletes a global key, that key won't be removed since it's only deleted if it appears with an empty value.

This is a minor edge case that may not affect current usage but could cause confusion if someone expects updater deletions to propagate for global keys.

src/pages/views/components/ViewSection.tsx (1)

11-12: Guard against sections declaring both uiRef and viewRef.

UIRef wiring looks good, but if both refs are present the viewRef path is silently ignored. Consider surfacing a configuration error to avoid ambiguous rendering.

🔧 Suggested guard
 const isUIRefSection = !!section.uiRef;
 const isViewRefSection = !!section.viewRef;

+ if (isUIRefSection && isViewRefSection) {
+   return (
+     <ErrorViewer
+       error="Invalid section: cannot set both viewRef and uiRef"
+       className="max-w-3xl"
+     />
+   );
+ }

Also applies to: 27-29

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

🤖 Fix all issues with AI agents
In `@src/ui/Dates/TimeRangePicker/useTimeRangeParams.tsx`:
- Around line 37-43: The code building defaultsParams misinterprets a
URLSearchParamsInit that is an array of tuples (ReadonlyArray<[string,string]>);
update the logic in useTimeRangeParams around the defaults -> defaultsParams
creation to explicitly detect when defaults is an array of key/value tuples
(Array.isArray(defaults) && defaults.every(e => Array.isArray(e) && e.length ===
2 && typeof e[0] === "string")) and pass that array directly to new
URLSearchParams, otherwise keep the existing handling for strings,
URLSearchParams instances, or object entries (and for object entries continue
flattening arrays of values as before); refer to the defaults variable and
defaultsParams construction in useTimeRangeParams.tsx and ensure
URLSearchParamsInit tuple arrays are handled before the Object.entries branch.
🧹 Nitpick comments (3)
src/ui/Dates/TimeRangePicker/useTimeRangeParams.tsx (3)

28-57: Consider documenting memoization requirement for defaults.

The useEffect dependency on defaults will cause repeated effect executions if callers pass inline object literals (e.g., useTimeRangeParams({ range: "1d" })). While the changed check prevents unnecessary URL updates, the effect body still runs each render. Consider adding a JSDoc note advising callers to memoize the defaults object or use a stable reference.

📝 Suggested documentation update
 /**
  *
  * useTimeRangeParams
  *
  * This hook is used to manage the time range parameters in the URL. It provides
  * a way to set the time range parameters and get the time range as an absolute
  * range, which can be consumed directly. It uses the useSearchParams hook from
  * react-router-dom to manage the URL parameters.
  *
+ * `@param` defaults - Initial URL params to apply if not already present. Should be
+ *   memoized (useMemo) or defined outside the component to avoid repeated effect runs.
+ * `@param` paramPrefix - Optional prefix for namespacing URL parameters.
  */

106-114: Redundant optional chaining after null check.

On line 112, range?.toString()! uses optional chaining, but the if (range) check on line 108 already ensures range is truthy. The ?. and ! are both redundant here.

✨ Suggested simplification
       if (rangeType === "relative") {
         const range = params.get("range");
         if (range) {
           return {
             type: rangeType,
             display: display as string,
-            range: range?.toString()!
+            range: range
           } satisfies TimeRangeOption;
         }
       }

154-162: Inconsistent error handling for invalid mapped time range.

timeRangeValue (line 160) logs to console and implicitly returns undefined, while timeRangeAbsoluteValue (line 207) throws an error for the same condition. This asymmetry could cause unexpected behavior if callers rely on one but not the other.

If intentional (e.g., timeRangeValue is for display and can be lenient), consider adding a comment. Otherwise, align the error handling approach.

Also applies to: 197-209

@moshloop
Copy link
Member

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

🤖 Fix all issues with AI agents
In `@src/api/query-hooks/useConfigChangesHooks.ts`:
- Around line 39-48: The function useGetAllConfigsChangesQuery currently loses
its default flags when callers pass an object with paramPrefix because the
parameter default only applies when no argument is provided; fix by merging
explicit defaults with the provided options: ensure useGetAllConfigsChangesQuery
accepts an options object (or default to {}), then combine defaultQueryOptions =
{ enabled: true, keepPreviousData: true } with the incoming options before
extracting paramPrefix and queryOptions so that queryOptions always contains
enabled and keepPreviousData unless explicitly overridden; update references to
paramPrefix and queryOptions accordingly.

In `@src/components/Forms/FormikFilterForm.tsx`:
- Around line 26-66: The two useEffect hooks (the one that calls setSearchParams
and the one that calls setFieldValue) create a feedback loop because the second
effect unconditionally calls setFieldValue; fix by adding a guard in the effect
that syncs URL -> form so it only calls setFieldValue when the incoming
searchParams value actually differs from the current Formik value
(values[field]) or when the params represent a genuine external change;
implement this by tracking the last-synced params in a ref (e.g.,
lastSyncedParamsRef) and comparing searchParams.get(field) against values[field]
(or lastSyncedParamsRef) before calling setFieldValue for each field (skip
calling setFieldValue when equal), so filterFields, searchParams, setFieldValue,
values and paramsToReset remain the reference points for the conditional logic.

In `@src/pages/views/components/ChangesUIFilters.tsx`:
- Around line 40-42: The forEach arrow callback implicitly returns the boolean
from nextParams.delete(param) which the linter flags; change the callback to a
block-bodied function so it does not return a value (e.g. replace
paramsToReset.configChanges.forEach((param) => nextParams.delete(param)) with a
block form that calls nextParams.delete(param) inside braces) or alternatively
iterate with a for...of loop over paramsToReset.configChanges and call
nextParams.delete(param) inside the loop; ensure you update the callback around
paramsToReset.configChanges.forEach and the call to nextParams.delete
accordingly.
- Around line 135-142: The current map over Object.entries(arbitraryFilters ??
{}) passes key={value} to FilterBadge which can cause duplicate React keys when
different param names share the same value; update the key to a composite unique
identifier (for example combine the entry key and value, e.g. `${key}-${value}`)
or include the index to guarantee uniqueness, keeping the props filters={value},
paramKey={key}, and paramPrefix={paramPrefix} unchanged so the component mapping
(FilterBadge) is stable.

In `@src/pages/views/components/ChangesUISection.tsx`:
- Around line 97-108: The code in the useMemo that builds changes uses non-null
assertions for change.type and change.name inside the returned config object
(the changes mapping in ChangesUISection, function/useMemo), which can propagate
undefined; update the mapping to avoid `!` by providing safe fallbacks (e.g.,
default strings or null) for type and name or derive reasonable values when
absent (use change.type ?? 'unknown' or change.name ?? ''), and ensure config.id
uses change.config_id safely as well; keep the same shape ({ id, type, name,
deleted_at }) so downstream rendering won't break when optional fields are
missing.

In `@src/ui/Dates/TimeRangePicker/useTimeRangeParams.tsx`:
- Around line 28-57: The useEffect in useTimeRangeParams calls setParams and can
cause URL churn when multiple instances run; update the effect to avoid
unnecessary param writes by comparing the serialized nextParams to currentParams
(e.g., compare nextParams.toString() vs currentParams.toString()) before calling
setParams, or when updating call setParams with a navigation option like
replace: true to avoid pushing history entries; ensure you reference the
existing variables nextParams, currentParams and the setParams updater in
useTimeRangeParams to apply the check and only return/perform the update when
the serialized outputs differ.
🧹 Nitpick comments (4)
src/pages/views/utils/__tests__/filterTranslation.test.ts (1)

123-153: Consider adding an idempotency test for already-translated values.

The PR comments report a navigation loop where health=healthy%3A-1 (i.e., healthy:-1) is repeatedly toggled in/out of the URL. This suggests that already-translated values may be getting re-translated. For example, if healthy:-1 is fed back through translateTristate, the -1 portion could be misinterpreted.

Adding a test case where the input is an already-translated value (e.g., translateTristate("healthy:-1")) would help confirm whether re-translation is the root cause:

it("should be idempotent (not re-translate already translated values)", () => {
  // If "healthy:-1" is fed back in, the "-1" is treated as an exclusion of "1"
  // which would produce "healthy:1,1:-1" — confirming a double-translation bug
  const alreadyTranslated = "healthy:-1";
  const result = translateTristate(alreadyTranslated);
  // Decide: should this be guarded or is double-translation prevented upstream?
  expect(result).toBe(/* expected value */);
});

This directly relates to the reported navigation loop bug.

src/pages/audit-report/types/index.ts (1)

466-471: ViewSection allows both viewRef and uiRef to be undefined simultaneously.

With both fields optional, a section could have neither a view reference nor a UI reference, which seems semantically invalid. Consider enforcing at least one via a discriminated union or a runtime check.

Possible type-level enforcement
export type ViewSection =
  | { title: string; icon?: string; viewRef: ViewRef; uiRef?: undefined }
  | { title: string; icon?: string; viewRef?: undefined; uiRef: UIRef };
src/components/Forms/FormikFilterForm.tsx (1)

102-113: initialValues depends on searchParams, but Formik ignores post-mount changes to initialValues by default.

The useMemo includes searchParams in its dependency array, but <Formik initialValues={...}> only uses initialValues on mount unless enableReinitialize is set. This won't cause bugs but the searchParams dependency is misleading — it suggests reactivity that doesn't exist.

src/pages/views/components/ViewSection.tsx (1)

68-82: sectionId for viewRef sections uses unsanitized namespace/name.

Line 73 constructs sectionId from raw section.viewRef.namespace and section.viewRef.name without passing them through toSafeId. If these contain special characters, the resulting id attribute may be invalid. The other branches correctly use safeTitle.

Suggested fix
  const sectionId = useMemo(() => {
    if (section.viewRef) {
-     return `section-${section.viewRef.namespace || "default"}-${section.viewRef.name}`;
+     return `section-${toSafeId(section.viewRef.namespace || "default")}-${toSafeId(section.viewRef.name || "")}`;
    }

Comment on lines +40 to +42
paramsToReset.configChanges.forEach((param) =>
nextParams.delete(param)
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

forEach callback should not return a value.

The arrow function implicitly returns the result of nextParams.delete(param), which the linter flags. Use a block body to suppress the warning.

Suggested fix
-       paramsToReset.configChanges.forEach((param) =>
-         nextParams.delete(param)
-       );
+       paramsToReset.configChanges.forEach((param) => {
+         nextParams.delete(param);
+       });
📝 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
paramsToReset.configChanges.forEach((param) =>
nextParams.delete(param)
);
paramsToReset.configChanges.forEach((param) => {
nextParams.delete(param);
});
🧰 Tools
🪛 Biome (2.3.14)

[error] 40-40: This callback passed to forEach() iterable method should not return a value.

Either remove this return or remove the returned value.

(lint/suspicious/useIterableCallbackReturn)

🤖 Prompt for AI Agents
In `@src/pages/views/components/ChangesUIFilters.tsx` around lines 40 - 42, The
forEach arrow callback implicitly returns the boolean from
nextParams.delete(param) which the linter flags; change the callback to a
block-bodied function so it does not return a value (e.g. replace
paramsToReset.configChanges.forEach((param) => nextParams.delete(param)) with a
block form that calls nextParams.delete(param) inside braces) or alternatively
iterate with a for...of loop over paramsToReset.configChanges and call
nextParams.delete(param) inside the loop; ensure you update the callback around
paramsToReset.configChanges.forEach and the call to nextParams.delete
accordingly.

Comment on lines +135 to +142
{Object.entries(arbitraryFilters ?? {}).map(([key, value]) => (
<FilterBadge
filters={value}
key={value}
paramKey={key}
paramPrefix={paramPrefix}
/>
))}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

key={value} can collide when different param keys have the same value.

If two arbitrary filters have the same value string but different key names, React will see duplicate keys. Use a composite key instead.

Suggested fix
        {Object.entries(arbitraryFilters ?? {}).map(([key, value]) => (
          <FilterBadge
            filters={value}
-           key={value}
+           key={`${key}-${value}`}
            paramKey={key}
            paramPrefix={paramPrefix}
          />
        ))}
📝 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
{Object.entries(arbitraryFilters ?? {}).map(([key, value]) => (
<FilterBadge
filters={value}
key={value}
paramKey={key}
paramPrefix={paramPrefix}
/>
))}
{Object.entries(arbitraryFilters ?? {}).map(([key, value]) => (
<FilterBadge
filters={value}
key={`${key}-${value}`}
paramKey={key}
paramPrefix={paramPrefix}
/>
))}
🤖 Prompt for AI Agents
In `@src/pages/views/components/ChangesUIFilters.tsx` around lines 135 - 142, The
current map over Object.entries(arbitraryFilters ?? {}) passes key={value} to
FilterBadge which can cause duplicate React keys when different param names
share the same value; update the key to a composite unique identifier (for
example combine the entry key and value, e.g. `${key}-${value}`) or include the
index to guarantee uniqueness, keeping the props filters={value},
paramKey={key}, and paramPrefix={paramPrefix} unchanged so the component mapping
(FilterBadge) is stable.

Comment on lines +97 to +108
const changes = useMemo(() => {
if (!data?.changes) return [];
return data.changes.map((change) => ({
...change,
config: {
id: change.config_id!,
type: change.type!,
name: change.name!,
deleted_at: change.deleted_at
}
}));
}, [data]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Non-null assertions on optional fields type and name may cause runtime issues.

Per the ConfigChange type definition (src/api/types/configs.ts), type and name are optional (type?: string, name?: string). The ! assertions on lines 103-104 assume they're always present, which could propagate undefined into the config object and cause downstream rendering issues.

Suggested fix: provide safe fallbacks
  const changes = useMemo(() => {
    if (!data?.changes) return [];
    return data.changes.map((change) => ({
      ...change,
      config: {
        id: change.config_id!,
-       type: change.type!,
-       name: change.name!,
+       type: change.type ?? "",
+       name: change.name ?? "",
        deleted_at: change.deleted_at
      }
    }));
  }, [data]);
📝 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 changes = useMemo(() => {
if (!data?.changes) return [];
return data.changes.map((change) => ({
...change,
config: {
id: change.config_id!,
type: change.type!,
name: change.name!,
deleted_at: change.deleted_at
}
}));
}, [data]);
const changes = useMemo(() => {
if (!data?.changes) return [];
return data.changes.map((change) => ({
...change,
config: {
id: change.config_id!,
type: change.type ?? "",
name: change.name ?? "",
deleted_at: change.deleted_at
}
}));
}, [data]);
🤖 Prompt for AI Agents
In `@src/pages/views/components/ChangesUISection.tsx` around lines 97 - 108, The
code in the useMemo that builds changes uses non-null assertions for change.type
and change.name inside the returned config object (the changes mapping in
ChangesUISection, function/useMemo), which can propagate undefined; update the
mapping to avoid `!` by providing safe fallbacks (e.g., default strings or null)
for type and name or derive reasonable values when absent (use change.type ??
'unknown' or change.name ?? ''), and ensure config.id uses change.config_id
safely as well; keep the same shape ({ id, type, name, deleted_at }) so
downstream rendering won't break when optional fields are missing.

@adityathebe adityathebe force-pushed the view-ui-refs branch 2 times, most recently from f216179 to f03e761 Compare February 12, 2026 11:57
View sections can now embed native Configs and Changes tables directly
via a new uiRef field, instead of only referencing other views via viewRef.

- Add ChangesUIFilters, ConfigsUIFilters, and UIRef types
- Add ChangesUISection and ConfigsUISection components with preset filter
  seeding via URL params
- Add ConfigsUIFilters and ChangesUIFilters filter bar components
- Add filter translation utilities to convert backend formats (Kubernetes-
  style selectors) to UI internal tristate format
- Update ViewSection to branch rendering based on viewRef vs uiRef
- Update ViewWithSections key generation for uiRef sections
- Skip uiRef sections in useViewData view fetching
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants