Skip to content

feat(groups): Add Groups list page#888

Draft
CryptoRodeo wants to merge 41 commits intoguacsec:mainfrom
CryptoRodeo:tc-3451
Draft

feat(groups): Add Groups list page#888
CryptoRodeo wants to merge 41 commits intoguacsec:mainfrom
CryptoRodeo:tc-3451

Conversation

@CryptoRodeo
Copy link
Collaborator

@CryptoRodeo CryptoRodeo commented Jan 23, 2026

🚧 Work In Progress. 🚧

Demo of what it currently looks like

GroupsTreeTableDemoWIP.mp4

Summary by Sourcery

Add a new Groups listing page wired into navigation and routing, backed by a table controls context and dummy query data.

New Features:

  • Introduce a Groups page with toolbar, table, and context provider using existing table control patterns.
  • Expose a new /groups route and sidebar navigation link to access the Groups list.
  • Add a groups table layout supporting expansion, bulk selection, filtering, and pagination based on placeholder group data.

Enhancements:

  • Extend table controls typings to support optional column keys and add a persistence key prefix for groups.
  • Provide a helper to build a hierarchical tree structure from flat group data for use in the Groups UI.

Summary by Sourcery

Add a new Groups listing page with navigation, routing, and table controls based on dummy group data.

New Features:

  • Introduce a Groups page composed of a toolbar, hierarchical groups table, and context provider using existing table control patterns.
  • Expose a new /groups route and sidebar navigation entry to access the Groups list.
  • Add a groups query hook and dummy data to back the Groups table until a real backend is available.

Enhancements:

  • Extend table persistence key prefixes to include groups to support saved table state.
  • Provide helpers and UI to render groups as a tree with expandable rows, labels, bulk selection, filtering, and pagination.

Work In Progress.

Signed-off-by: Bryan Ramos <bramos@redhat.com>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 23, 2026

Reviewer's Guide

Adds a new Groups list page wired into routing, sidebar navigation, and the shared table-controls system, backed by a dummy groups query that builds a hierarchical tree and renders an expandable, bulk-selectable table with filtering and pagination.

Sequence diagram for loading and displaying the Groups list

sequenceDiagram
  actor User
  participant Sidebar
  participant Router
  participant Groups
  participant GroupsProvider
  participant TableControls as TableControls_hooks
  participant GroupsQuery as useFetchGroups
  participant ReactQuery as ReactQuery_useQuery

  User->>Sidebar: Click_Groups_link
  Sidebar->>Router: Navigate_to_Paths.groups
  Router->>Groups: Render_Groups_component
  Groups->>GroupsProvider: Wrap_children_in_GroupsProvider

  GroupsProvider->>TableControls: useTableControlState(config_for_groups)
  TableControls-->>GroupsProvider: tableControlState

  GroupsProvider->>GroupsQuery: useFetchGroups(hubRequestParams)
  GroupsQuery->>ReactQuery: useQuery(queryKey_groups, queryFn)
  ReactQuery-->>GroupsQuery: dummyData(total,items)
  GroupsQuery->>GroupsQuery: buildGroupTree(items)
  GroupsQuery-->>GroupsProvider: roots,total,isFetching,fetchError

  GroupsProvider->>TableControls: useTableControlProps(state,roots,total)
  TableControls-->>GroupsProvider: tableControls
  GroupsProvider-->>Groups: Provide_GroupsContext(tableControls,bulkSelection,...)

  Groups->>GroupsToolbar: Render_toolbar_with_GroupsContext
  Groups->>GroupsTable: Render_table_with_GroupsContext

  GroupsTable->>GroupsContext: Read_tableControls_and_bulkSelection
  GroupsTable->>GroupsTable: flattenVisibleRows(currentPageItems,isCellExpanded)
  GroupsTable-->>User: Display_expandable_tree_table_with_pagination
Loading

Class diagram for new Groups types and components

classDiagram
  class TGroupDD {
    +string id
    +string parent
    +string name
    +Record~string,string~ labels
  }

  class TGroupTreeNode {
    +string id
    +string parent
    +string name
    +Record~string,string~ labels
    +TGroupTreeNode[] children
  }

  TGroupDD <|-- TGroupTreeNode

  class UseFetchGroupsHook {
    +useFetchGroups(params, disableQuery)
    +buildGroupTree(items)
  }

  TGroupDD --> UseFetchGroupsHook : items
  TGroupTreeNode --> UseFetchGroupsHook : roots

  class IGroupsContext {
    +ITableControls tableControls
    +BulkSelectionValues bulkSelection_controls
    +boolean bulkSelection_isEnabled
    +number totalItemCount
    +boolean isFetching
    +AxiosError fetchError
  }

  class GroupsContext {
    +IGroupsContext value
  }

  class GroupsProvider {
    +boolean isBulkSelectionEnabled
    +ReactNode children
  }

  class GroupsToolbar {
    +render()
  }

  class GroupsTable {
    +render()
    +flattenVisibleRows(nodes, isExpanded, depth)
  }

  class GroupTableData {
    +TGroupTreeNode item
    +render()
  }

  class GroupLabels {
    +Record~string,string~ labels
    +render()
  }

  class GroupsPage {
    +render()
  }

  class TableControlsHooks {
    +useTableControlState(config)
    +useTableControlProps(args)
  }

  class BulkSelectionHook {
    +useBulkSelection(config)
  }

  class TablePersistenceKeyPrefixes {
    +string groups
  }

  GroupsContext ..> IGroupsContext
  GroupsProvider --> GroupsContext : provides
  GroupsProvider --> TableControlsHooks : uses
  GroupsProvider --> BulkSelectionHook : uses
  GroupsProvider --> UseFetchGroupsHook : uses

  GroupsPage --> GroupsProvider
  GroupsPage --> GroupsToolbar
  GroupsPage --> GroupsTable

  GroupsToolbar --> GroupsContext : consumes
  GroupsTable --> GroupsContext : consumes
  GroupsTable --> GroupTableData
  GroupTableData --> GroupLabels

  TablePersistenceKeyPrefixes <.. GroupsProvider : uses_prefix_groups

  UseFetchGroupsHook ..> ReactQuery : uses

  class ReactQuery {
    +useQuery(options)
  }
Loading

Flow diagram for navigation and routing to the Groups page

flowchart LR
  subgraph SidebarNavigation
    SBOMsLink["SBOMs_nav_link"]
    GroupsLink["Groups_nav_link"]
  end

  PathsGroups["Paths.groups_/groups"]
  RouterConfig["AppRoutes_route_for_groups"]
  GroupsPage["Groups_page_component"]
  Provider["GroupsProvider_context"]
  Toolbar["GroupsToolbar"]
  Table["GroupsTable"]

  GroupsLink --> PathsGroups
  PathsGroups --> RouterConfig
  RouterConfig --> GroupsPage
  GroupsPage --> Provider
  Provider --> Toolbar
  Provider --> Table
Loading

File-Level Changes

Change Details Files
Introduce a Groups feature area with routed page, toolbar, and table wired into shared table-controls and selection utilities.
  • Add lazy-loaded Groups page route and /groups path constant, and register it in the main router.
  • Add a "Groups" nav item in the sidebar pointing at the new path.
  • Implement Groups page shell that sets document metadata, page sections, and wraps toolbar and table in a GroupsProvider context.
client/src/app/Routes.tsx
client/src/app/layout/sidebar.tsx
client/src/app/Constants.ts
client/src/app/pages/groups/groups.tsx
client/src/app/pages/groups/index.ts
Create a GroupsContext provider that connects table-controls state with the groups query and bulk selection.
  • Initialize table control state for groups with pagination, sorting on name, text search filter, selection, and single-row expansion using the groups persistence prefix and URL param persistence.
  • Wire useFetchGroups via getHubRequestParams into the table-controls, mapping hub sort fields and providing idProperty, currentPageItems, and totalItemCount.
  • Configure useBulkSelection over the table-controlled items with id-based equality and expose controls plus an isEnabled flag via context.
client/src/app/pages/groups/groups-context.tsx
client/src/app/Constants.ts
Implement a groups query hook using dummy hierarchical data and a helper to build a tree from flat groups.
  • Define flat group DTO type (TGroupDD) and tree node type (TGroupTreeNode) including children.
  • .Implement useFetchGroups using react-query with a static dummy dataset and standard query key, returning transformed roots and total along with loading/error state.
  • Add buildGroupTree utility that indexes items by id, wires parent-child relationships, and returns roots and lookup map.
  • Provide a dummy hierarchical groups dataset for development and UI wiring.
client/src/app/queries/groups.ts
client/src/app/pages/groups/dummy-data.ts
Build the Groups table UI, including hierarchical row flattening, bulk selection integration, and pagination.
  • Implement flattenVisibleRows helper to derive a flat list of visible rows from the groups tree using expansion state and depth tracking for indentation.
  • Render table rows via ConditionalTableBody and TableRowContentWithControls, wiring in expansion state, row/td props, and optional bulk-selection checkbox helpers.
  • Use depth-based padding to visually indent child rows and add bottom SimplePagination synchronized with table-controls pagination props.
client/src/app/pages/groups/groups-table.tsx
Add toolbar and row content components for Groups, including filters, create button, labels, and description.
  • Create GroupsToolbar that binds FilterToolbar and top SimplePagination to the groups table-controls toolbar prop helpers and includes a stubbed "Create group" primary button.
  • Add GroupTableData to render the group name as a link, associated labels, and a placeholder description using PatternFly layout components.
  • Introduce GroupLabels component that renders a LabelGroup from a labels record, skipping rendering when no labels are present.
client/src/app/pages/groups/groups-toolbar.tsx
client/src/app/pages/groups/group-table-data.tsx
client/src/app/pages/groups/group-labels.tsx
Adjust shared table-controls typings and table persistence prefixes to support the new Groups table.
  • Extend TablePersistenceKeyPrefixes with a groups-specific key prefix so groups table state can be persisted distinctly.
  • Tidy generics/union formatting in table-controls types to keep type definitions consistent and readable.
client/src/app/Constants.ts
client/src/app/hooks/table-controls/types.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@CryptoRodeo CryptoRodeo marked this pull request as draft January 23, 2026 23:14
@CryptoRodeo CryptoRodeo self-assigned this Jan 23, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 7 issues, and left some high level feedback:

  • In useFetchGroups, the query currently resolves dummyData and buildGroupTree is called on dummyData.items instead of the queried data?.items; this will make it easy to forget to switch to real data—consider wiring the tree-building to the actual response (and memoizing it) so the hook’s output always reflects the query result.
  • The GroupsTable renders multiple <Tbody> elements and uses fragments and some <Tbody> tags without stable keys; this can confuse React and PatternFly table expectations—consider using a single <Tbody> with PatternFly’s built‑in expandable row pattern (or at least ensure each logical row/section has a stable key on the immediate mapped element).
  • There are a few type loosenings that could cause issues later (e.g., GroupTableData’s item is typed as any and getTdProps now allows columnKey to be optional); tightening these to TGroupTreeNode and requiring columnKey where appropriate will help catch misuse of table helpers at compile time.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `useFetchGroups`, the query currently resolves `dummyData` and `buildGroupTree` is called on `dummyData.items` instead of the queried `data?.items`; this will make it easy to forget to switch to real data—consider wiring the tree-building to the actual response (and memoizing it) so the hook’s output always reflects the query result.
- The `GroupsTable` renders multiple `<Tbody>` elements and uses fragments and some `<Tbody>` tags without stable keys; this can confuse React and PatternFly table expectations—consider using a single `<Tbody>` with PatternFly’s built‑in expandable row pattern (or at least ensure each logical row/section has a stable key on the immediate mapped element).
- There are a few type loosenings that could cause issues later (e.g., `GroupTableData`’s `item` is typed as `any` and `getTdProps` now allows `columnKey` to be optional); tightening these to `TGroupTreeNode` and requiring `columnKey` where appropriate will help catch misuse of table helpers at compile time.

## Individual Comments

### Comment 1
<location> `client/src/app/queries/groups.ts:17-26` </location>
<code_context>
+const dummyData: { total: number, items: TGroupDD[], tree: Record<string, any> } = {
</code_context>

<issue_to_address>
**issue (bug_risk):** `dummyData.total` does not match the number of items defined.

`total` is set to 10 but there are only 9 entries in `items`, so pagination will indicate more rows than exist. Please keep `total` consistent with `items.length`, even for stub data, to avoid confusing UI behavior and hiding real pagination issues.
</issue_to_address>

### Comment 2
<location> `client/src/app/pages/groups/groups-table.tsx:52-61` </location>
<code_context>
+      <Table {...tableProps} aria-label="sbom-table">
</code_context>

<issue_to_address>
**nitpick (bug_risk):** The table ARIA label and pagination ID still reference `sbom-table` instead of `groups`.

`Table`'s `aria-label` and `SimplePagination`'s `idPrefix` still use `"sbom-table"`, likely copied from the SBOM view. Please rename them (e.g., to `"groups-table"`) so the DOM/ARIA metadata correctly reflects this page’s content and remains clear for accessibility and automation.
</issue_to_address>

### Comment 3
<location> `client/src/app/pages/groups/groups-table.tsx:63-51` </location>
<code_context>
+          isNoData={totalItemCount === 0}
+          numRenderedColumns={numRenderedColumns}
+        >
+          {currentPageItems.map((item, rowIndex) => {
+            return (
+              <>
+                <Tbody key={item.id} isExpanded={isCellExpanded(item)}>
+                  <Tr {...getTrProps({ item })}>
</code_context>

<issue_to_address>
**issue (bug_risk):** Using a bare fragment inside `.map` without a key will trigger React key warnings.

In `currentPageItems.map`, the fragment (`<>...</>`) is the top-level returned element, but the `key` is on the nested `<Tbody>`. React expects the `key` on the top-level element, which can cause key warnings and reconciliation issues. Either move the `key` to the fragment (e.g., `<React.Fragment key={item.id}>`) or remove the fragment and wrap the rows so that the keyed element is the one directly returned from `map`.
</issue_to_address>

### Comment 4
<location> `client/src/app/pages/groups/groups-table.tsx:90-92` </location>
<code_context>
+
+                </Tbody>
+                {
+                  item.children.map((childNode) => {
+                    return (
+                      <Tbody>
+                        <Tr
+                          key={childNode.id}
</code_context>

<issue_to_address>
**issue (bug_risk):** Child `Tbody` elements are missing a `key` and will also hit React’s key requirements.

For each `item.children.map` iteration, add a `key` prop to the returned `<Tbody>` (for example, `key={childNode.id}`) to avoid React warnings and potential instability when rows are added, removed, or reordered.
</issue_to_address>

### Comment 5
<location> `client/src/app/pages/groups/groups-context.tsx:89-93` </location>
<code_context>
+    idProperty: "id",
+    currentPageItems: groups,
+    totalItemCount,
+    isLoading: false,
+    hasActionsColumn: true,
+  });
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Table controls are always told `isLoading: false`, ignoring the actual fetch status.

`useFetchGroups` already provides `isFetching`, but the table controls ignore it and always show `isLoading: false`. Wire `isLoading` to `isFetching` so loading indicators and disabled states correctly reflect re-fetches and avoid inconsistent UX.

```suggestion
  const tableControls = useTableControlProps({
    ...tableControlState,
    idProperty: "id",
    currentPageItems: groups,
    totalItemCount,
    isLoading: isFetching,
```
</issue_to_address>

### Comment 6
<location> `client/src/app/pages/groups/group-table-data.tsx:4` </location>
<code_context>
+import { Stack, StackItem, Flex, FlexItem, Label, LabelGroup } from "@patternfly/react-core"
+import { NavLink } from "react-router-dom"
+
+export const GroupTableData = ({ item }: { item: any }) => {
+
+  return (
</code_context>

<issue_to_address>
**suggestion:** The `item` prop is typed as `any`, losing the structural guarantees you have elsewhere.

`GroupTableData` takes `{ item: any }` even though the groups domain already defines `TGroupTreeNode`. Typing `item` as `TGroupTreeNode` (or a shared interface) would give you compile-time checks for `item.name` and any future fields rendered here, making changes safer and catching shape mismatches earlier.

Suggested implementation:

```typescript
import { Stack, StackItem, Flex, FlexItem, Label, LabelGroup } from "@patternfly/react-core"
import { NavLink } from "react-router-dom"
import type { TGroupTreeNode } from "PATH/TO/GROUPS/DOMAIN/TGroupTreeNode"

export const GroupTableData = ({ item }: { item: TGroupTreeNode }) => {

```

You’ll need to adjust the import path for `TGroupTreeNode` so it matches your existing groups domain type export. For example, it might be something like:
- `import type { TGroupTreeNode } from "@/app/api/groups/types"` or
- `import type { TGroupTreeNode } from "../../api/groups/types"`

Also make sure that `TGroupTreeNode` exposes at least the fields you are using in this component (e.g. `name`, `id`, etc.), or adjust the type if `GroupTableData` needs a more specific subset.
</issue_to_address>

### Comment 7
<location> `client/src/app/pages/groups/group-table-data.tsx:14-16` </location>
<code_context>
               </NavLink>
             </li>
+            <li className={nav.navItem}>
+              <NavLink
+                to={Paths.groups}
+                className={({ isActive }) => {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Linking the group name to an external placeholder URL is likely not the intended navigation behavior.

The `NavLink` for the group name still points to `'https://example.com'`, which looks like a temporary stub. If this should go to a group details view, please update it to the correct internal route, or remove the link until that route exists to avoid sending users off-site unexpectedly.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 14 to 16
<NavLink
className="pf-v6-c-button pf-m-link pf-m-inline"
to={'https://example.com'}
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): Linking the group name to an external placeholder URL is likely not the intended navigation behavior.

The NavLink for the group name still points to 'https://example.com', which looks like a temporary stub. If this should go to a group details view, please update it to the correct internal route, or remove the link until that route exists to avoid sending users off-site unexpectedly.

Signed-off-by: Bryan Ramos <bramos@redhat.com>
@codecov
Copy link

codecov bot commented Jan 23, 2026

Codecov Report

❌ Patch coverage is 50.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 63.58%. Comparing base (3bb6d30) to head (396f7db).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
client/src/app/layout/sidebar.tsx 0.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #888      +/-   ##
==========================================
- Coverage   63.60%   63.58%   -0.03%     
==========================================
  Files         184      184              
  Lines        3264     3262       -2     
  Branches      742      743       +1     
==========================================
- Hits         2076     2074       -2     
+ Misses        898      895       -3     
- Partials      290      293       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
The structure should be what the ADR defines

Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Go with a flattening strategy.

Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
@CryptoRodeo
Copy link
Collaborator Author

@sourcery-ai review

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 4 issues, and left some high level feedback:

  • In GroupLabels, the emptiness check Object.keys(labels) === 0 is always false; use Object.keys(labels).length === 0 to avoid rendering an empty LabelGroup.
  • In GroupsTable, the bottom SimplePagination uses idPrefix="sbom-table" while the rest of the page is groups-specific (and the toolbar uses groups-table), which looks like a copy-paste mismatch that could complicate DOM IDs or test selectors.
  • In GroupTableData, the NavLink points to a hard-coded external URL (https://example.com); consider wiring this to the appropriate groups route or making the placeholder explicit so it doesn’t ship as-is.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `GroupLabels`, the emptiness check `Object.keys(labels) === 0` is always false; use `Object.keys(labels).length === 0` to avoid rendering an empty `LabelGroup`.
- In `GroupsTable`, the bottom `SimplePagination` uses `idPrefix="sbom-table"` while the rest of the page is groups-specific (and the toolbar uses `groups-table`), which looks like a copy-paste mismatch that could complicate DOM IDs or test selectors.
- In `GroupTableData`, the `NavLink` points to a hard-coded external URL (`https://example.com`); consider wiring this to the appropriate groups route or making the placeholder explicit so it doesn’t ship as-is.

## Individual Comments

### Comment 1
<location> `client/src/app/hooks/table-controls/types.ts:426` </location>
<code_context>
      */
     getTdProps: (
-      args: { columnKey: TColumnKey } & DiscriminatedArgs<
+      args: { columnKey?: TColumnKey } & DiscriminatedArgs<
         "isCompoundExpandToggle",
         { item: TItem; rowIndex: number }
</code_context>

<issue_to_address>
**issue (bug_risk):** Making `columnKey` optional in `getTdProps` may break assumptions in existing call sites and implementations.

Since `columnKey` was previously required, making it optional can break existing `getTdProps` implementations that assume it’s always defined (e.g., using it as a map key or for metrics), leading to runtime errors. Consider either a separate helper for the compound expand toggle case or a discriminated union where only the `isCompoundExpandToggle` branch omits `columnKey`, while keeping it required elsewhere.
</issue_to_address>

### Comment 2
<location> `client/src/app/pages/groups/group-labels.tsx:8` </location>
<code_context>
+
+// TODO: Change color based on label type
+export const GroupLabels = ({ labels }: Props) => {
+  if (!labels || Object.keys(labels) === 0) {
+    return null;
+  }
</code_context>

<issue_to_address>
**issue (bug_risk):** The empty-labels check is incorrect and will never treat an empty object as empty.

`Object.keys(labels)` returns an array, so comparing it directly to `0` is always false. As a result, an empty `labels` object still renders `LabelGroup`. Use `Object.keys(labels).length === 0` (or `!Object.keys(labels).length`) instead.
</issue_to_address>

### Comment 3
<location> `client/src/app/pages/groups/groups-table.tsx:129-133` </location>
<code_context>
+          })}
+        </ConditionalTableBody >
+      </Table >
+      <SimplePagination
+        idPrefix="sbom-table"
+        isTop={false}
+        paginationProps={paginationProps}
</code_context>

<issue_to_address>
**suggestion (bug_risk):** The pagination `idPrefix` is still `sbom-table`, which is confusing and may cause DOM id collisions.

This appears copied from the SBOM table. Reusing `idPrefix="sbom-table"` here complicates debugging and test selectors, and risks duplicate DOM IDs when both views are rendered. Please use a more specific prefix, e.g. `"groups-table"`.

```suggestion
      <SimplePagination
        idPrefix="groups-table"
        isTop={false}
        paginationProps={paginationProps}
      />
```
</issue_to_address>

### Comment 4
<location> `client/src/app/queries/groups.ts:293-295` </location>
<code_context>
+) => {
+  //  const { q, ...rest } = requestParamsQuery((params));
+
+  const { data, isLoading, error, refetch } = useQuery({
+    queryKey: [GroupQueryKey, params],
+    queryFn: () => Promise.resolve(dummyData),
+    enabled: !disableQuery
+  });
</code_context>

<issue_to_address>
**suggestion (bug_risk):** The hook ignores the backend `total` and derives `total` from roots only, which may misrepresent pagination once real data is used.

`useFetchGroups` currently sets `total` to `roots.length` even though the API response includes its own `total`. If the real API returns only a page of items plus a larger `total`, this will under-report the count and can break pagination controls. Please use `data?.total` for the overall count (optionally still exposing `roots.length` separately) so this hook aligns with other list hooks and behaves correctly once wired to the real backend.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Whoops

Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
@CryptoRodeo
Copy link
Collaborator Author

@sourcery-ai review

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • There is a type mismatch between labels in TGroupDD (Record<string, any>) and in GroupLabels (Record<string, string>); aligning these will prevent subtle runtime issues and unnecessary casting.
  • The NavLink in GroupTableData is hard-coded to https://example.com; consider wiring this to a real route or an ID-based detail path so the link behavior matches the data model.
  • The useFetchGroups hook still relies on dummyData and a Promise.resolve query function; adding a clear TODO and centralizing this mock behind a flag or test helper will reduce the risk of it persisting into production unintentionally.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- There is a type mismatch between `labels` in `TGroupDD` (`Record<string, any>`) and in `GroupLabels` (`Record<string, string>`); aligning these will prevent subtle runtime issues and unnecessary casting.
- The `NavLink` in `GroupTableData` is hard-coded to `https://example.com`; consider wiring this to a real route or an ID-based detail path so the link behavior matches the data model.
- The `useFetchGroups` hook still relies on `dummyData` and a `Promise.resolve` query function; adding a clear TODO and centralizing this mock behind a flag or test helper will reduce the risk of it persisting into production unintentionally.

## Individual Comments

### Comment 1
<location> `client/src/app/pages/groups/groups-table.tsx:85-86` </location>
<code_context>
+  return (
+    <>
+      <Table {...tableProps} aria-label="sbom-groups-table">
+        <Thead>
+          <Tr>
+          </Tr>
+        </Thead>
</code_context>

<issue_to_address>
**issue:** The table header row is empty, which produces a column-less header in the UI.

Since `<Thead>` contains an empty `<Tr>`, the table renders with no visible column headers. Consider adding at least one meaningful header cell (e.g., a placeholder like "Name") or omitting the header until real columns are available to avoid a confusing header row.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
@CryptoRodeo
Copy link
Collaborator Author

@sourcery-ai review

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In GroupTableData and GroupLabels, you're wrapping labels in LabelGroup twice, which will render nested LabelGroup components; consider letting only GroupLabels own the LabelGroup or having it render just Labels.
  • The NavLink in GroupTableData is hard-coded to https://example.com; if this is a temporary placeholder, consider wiring it to an internal path (e.g., using Paths) or at least marking it with a clear TODO so it’s not forgotten.
  • In GroupsContext, isSortEnabled is set to false but hubSortFieldKeys is still configured in getHubRequestParams; either enable sorting or remove the unused sort mapping to avoid confusion.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `GroupTableData` and `GroupLabels`, you're wrapping labels in `LabelGroup` twice, which will render nested `LabelGroup` components; consider letting only `GroupLabels` own the `LabelGroup` or having it render just `Label`s.
- The `NavLink` in `GroupTableData` is hard-coded to `https://example.com`; if this is a temporary placeholder, consider wiring it to an internal path (e.g., using `Paths`) or at least marking it with a clear TODO so it’s not forgotten.
- In `GroupsContext`, `isSortEnabled` is set to `false` but `hubSortFieldKeys` is still configured in `getHubRequestParams`; either enable sorting or remove the unused sort mapping to avoid confusion.

## Individual Comments

### Comment 1
<location> `client/src/app/pages/groups/group-table-data.tsx:14-23` </location>
<code_context>
+          alignItems={{ default: 'alignItemsCenter' }}
+          gap={{ default: 'gapSm' }}
+          flexWrap={{ default: "wrap" }}>
+          <FlexItem>
+            <NavLink
+              className="pf-v6-c-button pf-m-link pf-m-inline"
+              to={'https://example.com'}
+            >
+              {item.name}
+            </NavLink>
+          </FlexItem>
+          <FlexItem>
+            <LabelGroup>
+              <GroupLabels labels={item.labels} />
+            </LabelGroup>
+          </FlexItem>
</code_context>

<issue_to_address>
**issue:** Avoid nesting `LabelGroup` inside another `LabelGroup` when rendering labels.

`GroupLabels` already wraps its contents in a `LabelGroup`, so this creates nested `LabelGroup`s that can affect spacing and styling. Either drop the outer `LabelGroup` and let `GroupLabels` handle layout, or change `GroupLabels` to render plain `Label`s and keep the `LabelGroup` only here.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
@CryptoRodeo
Copy link
Collaborator Author

@sourcery-ai review

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The useFetchGroups hook imports dummyData from the page layer, which inverts normal layering; consider moving the dummy data into the queries layer (or a shared fixtures module) so the data layer doesn’t depend on UI code.
  • In GroupTableData, the group name links to a hardcoded https://example.com; even as a placeholder, it would be safer to either omit the link or route to an internal path to avoid shipping an external URL by mistake.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `useFetchGroups` hook imports `dummyData` from the page layer, which inverts normal layering; consider moving the dummy data into the queries layer (or a shared fixtures module) so the data layer doesn’t depend on UI code.
- In `GroupTableData`, the group name links to a hardcoded `https://example.com`; even as a placeholder, it would be safer to either omit the link or route to an internal path to avoid shipping an external URL by mistake.

## Individual Comments

### Comment 1
<location> `client/src/app/pages/groups/group-table-data.tsx:15-17` </location>
<code_context>
               </NavLink>
             </li>
+            <li className={nav.navItem}>
+              <NavLink
+                to={Paths.groups}
+                className={({ isActive }) => {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Using `NavLink` with an external URL is likely unintended and may break routing expectations.

`NavLink` is designed for internal routes and SPA history, so targeting a hardcoded external URL is confusing and may bypass the router. Use a plain `<a href="https://example.com">` for external navigation, or better, point `NavLink` at the appropriate internal route (e.g., a group details path derived from `item.id`).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 15 to 17
<NavLink
className="pf-v6-c-button pf-m-link pf-m-inline"
to={'https://example.com'}
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): Using NavLink with an external URL is likely unintended and may break routing expectations.

NavLink is designed for internal routes and SPA history, so targeting a hardcoded external URL is confusing and may bypass the router. Use a plain <a href="https://example.com"> for external navigation, or better, point NavLink at the appropriate internal route (e.g., a group details path derived from item.id).

CryptoRodeo and others added 10 commits January 23, 2026 23:01
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Also format

Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
No reason to flatten twice

Signed-off-by: Bryan Ramos <bramos@redhat.com>
Also update comment.

Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
I guess PatternFly has that. I should read the docs more.

Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
- Cleanup unused code
- Go back to single node selection
- Add action column with dummy actions

Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
- remove comments
- update return value names

Signed-off-by: Bryan Ramos <bramos@redhat.com>
Signed-off-by: Bryan Ramos <bramos@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant