From 344e4f2dcd3992ba4da3fc681c3100273be5e435 Mon Sep 17 00:00:00 2001 From: "kilo-code-bot[bot]" <240665456+kilo-code-bot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 13:41:02 -0300 Subject: [PATCH] Fix query results table deduplicating rows when data contains an `id` column (#42937) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Closes #42402 - **Root Cause 1 (Row deduplication):** `DataTable`'s `getRowId` used `row.id` directly as the react-table row identity. When query results contain an `id` column with non-unique values (e.g., `SELECT id FROM processes` where multiple hosts share PID 0), react-table deduplicates rows with the same row ID, displaying fewer results than expected. - **Root Cause 2 (Misleading count):** The "Showing X results" count was derived from react-table's `rows` array, which was already deduplicated. Fixing Root Cause 1 resolves this as well — all rows are now preserved, so the count is accurate. ## Changes - **`DataTable.tsx`**: Changed `getRowId` to append the array index to the row ID (`${row.id}-${index}` instead of `String(row.id)`), guaranteeing uniqueness while preserving the `id` prefix. - **`InstallSoftwareTable.tsx`**: Updated `generateSelectedRows` to produce keys matching the new `getRowId` format (`${software.id}-${index}`), so that `defaultSelectedRows` continues to correctly persist row selection. ## QA 1. Run a live query like `SELECT id FROM processes` targeting multiple hosts → all rows should appear (no deduplication). 2. Verify the results count matches the actual number of displayed rows. 3. Verify the Setup Experience > Install Software table still correctly shows pre-selected software with checkmarks persisted across pagination. --- Built for [Rachael Shaw](https://fleetdm.slack.com/archives/D0AFC5BRFHD/p1775167005579149?thread_ts=1775164653.589489&cid=D0AFC5BRFHD) by [Kilo for Slack](https://kilo.ai/features/slack-integration) --------- Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> Co-authored-by: Rachael Shaw Co-authored-by: nulmete --- .../components/TableContainer/DataTable/DataTable.tsx | 11 +++++++++-- frontend/components/TableContainer/TableContainer.tsx | 3 +++ .../details/HostQueryReport/HQRTable/HQRTable.tsx | 1 + .../details/components/QueryReport/QueryReport.tsx | 1 + .../edit/components/QueryResults/QueryResults.tsx | 1 + 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/frontend/components/TableContainer/DataTable/DataTable.tsx b/frontend/components/TableContainer/DataTable/DataTable.tsx index 49e34060c9..40b40dc0eb 100644 --- a/frontend/components/TableContainer/DataTable/DataTable.tsx +++ b/frontend/components/TableContainer/DataTable/DataTable.tsx @@ -81,6 +81,10 @@ interface IDataTableProps { setExportRows?: (rows: Row[]) => void; onClearSelection?: () => void; suppressHeaderActions?: boolean; + /** Optional override for react-table's row ID derivation. + * Note: avoid index-only row IDs in server-side paginated or selectable tables, + * as IDs would collide across pages. */ + getRowId?: (row: any, index: number) => string; } interface IHeaderGroup extends HeaderGroup { @@ -128,6 +132,7 @@ const DataTable = ({ setExportRows, onClearSelection = noop, suppressHeaderActions, + getRowId: getRowIdProp, }: IDataTableProps): JSX.Element => { // used to track the initial mount of the component. const isInitialRender = useRef(true); @@ -181,8 +186,10 @@ const DataTable = ({ columns, data, // Use a stable row ID when available (row.id), otherwise fall back to the index-based ID (default of react-table) - getRowId: (row: any, index: number) => - row && row.id != null ? String(row.id) : String(index), + getRowId: + getRowIdProp ?? + ((row: any, index: number) => + row && row.id != null ? String(row.id) : String(index)), initialState: { sortBy: initialSortBy, pageIndex: defaultPageIndex, diff --git a/frontend/components/TableContainer/TableContainer.tsx b/frontend/components/TableContainer/TableContainer.tsx index aa8979adf1..ad6da7c6e7 100644 --- a/frontend/components/TableContainer/TableContainer.tsx +++ b/frontend/components/TableContainer/TableContainer.tsx @@ -122,6 +122,7 @@ interface ITableContainerProps { onClearSelection?: () => void; /** don't show the Clear selection button and selected item count when items are selected */ suppressHeaderActions?: boolean; + getRowId?: (row: any, index: number) => string; } const baseClass = "table-container"; @@ -184,6 +185,7 @@ const TableContainer = ({ persistSelectedRows, onClearSelection = noop, suppressHeaderActions, + getRowId, }: ITableContainerProps) => { const isControlledSearchQuery = controlledSearchQuery !== undefined; const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); @@ -578,6 +580,7 @@ const TableContainer = ({ setExportRows={setExportRows} onClearSelection={onClearSelection} suppressHeaderActions={suppressHeaderActions} + getRowId={getRowId} persistSelectedRows={persistSelectedRows} hideFooter={hideFooter} /> diff --git a/frontend/pages/hosts/details/HostQueryReport/HQRTable/HQRTable.tsx b/frontend/pages/hosts/details/HostQueryReport/HQRTable/HQRTable.tsx index 1fd1c602e8..c7090efb81 100644 --- a/frontend/pages/hosts/details/HostQueryReport/HQRTable/HQRTable.tsx +++ b/frontend/pages/hosts/details/HostQueryReport/HQRTable/HQRTable.tsx @@ -211,6 +211,7 @@ const HQRTable = ({ emptyComponent={() => null} defaultSortHeader={columnConfigs[0].id} defaultSortDirection="asc" + getRowId={(_row, index) => String(index)} /> )} diff --git a/frontend/pages/queries/details/components/QueryReport/QueryReport.tsx b/frontend/pages/queries/details/components/QueryReport/QueryReport.tsx index f3e0ee8509..8ded5041ea 100644 --- a/frontend/pages/queries/details/components/QueryReport/QueryReport.tsx +++ b/frontend/pages/queries/details/components/QueryReport/QueryReport.tsx @@ -150,6 +150,7 @@ const QueryReport = ({ customControl={() => renderTableButtons()} setExportRows={setFilteredResults} renderCount={renderResultsCount} + getRowId={(_row, index) => String(index)} /> ); diff --git a/frontend/pages/queries/edit/components/QueryResults/QueryResults.tsx b/frontend/pages/queries/edit/components/QueryResults/QueryResults.tsx index b764bce7c4..cdb12dfa41 100644 --- a/frontend/pages/queries/edit/components/QueryResults/QueryResults.tsx +++ b/frontend/pages/queries/edit/components/QueryResults/QueryResults.tsx @@ -234,6 +234,7 @@ const QueryResults = ({ tableType === "results" ? setFilteredResults : setFilteredErrors } renderCount={() => renderCount(tableType)} + getRowId={(_row, index) => String(index)} /> );