mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
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:
parent
b447918b44
commit
344e4f2dcd
5 changed files with 15 additions and 2 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -211,6 +211,7 @@ const HQRTable = ({
|
|||
emptyComponent={() => null}
|
||||
defaultSortHeader={columnConfigs[0].id}
|
||||
defaultSortDirection="asc"
|
||||
getRowId={(_row, index) => String(index)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ const QueryReport = ({
|
|||
customControl={() => renderTableButtons()}
|
||||
setExportRows={setFilteredResults}
|
||||
renderCount={renderResultsCount}
|
||||
getRowId={(_row, index) => String(index)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -234,6 +234,7 @@ const QueryResults = ({
|
|||
tableType === "results" ? setFilteredResults : setFilteredErrors
|
||||
}
|
||||
renderCount={() => renderCount(tableType)}
|
||||
getRowId={(_row, index) => String(index)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue