Fix query results table deduplicating rows when data contains an id column (#42937)

## 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 <r@rachael.wtf>
Co-authored-by: nulmete <nicoulmete1@gmail.com>
This commit is contained in:
kilo-code-bot[bot] 2026-04-06 13:41:02 -03:00 committed by GitHub
parent b447918b44
commit 344e4f2dcd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 15 additions and 2 deletions

View file

@ -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,

View file

@ -122,6 +122,7 @@ interface ITableContainerProps<T = any> {
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 = <T,>({
persistSelectedRows,
onClearSelection = noop,
suppressHeaderActions,
getRowId,
}: ITableContainerProps<T>) => {
const isControlledSearchQuery = controlledSearchQuery !== undefined;
const [searchQuery, setSearchQuery] = useState(defaultSearchQuery);
@ -578,6 +580,7 @@ const TableContainer = <T,>({
setExportRows={setExportRows}
onClearSelection={onClearSelection}
suppressHeaderActions={suppressHeaderActions}
getRowId={getRowId}
persistSelectedRows={persistSelectedRows}
hideFooter={hideFooter}
/>

View file

@ -211,6 +211,7 @@ const HQRTable = ({
emptyComponent={() => null}
defaultSortHeader={columnConfigs[0].id}
defaultSortDirection="asc"
getRowId={(_row, index) => String(index)}
/>
)}
</div>

View file

@ -150,6 +150,7 @@ const QueryReport = ({
customControl={() => renderTableButtons()}
setExportRows={setFilteredResults}
renderCount={renderResultsCount}
getRowId={(_row, index) => String(index)}
/>
</div>
);

View file

@ -234,6 +234,7 @@ const QueryResults = ({
tableType === "results" ? setFilteredResults : setFilteredErrors
}
renderCount={() => renderCount(tableType)}
getRowId={(_row, index) => String(index)}
/>
</div>
);